diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cd0efd5db5..3e622a32b9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,12 +7,12 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'b729f9f5', + 'core.pkg.css' => 'b7b8d101', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', 'differential.pkg.js' => 'd0cd0df6', - 'diffusion.pkg.css' => 'dc8e0cc2', + 'diffusion.pkg.css' => '91c5d3a6', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', @@ -52,7 +52,7 @@ return array( 'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', - 'rsrc/css/application/countdown/timer.css' => '96696f21', + 'rsrc/css/application/countdown/timer.css' => '16c52f5c', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => '3311444d', + 'rsrc/css/application/diffusion/diffusion-icons.css' => 'd678600a', 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', 'rsrc/css/application/diffusion/diffusion-source.css' => '68b30fd3', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', @@ -104,8 +104,8 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'd0801452', - 'rsrc/css/core/remarkup.css' => '6aae5360', - 'rsrc/css/core/syntax.css' => '9fd11da8', + 'rsrc/css/core/remarkup.css' => '787105d6', + 'rsrc/css/core/syntax.css' => '5101175d', 'rsrc/css/core/z-index.css' => '5b6fcf3f', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', @@ -113,7 +113,7 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '6449bce8', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', - 'rsrc/css/layout/phabricator-side-menu-view.css' => '3a3d9f41', + 'rsrc/css/layout/phabricator-side-menu-view.css' => 'dd849797', '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', @@ -123,15 +123,15 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '3baef8db', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => 'd909ea3d', + 'rsrc/css/phui/phui-box.css' => '5c8387cf', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '1a1265d4', 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', - 'rsrc/css/phui/phui-document-pro.css' => '73e45fd2', + 'rsrc/css/phui/phui-document-pro.css' => '8419560b', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', - 'rsrc/css/phui/phui-document.css' => '9c71d2bf', - 'rsrc/css/phui/phui-feed-story.css' => 'd8440402', + 'rsrc/css/phui/phui-document.css' => '715aedfb', + 'rsrc/css/phui/phui-feed-story.css' => 'aa49845d', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => '6a51768e', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', @@ -153,7 +153,7 @@ return array( 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', 'rsrc/css/phui/phui-spacing.css' => '042804d6', - 'rsrc/css/phui/phui-status.css' => '37309046', + 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => '6e342216', 'rsrc/css/phui/phui-two-column-view.css' => 'b9538af1', @@ -164,6 +164,7 @@ return array( 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-menu.css' => '9dd65b92', 'rsrc/css/sprite-tokens.css' => '4f399012', + 'rsrc/css/syntax/syntax-default.css' => '9923583c', 'rsrc/externals/d3/d3.min.js' => 'a11a5ff2', 'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7', 'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e', @@ -503,6 +504,7 @@ return array( 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', 'rsrc/js/core/behavior-search-typeahead.js' => '06c32383', + 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-time-typeahead.js' => '522431f7', 'rsrc/js/core/behavior-toggle-class.js' => '92b9ec77', @@ -556,7 +558,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => '3311444d', + 'diffusion-icons-css' => 'd678600a', 'diffusion-readme-css' => '297373eb', 'diffusion-source-css' => '68b30fd3', 'diviner-shared-css' => 'aa3656aa', @@ -684,6 +686,7 @@ return array( 'javelin-behavior-repository-crossreference' => 'e5339c43', 'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-search-reorder-queries' => 'e9581f08', + 'javelin-behavior-select-content' => 'bf5374ef', 'javelin-behavior-select-on-click' => '4e3e79a6', 'javelin-behavior-slowvote-embed' => '887ad43f', 'javelin-behavior-stripe-payment-form' => '3f5d6dbf', @@ -758,7 +761,7 @@ return array( 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => 'd0801452', - 'phabricator-countdown-css' => '96696f21', + 'phabricator-countdown-css' => '16c52f5c', 'phabricator-dashboard-css' => 'bc6f2127', 'phabricator-drag-and-drop-file-upload' => '81f182b5', 'phabricator-draggable-list' => '5a13c79f', @@ -777,10 +780,10 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'e67df814', - 'phabricator-remarkup-css' => '6aae5360', + 'phabricator-remarkup-css' => '787105d6', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', - 'phabricator-side-menu-view-css' => '3a3d9f41', + 'phabricator-side-menu-view-css' => 'dd849797', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => 'e709f6d0', @@ -811,7 +814,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => '3baef8db', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => 'd909ea3d', + 'phui-box-css' => '5c8387cf', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', @@ -821,9 +824,9 @@ return array( 'phui-crumbs-view-css' => '1a1265d4', 'phui-curtain-view-css' => '7148ae25', 'phui-document-summary-view-css' => '9ca48bdf', - 'phui-document-view-css' => '9c71d2bf', - 'phui-document-view-pro-css' => '73e45fd2', - 'phui-feed-story-css' => 'd8440402', + 'phui-document-view-css' => '715aedfb', + 'phui-document-view-pro-css' => '8419560b', + 'phui-feed-story-css' => 'aa49845d', 'phui-font-icon-base-css' => '6449bce8', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', @@ -848,7 +851,7 @@ return array( 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', - 'phui-status-list-view-css' => '37309046', + 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '6e342216', @@ -877,7 +880,8 @@ return array( 'sprite-login-css' => '60e8560e', 'sprite-menu-css' => '9dd65b92', 'sprite-tokens-css' => '4f399012', - 'syntax-highlighting-css' => '9fd11da8', + 'syntax-default-css' => '9923583c', + 'syntax-highlighting-css' => '5101175d', 'tokens-css' => '3d0f239e', 'typeahead-browse-css' => 'd8581d2c', 'unhandled-exception-css' => '4c96257a', @@ -1239,6 +1243,9 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '5101175d' => array( + 'syntax-default-css', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1832,6 +1839,11 @@ return array( 'javelin-util', 'javelin-request', ), + 'bf5374ef' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + ), 'bff6884b' => array( 'javelin-install', 'javelin-dom', @@ -2192,6 +2204,7 @@ return array( 'aphront-list-filter-view-css', 'phabricator-remarkup-css', 'syntax-highlighting-css', + 'syntax-default-css', 'phui-pager-css', 'aphront-tooltip-css', 'phabricator-flag-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index df6c2ed726..fa19f50095 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -97,6 +97,7 @@ return array( 'phabricator-remarkup-css', 'syntax-highlighting-css', + 'syntax-default-css', 'phui-pager-css', 'aphront-tooltip-css', 'phabricator-flag-css', diff --git a/resources/sql/autopatches/20160112.repo.02.uri.index.php b/resources/sql/autopatches/20160112.repo.02.uri.index.php index 7a11be3179..e9cd061361 100644 --- a/resources/sql/autopatches/20160112.repo.02.uri.index.php +++ b/resources/sql/autopatches/20160112.repo.02.uri.index.php @@ -1,7 +1,4 @@ updateURIIndex(); -} +// A later patch ("20160510.repo.01.uriindex.php") performs an identical +// regeneration of the index, so we no longer need to do it here. diff --git a/resources/sql/autopatches/20160503.repo.01.lpath.sql b/resources/sql/autopatches/20160503.repo.01.lpath.sql new file mode 100644 index 0000000000..437dfb1317 --- /dev/null +++ b/resources/sql/autopatches/20160503.repo.01.lpath.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository + ADD localPath VARCHAR(128) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160503.repo.02.lpathkey.sql b/resources/sql/autopatches/20160503.repo.02.lpathkey.sql new file mode 100644 index 0000000000..b630d87c26 --- /dev/null +++ b/resources/sql/autopatches/20160503.repo.02.lpathkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository + ADD UNIQUE KEY `key_local` (localPath); diff --git a/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php b/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php new file mode 100644 index 0000000000..d810b074c9 --- /dev/null +++ b/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php @@ -0,0 +1,57 @@ +establishConnection('w'); + +$default_path = PhabricatorEnv::getEnvConfig('repository.default-local-path'); +$default_path = rtrim($default_path, '/'); + +foreach (new LiskMigrationIterator($table) as $repository) { + $local_path = $repository->getLocalPath(); + if (strlen($local_path)) { + // Repository already has a modern, unique local path. + continue; + } + + $local_path = $repository->getDetail('local-path'); + if (!strlen($local_path)) { + // Repository does not have a local path using the older format. + continue; + } + + $random = Filesystem::readRandomCharacters(8); + + // Try the configured path first, then a default path, then a path with some + // random noise. + $paths = array( + $local_path, + $default_path.'/'.$repository->getID().'/', + $default_path.'/'.$repository->getID().'-'.$random.'/', + ); + + foreach ($paths as $path) { + // Set, then get the path to normalize it. + $repository->setLocalPath($path); + $path = $repository->getLocalPath(); + + try { + queryfx( + $conn_w, + 'UPDATE %T SET localPath = %s WHERE id = %d', + $table->getTableName(), + $path, + $repository->getID()); + + echo tsprintf( + "%s\n", + pht( + 'Assigned repository "%s" to local path "%s".', + $repository->getDisplayName(), + $path)); + + break; + } catch (AphrontDuplicateKeyQueryException $ex) { + // Ignore, try the next one. + } + } +} diff --git a/resources/sql/autopatches/20160503.repo.04.mirrormigrate.php b/resources/sql/autopatches/20160503.repo.04.mirrormigrate.php new file mode 100644 index 0000000000..9613e43542 --- /dev/null +++ b/resources/sql/autopatches/20160503.repo.04.mirrormigrate.php @@ -0,0 +1,38 @@ +establishConnection('w'); + +$mirrors = queryfx_all( + $conn_w, + 'SELECT * FROM %T', + 'repository_mirror'); + +foreach ($mirrors as $mirror) { + $repository_phid = $mirror['repositoryPHID']; + $uri = $mirror['remoteURI']; + + $already_exists = id(new PhabricatorRepositoryURI())->loadOneWhere( + 'repositoryPHID = %s AND uri = %s', + $repository_phid, + $uri); + if ($already_exists) { + // Decline to migrate stuff that looks like it was already migrated. + continue; + } + + $new_uri = PhabricatorRepositoryURI::initializeNewURI() + ->setIOType(PhabricatorRepositoryURI::IO_MIRROR) + ->setRepositoryPHID($repository_phid) + ->setURI($uri) + ->setCredentialPHID($mirror['credentialPHID']) + ->setDateCreated($mirror['dateCreated']) + ->setDateModified($mirror['dateModified']) + ->save(); + + echo tsprintf( + "%s\n", + pht( + 'Migrated mirror "%s".', + $uri)); +} diff --git a/resources/sql/autopatches/20160503.repo.05.urimigrate.php b/resources/sql/autopatches/20160503.repo.05.urimigrate.php new file mode 100644 index 0000000000..d48afd4f66 --- /dev/null +++ b/resources/sql/autopatches/20160503.repo.05.urimigrate.php @@ -0,0 +1,82 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $repository) { + $uris = array(); + + $serve_http = $repository->getDetail('serve-over-http'); + $http_io = PhabricatorRepositoryURI::IO_DEFAULT; + $disable_http = false; + switch ($serve_http) { + case 'readwrite': + break; + case 'readonly': + $http_io = PhabricatorRepositoryURI::IO_READ; + break; + case 'off': + default: + $disable_http = true; + break; + } + + $serve_ssh = $repository->getDetail('serve-over-ssh'); + $ssh_io = PhabricatorRepositoryURI::IO_DEFAULT; + $disable_ssh = false; + switch ($serve_ssh) { + case 'readwrite': + break; + case 'readonly': + $ssh_io = PhabricatorRepositoryURI::IO_READ; + break; + case 'off': + default: + $disable_ssh = true; + break; + } + + $uris = $repository->newBuiltinURIs(); + + foreach ($uris as $uri) { + $builtin_protocol = $uri->getBuiltinProtocol(); + if ($builtin_protocol == PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH) { + $uri->setIsDisabled((int)$disable_ssh); + $uri->setIoType($ssh_io); + } else { + $uri->setIsDisabled((int)$disable_http); + $uri->setIoType($http_io); + } + } + + if (!$repository->isHosted()) { + $remote_uri = $repository->getDetail('remote-uri'); + if (strlen($remote_uri)) { + $uris[] = PhabricatorRepositoryURI::initializeNewURI() + ->setRepositoryPHID($repository->getPHID()) + ->attachRepository($repository) + ->setURI($remote_uri) + ->setCredentialPHID($repository->getCredentialPHID()) + ->setIOType(PhabricatorRepositoryURI::IO_OBSERVE); + } + } + + foreach ($uris as $uri) { + $already_exists = id(new PhabricatorRepositoryURI())->loadOneWhere( + 'repositoryPHID = %s AND uri = %s LIMIT 1', + $repository->getPHID(), + $uri->getURI()); + if ($already_exists) { + continue; + } + + $uri->save(); + + echo tsprintf( + "%s\n", + pht( + 'Migrated URI "%s" for repository "%s".', + $uri->getURI(), + $repository->getDisplayName())); + } +} diff --git a/resources/sql/autopatches/20160510.repo.01.uriindex.php b/resources/sql/autopatches/20160510.repo.01.uriindex.php new file mode 100644 index 0000000000..191985af47 --- /dev/null +++ b/resources/sql/autopatches/20160510.repo.01.uriindex.php @@ -0,0 +1,10 @@ +setViewer(PhabricatorUser::getOmnipotentUser()) + ->needURIs(true) + ->execute(); + +foreach ($repos as $repo) { + $repo->updateURIIndex(); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b0ffd4eef5..e11c20a33c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -226,6 +226,7 @@ phutil_register_library_map(array( 'CelerityHighContrastPostprocessor' => 'applications/celerity/postprocessor/CelerityHighContrastPostprocessor.php', 'CelerityLargeFontPostprocessor' => 'applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php', 'CelerityManagementMapWorkflow' => 'applications/celerity/management/CelerityManagementMapWorkflow.php', + 'CelerityManagementSyntaxWorkflow' => 'applications/celerity/management/CelerityManagementSyntaxWorkflow.php', 'CelerityManagementWorkflow' => 'applications/celerity/management/CelerityManagementWorkflow.php', 'CelerityPhabricatorResourceController' => 'applications/celerity/controller/CelerityPhabricatorResourceController.php', 'CelerityPhabricatorResources' => 'applications/celerity/resources/CelerityPhabricatorResources.php', @@ -361,6 +362,7 @@ phutil_register_library_map(array( 'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php', 'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php', 'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php', + 'DifferentialChangeDetailMailView' => 'applications/differential/mail/DifferentialChangeDetailMailView.php', 'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php', 'DifferentialChangeType' => 'applications/differential/constants/DifferentialChangeType.php', 'DifferentialChangesSinceLastUpdateField' => 'applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php', @@ -369,6 +371,7 @@ phutil_register_library_map(array( 'DifferentialChangesetFileTreeSideNavBuilder' => 'applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php', 'DifferentialChangesetHTMLRenderer' => 'applications/differential/render/DifferentialChangesetHTMLRenderer.php', 'DifferentialChangesetListView' => 'applications/differential/view/DifferentialChangesetListView.php', + 'DifferentialChangesetOneUpMailRenderer' => 'applications/differential/render/DifferentialChangesetOneUpMailRenderer.php', 'DifferentialChangesetOneUpRenderer' => 'applications/differential/render/DifferentialChangesetOneUpRenderer.php', 'DifferentialChangesetOneUpTestRenderer' => 'applications/differential/render/DifferentialChangesetOneUpTestRenderer.php', 'DifferentialChangesetParser' => 'applications/differential/parser/DifferentialChangesetParser.php', @@ -458,6 +461,7 @@ phutil_register_library_map(array( 'DifferentialHunkTestCase' => 'applications/differential/storage/__tests__/DifferentialHunkTestCase.php', 'DifferentialInlineComment' => 'applications/differential/storage/DifferentialInlineComment.php', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/DifferentialInlineCommentEditController.php', + 'DifferentialInlineCommentMailView' => 'applications/differential/mail/DifferentialInlineCommentMailView.php', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/DifferentialInlineCommentPreviewController.php', 'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', @@ -468,6 +472,7 @@ phutil_register_library_map(array( 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', 'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php', + 'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php', 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', 'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php', 'DifferentialNextStepField' => 'applications/differential/customfield/DifferentialNextStepField.php', @@ -570,6 +575,7 @@ phutil_register_library_map(array( 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php', + 'DiffusionCloneURIView' => 'applications/diffusion/view/DiffusionCloneURIView.php', 'DiffusionCommandEngine' => 'applications/diffusion/protocol/DiffusionCommandEngine.php', 'DiffusionCommandEngineTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php', 'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php', @@ -616,6 +622,7 @@ phutil_register_library_map(array( 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', 'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php', 'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php', + 'DiffusionDaemonLockException' => 'applications/diffusion/exception/DiffusionDaemonLockException.php', 'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php', 'DiffusionDefaultPushCapability' => 'applications/diffusion/capability/DiffusionDefaultPushCapability.php', 'DiffusionDefaultViewCapability' => 'applications/diffusion/capability/DiffusionDefaultViewCapability.php', @@ -659,6 +666,7 @@ phutil_register_library_map(array( 'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php', 'DiffusionLintCountQuery' => 'applications/diffusion/query/DiffusionLintCountQuery.php', 'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php', + 'DiffusionLocalRepositoryFilter' => 'applications/diffusion/data/DiffusionLocalRepositoryFilter.php', 'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php', 'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php', 'DiffusionLowLevelCommitQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php', @@ -682,8 +690,6 @@ phutil_register_library_map(array( 'DiffusionMercurialWireProtocolTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialWireProtocolTests.php', 'DiffusionMercurialWireSSHTestCase' => 'applications/diffusion/ssh/__tests__/DiffusionMercurialWireSSHTestCase.php', 'DiffusionMergedCommitsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php', - 'DiffusionMirrorDeleteController' => 'applications/diffusion/controller/DiffusionMirrorDeleteController.php', - 'DiffusionMirrorEditController' => 'applications/diffusion/controller/DiffusionMirrorEditController.php', 'DiffusionPathChange' => 'applications/diffusion/data/DiffusionPathChange.php', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php', 'DiffusionPathCompleteController' => 'applications/diffusion/controller/DiffusionPathCompleteController.php', @@ -751,32 +757,21 @@ phutil_register_library_map(array( 'DiffusionRepositoryClusterEngine' => 'applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php', 'DiffusionRepositoryClusterEngineLogInterface' => 'applications/diffusion/protocol/DiffusionRepositoryClusterEngineLogInterface.php', 'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php', - 'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php', 'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php', 'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php', - 'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php', + 'DiffusionRepositoryDocumentationManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php', 'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php', - 'DiffusionRepositoryEditAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php', - 'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php', - 'DiffusionRepositoryEditBranchesController' => 'applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php', 'DiffusionRepositoryEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositoryEditConduitAPIMethod.php', 'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php', 'DiffusionRepositoryEditDangerousController' => 'applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php', 'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php', - 'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php', 'DiffusionRepositoryEditEngine' => 'applications/diffusion/editor/DiffusionRepositoryEditEngine.php', - 'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php', - 'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php', - 'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php', - 'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php', - 'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php', 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', - 'DiffusionRepositoryEditproController' => 'applications/diffusion/controller/DiffusionRepositoryEditproController.php', 'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php', 'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php', 'DiffusionRepositoryManageController' => 'applications/diffusion/controller/DiffusionRepositoryManageController.php', + 'DiffusionRepositoryManagePanelsController' => 'applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php', 'DiffusionRepositoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryManagementPanel.php', - 'DiffusionRepositoryNewController' => 'applications/diffusion/controller/DiffusionRepositoryNewController.php', 'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php', 'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php', 'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php', @@ -785,10 +780,11 @@ phutil_register_library_map(array( 'DiffusionRepositoryStagingManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php', 'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php', 'DiffusionRepositoryStorageManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php', - 'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php', + 'DiffusionRepositorySubversionManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php', 'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php', 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php', + 'DiffusionRepositoryURICredentialController' => 'applications/diffusion/controller/DiffusionRepositoryURICredentialController.php', 'DiffusionRepositoryURIDisableController' => 'applications/diffusion/controller/DiffusionRepositoryURIDisableController.php', 'DiffusionRepositoryURIEditController' => 'applications/diffusion/controller/DiffusionRepositoryURIEditController.php', 'DiffusionRepositoryURIViewController' => 'applications/diffusion/controller/DiffusionRepositoryURIViewController.php', @@ -2286,6 +2282,7 @@ phutil_register_library_map(array( 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', + 'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php', 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', @@ -2357,6 +2354,7 @@ phutil_register_library_map(array( 'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php', 'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php', 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', + 'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php', 'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php', 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', 'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php', @@ -3188,7 +3186,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php', - 'PhabricatorRepositoryManagementEditWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php', 'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php', 'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php', @@ -3207,8 +3204,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php', 'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php', 'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php', - 'PhabricatorRepositoryMirrorPHIDType' => 'applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php', - 'PhabricatorRepositoryMirrorQuery' => 'applications/repository/query/PhabricatorRepositoryMirrorQuery.php', 'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php', 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', 'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php', @@ -3469,6 +3464,7 @@ phutil_register_library_map(array( 'PhabricatorSupportApplication' => 'applications/support/application/PhabricatorSupportApplication.php', 'PhabricatorSyntaxHighlighter' => 'infrastructure/markup/PhabricatorSyntaxHighlighter.php', 'PhabricatorSyntaxHighlightingConfigOptions' => 'applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php', + 'PhabricatorSyntaxStyle' => 'infrastructure/syntax/PhabricatorSyntaxStyle.php', 'PhabricatorSystemAction' => 'applications/system/action/PhabricatorSystemAction.php', 'PhabricatorSystemActionEngine' => 'applications/system/engine/PhabricatorSystemActionEngine.php', 'PhabricatorSystemActionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php', @@ -4103,7 +4099,6 @@ phutil_register_library_map(array( 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php', 'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php', 'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php', - 'RepositoryCreateConduitAPIMethod' => 'applications/repository/conduit/RepositoryCreateConduitAPIMethod.php', 'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php', 'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php', 'SlowvoteConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteConduitAPIMethod.php', @@ -4413,6 +4408,7 @@ phutil_register_library_map(array( 'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor', 'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor', 'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow', + 'CelerityManagementSyntaxWorkflow' => 'CelerityManagementWorkflow', 'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow', 'CelerityPhabricatorResourceController' => 'CelerityResourceController', 'CelerityPhabricatorResources' => 'CelerityResourcesOnDisk', @@ -4446,7 +4442,7 @@ phutil_register_library_map(array( 'ConduitCallTestCase' => 'PhabricatorTestCase', 'ConduitColumnsParameterType' => 'ConduitParameterType', 'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod', - 'ConduitEpochParameterType' => 'ConduitListParameterType', + 'ConduitEpochParameterType' => 'ConduitParameterType', 'ConduitException' => 'Exception', 'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod', @@ -4470,7 +4466,7 @@ phutil_register_library_map(array( 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitUserListParameterType' => 'ConduitListParameterType', 'ConduitUserParameterType' => 'ConduitParameterType', - 'ConduitWildParameterType' => 'ConduitListParameterType', + 'ConduitWildParameterType' => 'ConduitParameterType', 'ConpherenceColumnViewController' => 'ConpherenceController', 'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod', 'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -4559,6 +4555,7 @@ phutil_register_library_map(array( 'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField', 'DifferentialBlockHeraldAction' => 'HeraldAction', 'DifferentialBranchField' => 'DifferentialCustomField', + 'DifferentialChangeDetailMailView' => 'DifferentialMailView', 'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialChangeType' => 'Phobject', 'DifferentialChangesSinceLastUpdateField' => 'DifferentialCustomField', @@ -4570,6 +4567,7 @@ phutil_register_library_map(array( 'DifferentialChangesetFileTreeSideNavBuilder' => 'Phobject', 'DifferentialChangesetHTMLRenderer' => 'DifferentialChangesetRenderer', 'DifferentialChangesetListView' => 'AphrontView', + 'DifferentialChangesetOneUpMailRenderer' => 'DifferentialChangesetRenderer', 'DifferentialChangesetOneUpRenderer' => 'DifferentialChangesetHTMLRenderer', 'DifferentialChangesetOneUpTestRenderer' => 'DifferentialChangesetTestRenderer', 'DifferentialChangesetParser' => 'Phobject', @@ -4672,6 +4670,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentInterface', ), 'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController', + 'DifferentialInlineCommentMailView' => 'DifferentialMailView', 'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', @@ -4682,6 +4681,7 @@ phutil_register_library_map(array( 'DifferentialLintField' => 'DifferentialHarbormasterField', 'DifferentialLintStatus' => 'Phobject', 'DifferentialLocalCommitsView' => 'AphrontView', + 'DifferentialMailView' => 'Phobject', 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', 'DifferentialModernHunk' => 'DifferentialHunk', 'DifferentialNextStepField' => 'DifferentialCustomField', @@ -4799,6 +4799,7 @@ phutil_register_library_map(array( 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup', + 'DiffusionCloneURIView' => 'AphrontView', 'DiffusionCommandEngine' => 'Phobject', 'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase', 'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField', @@ -4845,6 +4846,7 @@ phutil_register_library_map(array( 'DiffusionController' => 'PhabricatorController', 'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability', + 'DiffusionDaemonLockException' => 'Exception', 'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability', @@ -4891,6 +4893,7 @@ phutil_register_library_map(array( 'DiffusionLintController' => 'DiffusionController', 'DiffusionLintCountQuery' => 'PhabricatorQuery', 'DiffusionLintSaveRunner' => 'Phobject', + 'DiffusionLocalRepositoryFilter' => 'Phobject', 'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelCommitQuery' => 'DiffusionLowLevelQuery', @@ -4914,8 +4917,6 @@ phutil_register_library_map(array( 'DiffusionMercurialWireProtocolTests' => 'PhabricatorTestCase', 'DiffusionMercurialWireSSHTestCase' => 'PhabricatorTestCase', 'DiffusionMergedCommitsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionMirrorDeleteController' => 'DiffusionController', - 'DiffusionMirrorEditController' => 'DiffusionController', 'DiffusionPathChange' => 'Phobject', 'DiffusionPathChangeQuery' => 'Phobject', 'DiffusionPathCompleteController' => 'DiffusionController', @@ -4982,32 +4983,21 @@ phutil_register_library_map(array( 'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionRepositoryClusterEngine' => 'Phobject', 'DiffusionRepositoryController' => 'DiffusionController', - 'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionRepositoryDefaultController' => 'DiffusionController', - 'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditAutomationController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditBranchesController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryDocumentationManagementPanel' => 'DiffusionRepositoryManagementPanel', + 'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', - 'DiffusionRepositoryEditController' => 'DiffusionController', - 'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryEditController' => 'DiffusionRepositoryManageController', + 'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryManageController', + 'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine', - 'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditproController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryListController' => 'DiffusionController', 'DiffusionRepositoryManageController' => 'DiffusionController', + 'DiffusionRepositoryManagePanelsController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryManagementPanel' => 'Phobject', - 'DiffusionRepositoryNewController' => 'DiffusionController', 'DiffusionRepositoryPath' => 'Phobject', 'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryRef' => 'Phobject', @@ -5016,10 +5006,11 @@ phutil_register_library_map(array( 'DiffusionRepositoryStagingManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel', - 'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositorySubversionManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryTag' => 'Phobject', - 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryManageController', + 'DiffusionRepositoryURICredentialController' => 'DiffusionController', 'DiffusionRepositoryURIDisableController' => 'DiffusionController', 'DiffusionRepositoryURIEditController' => 'DiffusionController', 'DiffusionRepositoryURIViewController' => 'DiffusionController', @@ -6793,6 +6784,7 @@ phutil_register_library_map(array( 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', + 'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle', 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDestructionEngineExtension' => 'Phobject', @@ -6870,6 +6862,7 @@ phutil_register_library_map(array( 'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditField' => 'Phobject', + 'PhabricatorEditPage' => 'Phobject', 'PhabricatorEditType' => 'Phobject', 'PhabricatorEditor' => 'Phobject', 'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', @@ -7864,7 +7857,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow', - 'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', @@ -7881,13 +7873,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', - 'PhabricatorRepositoryMirror' => array( - 'PhabricatorRepositoryDAO', - 'PhabricatorPolicyInterface', - ), + 'PhabricatorRepositoryMirror' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine', - 'PhabricatorRepositoryMirrorPHIDType' => 'PhabricatorPHIDType', - 'PhabricatorRepositoryMirrorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryParsedChange' => 'Phobject', 'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryPullEvent' => array( @@ -8184,6 +8171,7 @@ phutil_register_library_map(array( 'PhabricatorSupportApplication' => 'PhabricatorApplication', 'PhabricatorSyntaxHighlighter' => 'Phobject', 'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorSyntaxStyle' => 'Phobject', 'PhabricatorSystemAction' => 'Phobject', 'PhabricatorSystemActionEngine' => 'Phobject', 'PhabricatorSystemActionGarbageCollector' => 'PhabricatorGarbageCollector', @@ -8984,7 +8972,6 @@ phutil_register_library_map(array( 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'ReleephConduitAPIMethod', 'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod', 'RepositoryConduitAPIMethod' => 'ConduitAPIMethod', - 'RepositoryCreateConduitAPIMethod' => 'RepositoryConduitAPIMethod', 'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod', 'ShellLogView' => 'AphrontView', 'SlowvoteConduitAPIMethod' => 'ConduitAPIMethod', diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index 9305d6b48b..a49821e10b 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -484,7 +484,7 @@ final class AphrontRequest extends Phobject { pht( 'This Phabricator install is configured as "%s", but you are '. 'using the domain name "%s" to access a page which is trying to '. - 'set a cookie. Acccess Phabricator on the configured primary '. + 'set a cookie. Access Phabricator on the configured primary '. 'domain or a configured alternate domain. Phabricator will not '. 'set cookies on other domains for security reasons.', $configured_as, diff --git a/src/applications/almanac/util/AlmanacKeys.php b/src/applications/almanac/util/AlmanacKeys.php index 3f482416df..7ed9098e97 100644 --- a/src/applications/almanac/util/AlmanacKeys.php +++ b/src/applications/almanac/util/AlmanacKeys.php @@ -10,6 +10,14 @@ final class AlmanacKeys extends Phobject { } public static function getDeviceID() { + // While running unit tests, ignore any configured device identity. + try { + PhabricatorTestCase::assertExecutingUnitTests(); + return null; + } catch (Exception $ex) { + // Continue normally. + } + $device_id_path = self::getKeyPath('device.id'); if (Filesystem::pathExists($device_id_path)) { diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 0bbb38ff89..a667717dd9 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -624,11 +624,11 @@ abstract class PhabricatorApplication '(?P[0-9]\d*)/)?'. '(?:'. '(?:'. - '(?Pparameters|nodefault|nocreate|nomanage|comment)'. + '(?Pparameters|nodefault|nocreate|nomanage|comment)/'. '|'. - '(?:form/(?P[^/]+))'. + '(?:form/(?P[^/]+)/)?(?:page/(?P[^/]+)/)?'. ')'. - '/)?'; + ')?'; } protected function getQueryRoutePattern($base = null) { diff --git a/src/applications/celerity/management/CelerityManagementSyntaxWorkflow.php b/src/applications/celerity/management/CelerityManagementSyntaxWorkflow.php new file mode 100644 index 0000000000..d9e4ee8eee --- /dev/null +++ b/src/applications/celerity/management/CelerityManagementSyntaxWorkflow.php @@ -0,0 +1,67 @@ +setName('syntax') + ->setExamples('**syntax** [options]') + ->setSynopsis(pht('Rebuild syntax highlighting CSS.')) + ->setArguments( + array()); + } + + public function execute(PhutilArgumentParser $args) { + $styles = PhabricatorSyntaxStyle::getAllStyles(); + + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/webroot/rsrc/css/syntax/'; + + foreach ($styles as $key => $style) { + $content = $this->generateCSS($style); + $path = $root.'/syntax-'.$key.'.css'; + Filesystem::writeFile($path, $content); + + echo tsprintf( + "%s\n", + pht( + 'Rebuilt "%s" syntax CSS.', + basename($path))); + } + + return 0; + } + + private function generateCSS(PhabricatorSyntaxStyle $style) { + $key = $style->getSyntaxStyleKey(); + $provides = "syntax-{$key}-css"; + $generated = 'generated'; + + $header = + "/**\n". + " * @provides {$provides}\n". + " * @{$generated}\n". + " */\n\n"; + + $groups = array(); + $map = $style->getStyleMap(); + ksort($map); + foreach ($map as $key => $value) { + $groups[$value][] = $key; + } + + $rules = array(); + foreach ($groups as $body => $classes) { + $parts = array(); + foreach ($classes as $class) { + $parts[] = ".remarkup-code .{$class}"; + } + $rules[] = implode(",\n", $parts)." {\n {$body}\n}"; + } + $rules = implode("\n\n", $rules); + + return $header.$rules."\n"; + } + +} diff --git a/src/applications/conduit/parametertype/ConduitEpochParameterType.php b/src/applications/conduit/parametertype/ConduitEpochParameterType.php index 9f906c9c31..1594186e5c 100644 --- a/src/applications/conduit/parametertype/ConduitEpochParameterType.php +++ b/src/applications/conduit/parametertype/ConduitEpochParameterType.php @@ -1,7 +1,7 @@ $aphlict_reason, 'notification.client-uri' => $aphlict_reason, 'notification.server-uri' => $aphlict_reason, + + 'metamta.differential.unified-comment-context' => pht( + 'Inline comments are now always rendered with a limited amount '. + 'of context.'), ); return $ancient_config; diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index bd1aa99359..70f9fe77ed 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -287,21 +287,6 @@ final class PhabricatorDifferentialConfigOptions 'unified') ->setDescription( pht("Format for inlined or attached patches: 'git' or 'unified'.")), - $this->newOption( - 'metamta.differential.unified-comment-context', - 'bool', - false) - ->setBoolOptions( - array( - pht('Show context'), - pht('Do not show context'), - )) - ->setSummary(pht('Show diff context around inline comments in email.')) - ->setDescription( - pht( - 'Normally, inline comments in emails are shown with a file and '. - 'line but without any diff context. Enabling this option adds '. - 'diff context and the comment thread.')), ); } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index fabd2511ba..3855edeac9 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1212,9 +1212,7 @@ final class DifferentialTransactionEditor } if ($inlines) { - $body->addTextSection( - pht('INLINE COMMENTS'), - $this->renderInlineCommentsForMail($object, $inlines)); + $this->appendInlineCommentsForMail($object, $inlines, $body); } $changed_uri = $this->getChangedPriorToCommitURI(); @@ -1253,21 +1251,18 @@ final class DifferentialTransactionEditor $config_attach = PhabricatorEnv::getEnvConfig($config_key_attach); if ($config_inline || $config_attach) { - $patch_section = $this->renderPatchForMail($diff); - $lines = count(phutil_split_lines($patch_section->getPlaintext())); + $patch = $this->buildPatchForMail($diff); + $lines = substr_count($patch, "\n"); if ($config_inline && ($lines <= $config_inline)) { - $body->addTextSection( - pht('CHANGE DETAILS'), - $patch_section); + $this->appendChangeDetailsForMail($object, $diff, $patch, $body); } if ($config_attach) { $name = pht('D%s.%s.patch', $object->getID(), $diff->getID()); $mime_type = 'text/x-patch; charset=utf-8'; $body->addAttachment( - new PhabricatorMetaMTAAttachment( - $patch_section->getPlaintext(), $name, $mime_type)); + new PhabricatorMetaMTAAttachment($patch, $name, $mime_type)); } } } @@ -1374,138 +1369,64 @@ final class DifferentialTransactionEditor return $result; } - protected function indentForMail(array $lines) { - $indented = array(); - foreach ($lines as $line) { - $indented[] = '> '.$line; - } - return $indented; - } - - protected function nestCommentHistory( - DifferentialTransactionComment $comment, array $comments_by_line_number, - array $users_by_phid) { - - $nested = array(); - $previous_comments = $comments_by_line_number[$comment->getChangesetID()] - [$comment->getLineNumber()]; - foreach ($previous_comments as $previous_comment) { - if ($previous_comment->getID() >= $comment->getID()) { - break; - } - $nested = $this->indentForMail( - array_merge( - $nested, - explode("\n", $previous_comment->getContent()))); - $user = idx($users_by_phid, $previous_comment->getAuthorPHID(), null); - if ($user) { - array_unshift($nested, pht('%s wrote:', $user->getUserName())); - } - } - - $nested = array_merge($nested, explode("\n", $comment->getContent())); - return implode("\n", $nested); - } - - private function renderInlineCommentsForMail( + private function appendInlineCommentsForMail( PhabricatorLiskDAO $object, - array $inlines) { + array $inlines, + PhabricatorMetaMTAMailBody $body) { - $context_key = 'metamta.differential.unified-comment-context'; - $show_context = PhabricatorEnv::getEnvConfig($context_key); - - $changeset_ids = array(); - $line_numbers_by_changeset = array(); - foreach ($inlines as $inline) { - $id = $inline->getComment()->getChangesetID(); - $changeset_ids[$id] = $id; - $line_numbers_by_changeset[$id][] = - $inline->getComment()->getLineNumber(); - } - - $changesets = id(new DifferentialChangesetQuery()) + $section = id(new DifferentialInlineCommentMailView()) ->setViewer($this->getActor()) - ->withIDs($changeset_ids) - ->needHunks(true) - ->execute(); + ->setInlines($inlines) + ->buildMailSection(); - $inline_groups = DifferentialTransactionComment::sortAndGroupInlines( - $inlines, - $changesets); + $header = pht('INLINE COMMENTS'); - if ($show_context) { - $hunk_parser = new DifferentialHunkParser(); - $table = new DifferentialTransactionComment(); - $conn_r = $table->establishConnection('r'); - $queries = array(); - foreach ($line_numbers_by_changeset as $id => $line_numbers) { - $queries[] = qsprintf( - $conn_r, - '(changesetID = %d AND lineNumber IN (%Ld))', - $id, $line_numbers); - } - $all_comments = id(new DifferentialTransactionComment())->loadAllWhere( - 'transactionPHID IS NOT NULL AND (%Q)', implode(' OR ', $queries)); - $comments_by_line_number = array(); - foreach ($all_comments as $comment) { - $comments_by_line_number - [$comment->getChangesetID()] - [$comment->getLineNumber()] - [$comment->getID()] = $comment; - } - $author_phids = mpull($all_comments, 'getAuthorPHID'); - $authors = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($author_phids) - ->execute(); - $authors_by_phid = mpull($authors, null, 'getPHID'); - } + $section_text = "\n".$section->getPlaintext(); - $section = new PhabricatorMetaMTAMailSection(); - foreach ($inline_groups as $changeset_id => $group) { - $changeset = idx($changesets, $changeset_id); - if (!$changeset) { - continue; - } + $style = array( + 'margin: 6px 0 12px 0;', + ); - foreach ($group as $inline) { - $comment = $inline->getComment(); - $file = $changeset->getFilename(); - $start = $comment->getLineNumber(); - $len = $comment->getLineLength(); - if ($len) { - $range = $start.'-'.($start + $len); - } else { - $range = $start; - } + $section_html = phutil_tag( + 'div', + array( + 'style' => implode(' ', $style), + ), + $section->getHTML()); - $inline_content = $comment->getContent(); + $body->addPlaintextSection($header, $section_text, false); + $body->addHTMLSection($header, $section_html); + } - if (!$show_context) { - $section->addFragment("{$file}:{$range} {$inline_content}"); - } else { - $patch = $hunk_parser->makeContextDiff( - $changeset->getHunks(), - $comment->getIsNewFile(), - $comment->getLineNumber(), - $comment->getLineLength(), - 1); - $nested_comments = $this->nestCommentHistory( - $inline->getComment(), $comments_by_line_number, $authors_by_phid); + private function appendChangeDetailsForMail( + PhabricatorLiskDAO $object, + DifferentialDiff $diff, + $patch, + PhabricatorMetaMTAMailBody $body) { - $section - ->addFragment('================') - ->addFragment(pht('Comment at: %s:%s', $file, $range)) - ->addPlaintextFragment($patch) - ->addHTMLFragment($this->renderPatchHTMLForMail($patch)) - ->addFragment('----------------') - ->addFragment($nested_comments) - ->addFragment(null); - } - } - } + $section = id(new DifferentialChangeDetailMailView()) + ->setViewer($this->getActor()) + ->setDiff($diff) + ->setPatch($patch) + ->buildMailSection(); - return $section; + $header = pht('CHANGE DETAILS'); + + $section_text = "\n".$section->getPlaintext(); + + $style = array( + 'margin: 6px 0 12px 0;', + ); + + $section_html = phutil_tag( + 'div', + array( + 'style' => implode(' ', $style), + ), + $section->getHTML()); + + $body->addPlaintextSection($header, $section_text, false); + $body->addHTMLSection($header, $section_html); } private function loadDiff($phid, $need_changesets = false) { @@ -1766,20 +1687,14 @@ final class DifferentialTransactionEditor array('style' => 'font-family: monospace;'), $patch); } - private function renderPatchForMail(DifferentialDiff $diff) { + private function buildPatchForMail(DifferentialDiff $diff) { $format = PhabricatorEnv::getEnvConfig('metamta.differential.patch-format'); - $patch = id(new DifferentialRawDiffRenderer()) + return id(new DifferentialRawDiffRenderer()) ->setViewer($this->getActor()) ->setFormat($format) ->setChangesets($diff->getChangesets()) ->buildPatch(); - - $section = new PhabricatorMetaMTAMailSection(); - $section->addHTMLFragment($this->renderPatchHTMLForMail($patch)); - $section->addPlaintextFragment($patch); - - return $section; } protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/differential/mail/DifferentialChangeDetailMailView.php b/src/applications/differential/mail/DifferentialChangeDetailMailView.php new file mode 100644 index 0000000000..d0f0be71b1 --- /dev/null +++ b/src/applications/differential/mail/DifferentialChangeDetailMailView.php @@ -0,0 +1,77 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setDiff(DifferentialDiff $diff) { + $this->diff = $diff; + return $this; + } + + public function getDiff() { + return $this->diff; + } + + public function setPatch($patch) { + $this->patch = $patch; + return $this; + } + + public function getPatch() { + return $this->patch; + } + + public function buildMailSection() { + $viewer = $this->getViewer(); + + $diff = $this->getDiff(); + + $engine = new PhabricatorMarkupEngine(); + + $out = array(); + foreach ($diff->getChangesets() as $changeset) { + $parser = id(new DifferentialChangesetParser()) + ->setUser($viewer) + ->setChangeset($changeset) + ->setLinesOfContext(2) + ->setMarkupEngine($engine); + + $parser->setRenderer(new DifferentialChangesetOneUpMailRenderer()); + $block = $parser->render(); + + $filename = $changeset->getFilename(); + $filename = $this->renderHeaderBold($filename); + $header = $this->renderHeaderBlock($filename); + + $out[] = $this->renderContentBox( + array( + $header, + $this->renderCodeBlock($block), + )); + } + + $out = phutil_implode_html(phutil_tag('br'), $out); + + $patch_html = $out; + + $patch_text = $this->getPatch(); + + return id(new PhabricatorMetaMTAMailSection()) + ->addPlaintextFragment($patch_text) + ->addHTMLFragment($patch_html); + } + +} diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php new file mode 100644 index 0000000000..57958202b1 --- /dev/null +++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php @@ -0,0 +1,477 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setInlines($inlines) { + $this->inlines = $inlines; + return $this; + } + + public function getInlines() { + return $this->inlines; + } + + public function buildMailSection() { + $inlines = $this->getInlines(); + + $comments = mpull($inlines, 'getComment'); + $comments = mpull($comments, null, 'getPHID'); + $parents = $this->loadParents($comments); + $all_comments = $comments + $parents; + + $this->changesets = $this->loadChangesets($all_comments); + $this->authors = $this->loadAuthors($all_comments); + $groups = $this->groupInlines($inlines); + + $hunk_parser = new DifferentialHunkParser(); + + $spacer_text = null; + $spacer_html = phutil_tag('br'); + + $section = new PhabricatorMetaMTAMailSection(); + + $last_group_key = last_key($groups); + foreach ($groups as $changeset_id => $group) { + $changeset = $this->getChangeset($changeset_id); + if (!$changeset) { + continue; + } + + $is_last_group = ($changeset_id == $last_group_key); + + $last_inline_key = last_key($group); + foreach ($group as $inline_key => $inline) { + $comment = $inline->getComment(); + $parent_phid = $comment->getReplyToCommentPHID(); + + $is_last_inline = ($inline_key == $last_inline_key); + + $context_text = null; + $context_html = null; + + if ($parent_phid) { + $parent = idx($parents, $parent_phid); + if ($parent) { + $context_text = $this->renderInline($parent, false, true); + $context_html = $this->renderInline($parent, true, true); + } + } else { + $patch_text = $this->getPatch($hunk_parser, $comment, false); + $context_text = $this->renderPatch($comment, $patch_text, false); + + $patch_html = $this->getPatch($hunk_parser, $comment, true); + $context_html = $this->renderPatch($comment, $patch_html, true); + } + + $render_text = $this->renderInline($comment, false, false); + $render_html = $this->renderInline($comment, true, false); + + $section->addPlaintextFragment($context_text); + $section->addPlaintextFragment($spacer_text); + $section->addPlaintextFragment($render_text); + + $html_fragment = $this->renderContentBox( + array( + $context_html, + $render_html, + )); + + $section->addHTMLFragment($html_fragment); + + if (!$is_last_group || !$is_last_inline) { + $section->addPlaintextFragment($spacer_text); + $section->addHTMLFragment($spacer_html); + } + } + } + + return $section; + } + + private function loadChangesets(array $comments) { + if (!$comments) { + return array(); + } + + $ids = array(); + foreach ($comments as $comment) { + $ids[] = $comment->getChangesetID(); + } + + $changesets = id(new DifferentialChangesetQuery()) + ->setViewer($this->getViewer()) + ->withIDs($ids) + ->needHunks(true) + ->execute(); + + return mpull($changesets, null, 'getID'); + } + + private function loadParents(array $comments) { + $viewer = $this->getViewer(); + + $phids = array(); + foreach ($comments as $comment) { + $parent_phid = $comment->getReplyToCommentPHID(); + if (!$parent_phid) { + continue; + } + $phids[] = $parent_phid; + } + + if (!$phids) { + return array(); + } + + $parents = id(new DifferentialDiffInlineCommentQuery()) + ->setViewer($viewer) + ->withPHIDs($phids) + ->execute(); + + return mpull($parents, null, 'getPHID'); + } + + private function loadAuthors(array $comments) { + $viewer = $this->getViewer(); + + $phids = array(); + foreach ($comments as $comment) { + $author_phid = $comment->getAuthorPHID(); + if (!$author_phid) { + continue; + } + $phids[] = $author_phid; + } + + if (!$phids) { + return array(); + } + + return $viewer->loadHandles($phids); + } + + private function groupInlines(array $inlines) { + return DifferentialTransactionComment::sortAndGroupInlines( + $inlines, + $this->changesets); + } + + private function renderInline( + DifferentialTransactionComment $comment, + $is_html, + $is_quote) { + + $changeset = $this->getChangeset($comment->getChangesetID()); + if (!$changeset) { + return null; + } + + $content = $comment->getContent(); + $content = $this->renderRemarkupContent($content, $is_html); + + if ($is_quote) { + $header = $this->renderHeader($comment, $is_html, true); + } else { + $header = null; + } + + if ($is_html) { + $style = array( + 'margin: 8px 0;', + 'padding: 0 12px;', + ); + + if ($is_quote) { + $style[] = 'color: #74777D;'; + } + + $content = phutil_tag( + 'div', + array( + 'style' => implode(' ', $style), + ), + $content); + } + + $parts = array( + $header, + "\n", + $content, + ); + + if (!$is_html) { + $parts = implode('', $parts); + $parts = trim($parts); + } + + if ($is_quote) { + if ($is_html) { + $parts = $this->quoteHTML($parts); + } else { + $parts = $this->quoteText($parts); + } + } + + return $parts; + } + + private function renderRemarkupContent($content, $is_html) { + $viewer = $this->getViewer(); + $production_uri = PhabricatorEnv::getProductionURI('/'); + + if ($is_html) { + $mode = PhutilRemarkupEngine::MODE_HTML_MAIL; + } else { + $mode = PhutilRemarkupEngine::MODE_TEXT; + } + + $attributes = array( + 'style' => 'padding: 0; margin: 8px;', + ); + + $engine = PhabricatorMarkupEngine::newMarkupEngine(array()) + ->setConfig('viewer', $viewer) + ->setConfig('uri.base', $production_uri) + ->setConfig('default.p.attributes', $attributes) + ->setMode($mode); + + try { + return $engine->markupText($content); + } catch (Exception $ex) { + return $content; + } + } + + private function getChangeset($id) { + return idx($this->changesets, $id); + } + + private function getAuthor($phid) { + if (isset($this->authors[$phid])) { + return $this->authors[$phid]; + } + return null; + } + + private function quoteText($block) { + $block = phutil_split_lines($block); + foreach ($block as $key => $line) { + $block[$key] = '> '.$line; + } + + return implode('', $block); + } + + private function quoteHTML($block) { + $styles = array( + 'padding: 0;', + 'background: #F7F7F7;', + 'border-color: #e3e4e8;', + 'border-style: solid;', + 'border-width: 0 0 1px 0;', + 'margin: 0;', + ); + + $styles = implode(' ', $styles); + + return phutil_tag( + 'div', + array( + 'style' => $styles, + ), + $block); + } + + private function getPatch( + DifferentialHunkParser $parser, + DifferentialTransactionComment $comment, + $is_html) { + + $changeset = $this->getChangeset($comment->getChangesetID()); + $is_new = $comment->getIsNewFile(); + $start = $comment->getLineNumber(); + $length = $comment->getLineLength(); + + // By default, show one line of context around the target inline. + $context = 1; + + // If the inline is at least 3 lines long, don't show any extra context. + if ($length >= 2) { + $context = 0; + } + + // If the inline is more than 7 lines long, only show the first 7 lines. + if ($length >= 6) { + $length = 6; + } + + if (!$is_html) { + $hunks = $changeset->getHunks(); + $patch = $parser->makeContextDiff( + $hunks, + $is_new, + $start, + $length, + $context); + $patch = phutil_split_lines($patch); + + // Remove the "@@ -x,y +u,v @@" line. + array_shift($patch); + + return implode('', $patch); + } + + $viewer = $this->getViewer(); + $engine = new PhabricatorMarkupEngine(); + + if ($is_new) { + $offset_mode = 'new'; + } else { + $offset_mode = 'old'; + } + + $parser = id(new DifferentialChangesetParser()) + ->setUser($viewer) + ->setChangeset($changeset) + ->setOffsetMode($offset_mode) + ->setMarkupEngine($engine); + + $parser->setRenderer(new DifferentialChangesetOneUpMailRenderer()); + + return $parser->render( + $start - $context, + $length + 1 + (2 * $context), + array()); + } + + private function renderPatch( + DifferentialTransactionComment $comment, + $patch, + $is_html) { + + if ($is_html) { + $patch = $this->renderCodeBlock($patch); + } + + $header = $this->renderHeader($comment, $is_html, false); + + $patch = array( + $header, + "\n", + $patch, + ); + + if (!$is_html) { + $patch = implode('', $patch); + $patch = $this->quoteText($patch); + } else { + $patch = $this->quoteHTML($patch); + } + + return $patch; + } + + private function renderHeader( + DifferentialTransactionComment $comment, + $is_html, + $with_author) { + + $changeset = $this->getChangeset($comment->getChangesetID()); + $path = $changeset->getFilename(); + + // Only show the filename. + $path = basename($path); + + $start = $comment->getLineNumber(); + $length = $comment->getLineLength(); + if ($length) { + $range = pht('%s-%s', $start, $start + $length); + } else { + $range = $start; + } + + $header = "{$path}:{$range}"; + if ($is_html) { + $header = $this->renderHeaderBold($header); + } + + if ($with_author) { + $author = $this->getAuthor($comment->getAuthorPHID()); + } else { + $author = null; + } + + if ($author) { + $byline = $author->getName(); + + if ($is_html) { + $byline = $this->renderHeaderBold($byline); + } + + $header = pht('%s wrote in %s', $byline, $header); + } + + if ($is_html) { + $link_href = $this->getInlineURI($comment); + if ($link_href) { + $link_style = array( + 'float: right;', + 'text-decoration: none;', + ); + + $link = phutil_tag( + 'a', + array( + 'style' => implode(' ', $link_style), + 'href' => $link_href, + ), + pht('View Inline')); + } else { + $link = null; + } + + $header = $this->renderHeaderBlock(array($link, $header)); + } + + return $header; + } + + private function getInlineURI(DifferentialTransactionComment $comment) { + $changeset = $this->getChangeset($comment->getChangesetID()); + if (!$changeset) { + return null; + } + + $diff = $changeset->getDiff(); + if (!$diff) { + return null; + } + + $revision = $diff->getRevision(); + if (!$revision) { + return null; + } + + $link_href = '/'.$revision->getMonogram().'#inline-'.$comment->getID(); + $link_href = PhabricatorEnv::getProductionURI($link_href); + + return $link_href; + } + + +} diff --git a/src/applications/differential/mail/DifferentialMailView.php b/src/applications/differential/mail/DifferentialMailView.php new file mode 100644 index 0000000000..81b7836765 --- /dev/null +++ b/src/applications/differential/mail/DifferentialMailView.php @@ -0,0 +1,62 @@ + implode(' ', $style), + ), + $block); + } + + protected function renderHeaderBlock($block) { + $style = array( + 'color: #74777d;', + 'background: #eff2f4;', + 'padding: 6px 8px;', + 'overflow: hidden;', + ); + + return phutil_tag( + 'div', + array( + 'style' => implode(' ', $style), + ), + $block); + } + + protected function renderHeaderBold($content) { + return phutil_tag( + 'span', + array( + 'style' => 'color: #4b4d51; font-weight: bold;', + ), + $content); + } + + protected function renderContentBox($content) { + $style = array( + 'border: 1px solid #C7CCD9;', + 'border-radius: 3px;', + ); + + return phutil_tag( + 'div', + array( + 'style' => implode(' ', $style), + ), + $content); + } + +} diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index f6c7309ec7..9fc0adf537 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -50,10 +50,12 @@ final class DifferentialChangesetParser extends Phobject { private $showEditAndReplyLinks = true; private $canMarkDone; private $objectOwnerPHID; + private $offsetMode; private $rangeStart; private $rangeEnd; private $mask; + private $linesOfContext = 8; private $highlightEngine; @@ -138,6 +140,15 @@ final class DifferentialChangesetParser extends Phobject { return $this->objectOwnerPHID; } + public function setOffsetMode($offset_mode) { + $this->offsetMode = $offset_mode; + return $this; + } + + public function getOffsetMode() { + return $this->offsetMode; + } + public static function getDefaultRendererForViewer(PhabricatorUser $viewer) { $prefs = $viewer->loadPreferences(); $pref_unified = PhabricatorUserPreferences::PREFERENCE_DIFF_UNIFIED; @@ -185,8 +196,6 @@ final class DifferentialChangesetParser extends Phobject { const ATTR_WHITELINES = 'attr:white'; const ATTR_MOVEAWAY = 'attr:moveaway'; - const LINES_CONTEXT = 8; - const WHITESPACE_SHOW_ALL = 'show-all'; const WHITESPACE_IGNORE_TRAILING = 'ignore-trailing'; const WHITESPACE_IGNORE_MOST = 'ignore-most'; @@ -217,6 +226,16 @@ final class DifferentialChangesetParser extends Phobject { return $this; } + public function setLinesOfContext($lines_of_context) { + $this->linesOfContext = $lines_of_context; + return $this; + } + + public function getLinesOfContext() { + return $this->linesOfContext; + } + + /** * Configure which Changeset comments added to the right side of the visible * diff will be attached to. The ID must be the ID of a real Differential @@ -714,8 +733,10 @@ final class DifferentialChangesetParser extends Phobject { self::ATTR_MOVEAWAY => $moveaway, )); + $lines_context = $this->getLinesOfContext(); + $hunk_parser->generateIntraLineDiffs(); - $hunk_parser->generateVisibileLinesMask(); + $hunk_parser->generateVisibileLinesMask($lines_context); $this->setOldLines($hunk_parser->getOldLines()); $this->setNewLines($hunk_parser->getNewLines()); @@ -829,6 +850,22 @@ final class DifferentialChangesetParser extends Phobject { } $this->tryCacheStuff(); + + // If we're rendering in an offset mode, treat the range numbers as line + // numbers instead of rendering offsets. + $offset_mode = $this->getOffsetMode(); + if ($offset_mode) { + if ($offset_mode == 'new') { + $offset_map = $this->new; + } else { + $offset_map = $this->old; + } + + $range_end = $this->getOffset($offset_map, $range_start + $range_len); + $range_start = $this->getOffset($offset_map, $range_start); + $range_len = ($range_end - $range_start); + } + $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset); $rows = max( @@ -933,6 +970,7 @@ final class DifferentialChangesetParser extends Phobject { $old_mask = array(); $new_mask = array(); $feedback_mask = array(); + $lines_context = $this->getLinesOfContext(); if ($this->comments) { // If there are any comments which appear in sections of the file which @@ -975,10 +1013,10 @@ final class DifferentialChangesetParser extends Phobject { } - $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0); + $start = max($comment->getLineNumber() - $lines_context, 0); $end = $comment->getLineNumber() + $comment->getLineLength() + - self::LINES_CONTEXT; + $lines_context; for ($ii = $start; $ii <= $end; $ii++) { if ($new_side) { $new_mask[$ii] = true; @@ -1163,6 +1201,8 @@ final class DifferentialChangesetParser extends Phobject { $range_start, $range_len) { + $lines_context = $this->getLinesOfContext(); + // Calculate gaps and mask first $gaps = array(); $gap_start = 0; @@ -1173,7 +1213,7 @@ final class DifferentialChangesetParser extends Phobject { if (isset($base_mask[$ii])) { if ($in_gap) { $gap_length = $ii - $gap_start; - if ($gap_length <= self::LINES_CONTEXT) { + if ($gap_length <= $lines_context) { for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) { $base_mask[$jj] = true; } @@ -1632,5 +1672,21 @@ final class DifferentialChangesetParser extends Phobject { return $results; } + private function getOffset(array $map, $line) { + if (!$map) { + return null; + } + + $line = (int)$line; + foreach ($map as $key => $spec) { + if ($spec && isset($spec['line'])) { + if ((int)$spec['line'] >= $line) { + return $key; + } + } + } + + return $key; + } } diff --git a/src/applications/differential/parser/DifferentialHunkParser.php b/src/applications/differential/parser/DifferentialHunkParser.php index 22b82e002e..5bd98e9012 100644 --- a/src/applications/differential/parser/DifferentialHunkParser.php +++ b/src/applications/differential/parser/DifferentialHunkParser.php @@ -353,8 +353,7 @@ final class DifferentialHunkParser extends Phobject { return $this; } - public function generateVisibileLinesMask() { - $lines_context = DifferentialChangesetParser::LINES_CONTEXT; + public function generateVisibileLinesMask($lines_context) { $old = $this->getOldLines(); $new = $this->getNewLines(); $max_length = max(count($old), count($new)); diff --git a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php new file mode 100644 index 0000000000..e720422ecd --- /dev/null +++ b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php @@ -0,0 +1,160 @@ +buildPrimitives($range_start, $range_len); + return $this->renderPrimitives($primitives, $rows); + } + + protected function renderPrimitives(array $primitives, $rows) { + $out = array(); + + $context_style = array( + 'background: #F7F7F7;', + 'color: #74777D;', + 'border-style: dashed;', + 'border-color: #C7CCD9;', + 'border-width: 1px 0;', + ); + + $context_style = implode(' ', $context_style); + + foreach ($primitives as $k => $p) { + $type = $p['type']; + switch ($type) { + case 'old': + case 'new': + case 'old-file': + case 'new-file': + $is_old = ($type == 'old' || $type == 'old-file'); + + if ($is_old) { + if ($p['htype']) { + $style = 'background: #ffd0d0;'; + } else { + $style = null; + } + } else { + if ($p['htype']) { + $style = 'background: #d0ffd0;'; + } else { + $style = null; + } + } + + $out[] = array( + 'style' => $style, + 'render' => $p['render'], + 'text' => (string)$p['render'], + ); + break; + case 'context': + // NOTE: These are being included with no text so they get stripped + // in the header and footer. + $out[] = array( + 'style' => $context_style, + 'render' => '...', + 'text' => '', + ); + break; + default: + break; + } + } + + // Remove all leading and trailing empty lines, since these just look kind + // of weird in mail. + foreach ($out as $key => $line) { + if (!strlen(trim($line['text']))) { + unset($out[$key]); + } else { + break; + } + } + + $keys = array_reverse(array_keys($out)); + foreach ($keys as $key) { + $line = $out[$key]; + if (!strlen(trim($line['text']))) { + unset($out[$key]); + } else { + break; + } + } + + // If the user has commented on an empty line in the middle of a bunch of + // other empty lines, emit an explicit marker instead of just rendering + // nothing. + if (!$out) { + $out[] = array( + 'style' => 'color: #888888;', + 'render' => pht('(Empty.)'), + ); + } + + $render = array(); + foreach ($out as $line) { + $style = $line['style']; + $style = "padding: 0 8px; margin: 0 4px; {$style}"; + + $render[] = phutil_tag( + 'div', + array( + 'style' => $style, + ), + $line['render']); + } + + $style_map = id(new PhabricatorDefaultSyntaxStyle()) + ->getRemarkupStyleMap(); + + $styled_body = id(new PhutilPygmentizeParser()) + ->setMap($style_map) + ->parse((string)hsprintf('%s', $render)); + + return phutil_safe_html($styled_body); + } + +} diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index ac4e482278..b94e246d73 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -58,15 +58,15 @@ final class DifferentialReviewersView extends AphrontView { } else { $item->setIcon( PHUIStatusItemView::ICON_ACCEPT, - 'dark', + 'bluegrey', pht('Accepted Prior Diff')); } break; case DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER: $item->setIcon( - PHUIStatusItemView::ICON_ACCEPT, - 'dark', + 'fa-check-circle-o', + 'bluegrey', pht('Accepted Prior Diff')); break; @@ -78,28 +78,29 @@ final class DifferentialReviewersView extends AphrontView { pht('Requested Changes')); } else { $item->setIcon( - PHUIStatusItemView::ICON_REJECT, - 'dark', + 'fa-times-circle-o', + 'bluegrey', pht('Requested Changes to Prior Diff')); } break; case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: $item->setIcon( - PHUIStatusItemView::ICON_REJECT, - 'dark', + 'fa-times-circle-o', + 'bluegrey', pht('Rejected Prior Diff')); break; case DifferentialReviewerStatus::STATUS_COMMENTED: if ($is_current) { $item->setIcon( - PHUIStatusItemView::ICON_INFO, + 'fa-question-circle', 'blue', pht('Commented')); } else { $item->setIcon( - 'info-dark', + 'fa-question-circle-o', + 'bluegrey', pht('Commented Previously')); } break; diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index ec27124d05..7eff7d7bc8 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -57,11 +57,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '/diffusion/' => array( $this->getQueryRoutePattern() => 'DiffusionRepositoryListController', - $this->getEditRoutePattern('editpro/') => - 'DiffusionRepositoryEditproController', - 'new/' => 'DiffusionRepositoryNewController', - '(?Pcreate)/' => 'DiffusionRepositoryCreateController', - '(?Pimport)/' => 'DiffusionRepositoryCreateController', + $this->getEditRoutePattern('edit/') => + 'DiffusionRepositoryEditController', 'pushlog/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', @@ -90,40 +87,24 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'commit/(?P[a-z0-9]+)/edit/' => 'DiffusionCommitEditController', 'manage/(?:(?P[^/]+)/)?' - => 'DiffusionRepositoryManageController', + => 'DiffusionRepositoryManagePanelsController', 'uri/' => array( 'view/(?P[0-9]\d*)/' => 'DiffusionRepositoryURIViewController', 'disable/(?P[0-9]\d*)/' => 'DiffusionRepositoryURIDisableController', $this->getEditRoutePattern('edit/') => 'DiffusionRepositoryURIEditController', + 'credential/(?P[0-9]\d*)/(?Pedit|remove)/' + => 'DiffusionRepositoryURICredentialController', ), 'edit/' => array( - '' => 'DiffusionRepositoryEditMainController', - 'basic/' => 'DiffusionRepositoryEditBasicController', - 'encoding/' => 'DiffusionRepositoryEditEncodingController', 'activate/' => 'DiffusionRepositoryEditActivateController', 'dangerous/' => 'DiffusionRepositoryEditDangerousController', - 'branches/' => 'DiffusionRepositoryEditBranchesController', - 'subversion/' => 'DiffusionRepositoryEditSubversionController', - 'actions/' => 'DiffusionRepositoryEditActionsController', - '(?Premote)/' => 'DiffusionRepositoryCreateController', - '(?Ppolicy)/' => 'DiffusionRepositoryCreateController', - 'storage/' => 'DiffusionRepositoryEditStorageController', 'delete/' => 'DiffusionRepositoryEditDeleteController', - 'hosting/' => 'DiffusionRepositoryEditHostingController', - '(?Pserve)/' => 'DiffusionRepositoryEditHostingController', 'update/' => 'DiffusionRepositoryEditUpdateController', - 'symbol/' => 'DiffusionRepositorySymbolsController', - 'staging/' => 'DiffusionRepositoryEditStagingController', - 'automation/' => 'DiffusionRepositoryEditAutomationController', 'testautomation/' => 'DiffusionRepositoryTestAutomationController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', - 'mirror/' => array( - 'edit/(?:(?P\d+)/)?' => 'DiffusionMirrorEditController', - 'delete/(?P\d+)/' => 'DiffusionMirrorDeleteController', - ), ), // NOTE: This must come after the rule above; it just gives us a diff --git a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php deleted file mode 100644 index a049146cb5..0000000000 --- a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php +++ /dev/null @@ -1,45 +0,0 @@ -loadDiffusionContext(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $mirror = id(new PhabricatorRepositoryMirrorQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$mirror) { - return new Aphront404Response(); - } - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/#mirrors'); - - if ($request->isFormPost()) { - $mirror->delete(); - return id(new AphrontReloadResponse())->setURI($edit_uri); - } - - 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); - } - - -} diff --git a/src/applications/diffusion/controller/DiffusionMirrorEditController.php b/src/applications/diffusion/controller/DiffusionMirrorEditController.php deleted file mode 100644 index d438a9e6d2..0000000000 --- a/src/applications/diffusion/controller/DiffusionMirrorEditController.php +++ /dev/null @@ -1,130 +0,0 @@ -loadDiffusionContext(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - PhabricatorPolicyFilter::requireCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - if ($request->getURIData('id')) { - $mirror = id(new PhabricatorRepositoryMirrorQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$mirror) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $mirror = PhabricatorRepositoryMirror::initializeNewMirror($viewer) - ->setRepositoryPHID($repository->getPHID()) - ->attachRepository($repository); - $is_new = true; - } - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/#mirrors'); - - $v_remote = $mirror->getRemoteURI(); - $e_remote = true; - - $v_credentials = $mirror->getCredentialPHID(); - $e_credentials = null; - - $credentials = id(new PassphraseCredentialQuery()) - ->setViewer($viewer) - ->withIsDestroyed(false) - ->execute(); - - $errors = array(); - if ($request->isFormPost()) { - $v_remote = $request->getStr('remoteURI'); - if (strlen($v_remote)) { - try { - PhabricatorRepository::assertValidRemoteURI($v_remote); - $e_remote = null; - } catch (Exception $ex) { - $e_remote = pht('Invalid'); - $errors[] = $ex->getMessage(); - } - } else { - $e_remote = pht('Required'); - $errors[] = pht('You must provide a remote URI.'); - } - - $v_credentials = $request->getStr('credential'); - if ($v_credentials) { - $phids = mpull($credentials, null, 'getPHID'); - if (empty($phids[$v_credentials])) { - $e_credentials = pht('Invalid'); - $errors[] = pht( - 'You do not have permission to use those credentials.'); - } - } - - if (!$errors) { - $mirror - ->setRemoteURI($v_remote) - ->setCredentialPHID($v_credentials) - ->save(); - return id(new AphrontReloadResponse())->setURI($edit_uri); - } - } - - $form_errors = null; - if ($errors) { - $form_errors = id(new PHUIInfoView()) - ->setErrors($errors); - } - - if ($is_new) { - $title = pht('Create Mirror'); - $submit = pht('Create Mirror'); - } else { - $title = pht('Edit Mirror'); - $submit = pht('Save Changes'); - } - - $form = id(new PHUIFormLayoutView()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Remote URI')) - ->setName('remoteURI') - ->setValue($v_remote) - ->setError($e_remote)) - ->appendChild( - id(new PassphraseCredentialControl()) - ->setLabel(pht('Credentials')) - ->setName('credential') - ->setAllowNull(true) - ->setValue($v_credentials) - ->setError($e_credentials) - ->setOptions($credentials)); - - return $this->newDialog() - ->setTitle($title) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form_errors) - ->appendChild($form) - ->addSubmitButton($submit) - ->addCancelButton($edit_uri); - } - - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index b9e8fa40c6..d38ad77787 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -248,21 +248,14 @@ final class DiffusionRepositoryController extends DiffusionController { private function buildCurtain(PhabricatorRepository $repository) { $viewer = $this->getViewer(); - $edit_uri = $repository->getPathURI('edit/'); + $edit_uri = $repository->getPathURI('manage/'); $curtain = $this->newCurtainView($repository); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Edit Repository')) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setWorkflow(!$can_edit) - ->setDisabled(!$can_edit)); + ->setName(pht('Manage Repository')) + ->setIcon('fa-cogs') + ->setHref($edit_uri)); if ($repository->isHosted()) { $push_uri = $this->getApplicationURI( @@ -301,56 +294,27 @@ final class DiffusionRepositoryController extends DiffusionController { $view = id(new PHUIPropertyListView()) ->setUser($viewer); - if ($repository->isHosted()) { - $ssh_uri = $repository->getSSHCloneURIObject(); - if ($ssh_uri) { - $clone_uri = $this->renderCloneCommand( - $repository, - $ssh_uri, - $repository->getServeOverSSH(), - '/settings/panel/ssh/'); + $display_never = PhabricatorRepositoryURI::DISPLAY_NEVER; - $view->addProperty( - $repository->isSVN() - ? pht('Checkout (SSH)') - : pht('Clone (SSH)'), - $clone_uri); + $uris = $repository->getURIs(); + foreach ($uris as $uri) { + if ($uri->getIsDisabled()) { + continue; } - $http_uri = $repository->getHTTPCloneURIObject(); - if ($http_uri) { - $clone_uri = $this->renderCloneCommand( - $repository, - $http_uri, - $repository->getServeOverHTTP(), - PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth') - ? '/settings/panel/vcspassword/' - : null); + if ($uri->getEffectiveDisplayType() == $display_never) { + continue; + } - $view->addProperty( - $repository->isSVN() - ? pht('Checkout (HTTP)') - : pht('Clone (HTTP)'), - $clone_uri); - } - } else { - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $view->addProperty( - pht('Clone'), - $this->renderCloneCommand( - $repository, - $repository->getPublicCloneURI())); - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $view->addProperty( - pht('Checkout'), - $this->renderCloneCommand( - $repository, - $repository->getPublicCloneURI())); - break; + if ($repository->isSVN()) { + $label = pht('Checkout'); + } else { + $label = pht('Clone'); } + + $view->addProperty( + $label, + $this->renderCloneURI($repository, $uri)); } $box = id(new PHUIObjectBoxView()) @@ -701,80 +665,27 @@ final class DiffusionRepositoryController extends DiffusionController { ); } - private function renderCloneCommand( + private function renderCloneURI( PhabricatorRepository $repository, - $uri, - $serve_mode = null, - $manage_uri = null) { + PhabricatorRepositoryURI $uri) { - require_celerity_resource('diffusion-icons-css'); - - Javelin::initBehavior('select-on-click'); - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $command = csprintf( - 'git clone %R', - $uri); - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $command = csprintf( - 'hg clone %R', - $uri); - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - if ($repository->isHosted()) { - $command = csprintf( - 'svn checkout %R %R', - $uri, - $repository->getCloneName()); - } else { - $command = csprintf( - 'svn checkout %R', - $uri); - } - break; + if ($repository->isSVN()) { + $display = csprintf( + 'svn checkout %R %R', + (string)$uri->getDisplayURI(), + $repository->getCloneName()); + } else { + $display = csprintf('%R', (string)$uri->getDisplayURI()); } - $input = javelin_tag( - 'input', - array( - 'type' => 'text', - 'value' => (string)$command, - 'class' => 'diffusion-clone-uri', - 'sigil' => 'select-on-click', - 'readonly' => 'true', - )); + $display = (string)$display; + $viewer = $this->getViewer(); - $extras = array(); - if ($serve_mode) { - if ($serve_mode === PhabricatorRepository::SERVE_READONLY) { - $extras[] = pht('(Read Only)'); - } - } - - if ($manage_uri) { - if ($this->getRequest()->getUser()->isLoggedIn()) { - $extras[] = phutil_tag( - 'a', - array( - 'href' => $manage_uri, - ), - pht('Manage Credentials')); - } - } - - if ($extras) { - $extras = phutil_implode_html(' ', $extras); - $extras = phutil_tag( - 'div', - array( - 'class' => 'diffusion-clone-extras', - ), - $extras); - } - - return array($input, $extras); + return id(new DiffusionCloneURIView()) + ->setViewer($viewer) + ->setRepository($repository) + ->setRepositoryURI($uri) + ->setDisplayURI($display); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php deleted file mode 100644 index 09fdd23565..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ /dev/null @@ -1,848 +0,0 @@ -getUser(); - $this->edit = $request->getURIData('edit'); - - // NOTE: We can end up here via either "Create Repository", or via - // "Import Repository", or via "Edit Remote", or via "Edit Policies". In - // the latter two cases, we show only a few of the pages. - - $repository = null; - $service = null; - switch ($this->edit) { - case 'remote': - case 'policy': - $response = $this->loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $repository = $this->getDiffusionRequest()->getRepository(); - - // Make sure we have CAN_EDIT. - PhabricatorPolicyFilter::requireCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $this->setRepository($repository); - - $cancel_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - break; - case 'import': - case 'create': - $this->requireApplicationCapability( - DiffusionCreateRepositoriesCapability::CAPABILITY); - - // Pick a random open service to allocate this repository on, if any - // exist. If there are no services, we aren't in cluster mode and - // will allocate locally. If there are services but none permit - // allocations, we fail. - $services = id(new AlmanacServiceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withServiceTypes( - array( - AlmanacClusterRepositoryServiceType::SERVICETYPE, - )) - ->needProperties(true) - ->execute(); - if ($services) { - // Filter out services which do not permit new allocations. - foreach ($services as $key => $possible_service) { - if ($possible_service->getAlmanacPropertyValue('closed')) { - unset($services[$key]); - } - } - - if (!$services) { - throw new Exception( - pht( - 'This install is configured in cluster mode, but all '. - 'available repository cluster services are closed to new '. - 'allocations. At least one service must be open to allow '. - 'new allocations to take place.')); - } - - shuffle($services); - $service = head($services); - } - - $cancel_uri = $this->getApplicationURI('new/'); - break; - default: - throw new Exception(pht('Invalid edit operation!')); - } - - $form = id(new PHUIPagedFormView()) - ->setUser($viewer) - ->setCancelURI($cancel_uri); - - switch ($this->edit) { - case 'remote': - $title = pht('Edit Remote'); - $form - ->addPage('remote-uri', $this->buildRemoteURIPage()) - ->addPage('auth', $this->buildAuthPage()); - break; - case 'policy': - $title = pht('Edit Policies'); - $form - ->addPage('policy', $this->buildPolicyPage()); - break; - case 'create': - $title = pht('Create Repository'); - $form - ->addPage('vcs', $this->buildVCSPage()) - ->addPage('name', $this->buildNamePage()) - ->addPage('policy', $this->buildPolicyPage()) - ->addPage('done', $this->buildDonePage()); - break; - case 'import': - $title = pht('Import Repository'); - $form - ->addPage('vcs', $this->buildVCSPage()) - ->addPage('name', $this->buildNamePage()) - ->addPage('remote-uri', $this->buildRemoteURIPage()) - ->addPage('auth', $this->buildAuthPage()) - ->addPage('policy', $this->buildPolicyPage()) - ->addPage('done', $this->buildDonePage()); - break; - } - - if ($request->isFormPost()) { - $form->readFromRequest($request); - if ($form->isComplete()) { - - $is_create = ($this->edit === 'import' || $this->edit === 'create'); - $is_auth = ($this->edit == 'import' || $this->edit == 'remote'); - $is_policy = ($this->edit != 'remote'); - $is_init = ($this->edit == 'create'); - - if ($is_create) { - $repository = PhabricatorRepository::initializeNewRepository( - $viewer); - } - - $template = id(new PhabricatorRepositoryTransaction()); - - $type_name = PhabricatorRepositoryTransaction::TYPE_NAME; - $type_vcs = PhabricatorRepositoryTransaction::TYPE_VCS; - $type_activate = PhabricatorRepositoryTransaction::TYPE_ACTIVATE; - $type_remote_uri = PhabricatorRepositoryTransaction::TYPE_REMOTE_URI; - $type_hosting = PhabricatorRepositoryTransaction::TYPE_HOSTING; - $type_http = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; - $type_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; - $type_credential = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - $type_space = PhabricatorTransactions::TYPE_SPACE; - $type_push = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY; - $type_service = PhabricatorRepositoryTransaction::TYPE_SERVICE; - - $xactions = array(); - - // If we're creating a new repository, set all this core stuff. - if ($is_create) { - $xactions[] = id(clone $template) - ->setTransactionType($type_name) - ->setNewValue( - $form->getPage('name')->getControl('name')->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_vcs) - ->setNewValue( - $form->getPage('vcs')->getControl('vcs')->getValue()); - - $activate = $form->getPage('done') - ->getControl('activate')->getValue(); - if ($activate == 'start') { - $initial_status = PhabricatorRepository::STATUS_ACTIVE; - } else { - $initial_status = PhabricatorRepository::STATUS_INACTIVE; - } - - $xactions[] = id(clone $template) - ->setTransactionType($type_activate) - ->setNewValue($initial_status); - - if ($service) { - $xactions[] = id(clone $template) - ->setTransactionType($type_service) - ->setNewValue($service->getPHID()); - } - } - - if ($is_init) { - $xactions[] = id(clone $template) - ->setTransactionType($type_hosting) - ->setNewValue(true); - $vcs = $form->getPage('vcs')->getControl('vcs')->getValue(); - if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) { - if (PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) { - $v_http_mode = PhabricatorRepository::SERVE_READWRITE; - } else { - $v_http_mode = PhabricatorRepository::SERVE_OFF; - } - $xactions[] = id(clone $template) - ->setTransactionType($type_http) - ->setNewValue($v_http_mode); - } - - if (PhabricatorEnv::getEnvConfig('diffusion.ssh-user')) { - $v_ssh_mode = PhabricatorRepository::SERVE_READWRITE; - } else { - $v_ssh_mode = PhabricatorRepository::SERVE_OFF; - } - $xactions[] = id(clone $template) - ->setTransactionType($type_ssh) - ->setNewValue($v_ssh_mode); - } - - if ($is_auth) { - $xactions[] = id(clone $template) - ->setTransactionType($type_remote_uri) - ->setNewValue( - $form->getPage('remote-uri')->getControl('remoteURI') - ->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_credential) - ->setNewValue( - $form->getPage('auth')->getControl('credential')->getValue()); - } - - if ($is_policy) { - $policy_page = $form->getPage('policy'); - - $xactions[] = id(clone $template) - ->setTransactionType($type_view) - ->setNewValue($policy_page->getControl('viewPolicy')->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_edit) - ->setNewValue($policy_page->getControl('editPolicy')->getValue()); - - if ($is_init || $repository->isHosted()) { - $xactions[] = id(clone $template) - ->setTransactionType($type_push) - ->setNewValue($policy_page->getControl('pushPolicy')->getValue()); - } - - $xactions[] = id(clone $template) - ->setTransactionType($type_space) - ->setNewValue( - $policy_page->getControl('viewPolicy')->getSpacePHID()); - } - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->applyTransactions($repository, $xactions); - - $repo_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - return id(new AphrontRedirectResponse())->setURI($repo_uri); - } - } else { - $dict = array(); - if ($repository) { - $dict = array( - 'remoteURI' => $repository->getRemoteURI(), - 'credential' => $repository->getCredentialPHID(), - 'viewPolicy' => $repository->getViewPolicy(), - 'editPolicy' => $repository->getEditPolicy(), - 'pushPolicy' => $repository->getPushPolicy(), - 'spacePHID' => $repository->getSpacePHID(), - ); - } - $form->readFromObject($dict); - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - - } - - -/* -( Page: VCS Type )----------------------------------------------------- */ - - - private function buildVCSPage() { - - $is_import = ($this->edit == 'import'); - - if ($is_import) { - $git_str = pht( - 'Import a Git repository (for example, a repository hosted '. - 'on GitHub).'); - $hg_str = pht( - 'Import a Mercurial repository (for example, a repository '. - 'hosted on Bitbucket).'); - $svn_str = pht('Import a Subversion repository.'); - } else { - $git_str = pht('Create a new, empty Git repository.'); - $hg_str = pht('Create a new, empty Mercurial repository.'); - $svn_str = pht('Create a new, empty Subversion repository.'); - } - - $control = id(new AphrontFormRadioButtonControl()) - ->setName('vcs') - ->setLabel(pht('Type')) - ->addButton( - PhabricatorRepositoryType::REPOSITORY_TYPE_GIT, - pht('Git'), - $git_str) - ->addButton( - PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL, - pht('Mercurial'), - $hg_str) - ->addButton( - PhabricatorRepositoryType::REPOSITORY_TYPE_SVN, - pht('Subversion'), - $svn_str); - - return id(new PHUIFormPageView()) - ->setPageName(pht('Repository Type')) - ->setUser($this->getRequest()->getUser()) - ->setValidateFormPageCallback(array($this, 'validateVCSPage')) - ->addControl($control); - } - - public function validateVCSPage(PHUIFormPageView $page) { - $valid = array( - PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => true, - PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => true, - PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => true, - ); - - $c_vcs = $page->getControl('vcs'); - $v_vcs = $c_vcs->getValue(); - if (!$v_vcs) { - $c_vcs->setError(pht('Required')); - $page->addPageError( - pht('You must select a version control system.')); - } else if (empty($valid[$v_vcs])) { - $c_vcs->setError(pht('Invalid')); - $page->addPageError( - pht('You must select a valid version control system.')); - } - - return $c_vcs->isValid(); - } - - -/* -( Page: Name )--------------------------------------------------------- */ - - - private function buildNamePage() { - return id(new PHUIFormPageView()) - ->setUser($this->getRequest()->getUser()) - ->setPageName(pht('Repository Name and Location')) - ->setValidateFormPageCallback(array($this, 'validateNamePage')) - ->addRemarkupInstructions( - pht( - '**Choose a human-readable name for this repository**, like '. - '"CompanyName Mobile App" or "CompanyName Backend Server". You '. - 'can change this later.')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name'))); - } - - public function validateNamePage(PHUIFormPageView $page) { - $c_name = $page->getControl('name'); - $v_name = $c_name->getValue(); - if (!strlen($v_name)) { - $c_name->setError(pht('Required')); - $page->addPageError( - pht('You must choose a name for this repository.')); - } - - return $c_name->isValid(); - } - - -/* -( Page: Remote URI )--------------------------------------------------- */ - - - private function buildRemoteURIPage() { - return id(new PHUIFormPageView()) - ->setUser($this->getRequest()->getUser()) - ->setPageName(pht('Repository Remote URI')) - ->setValidateFormPageCallback(array($this, 'validateRemoteURIPage')) - ->setAdjustFormPageCallback(array($this, 'adjustRemoteURIPage')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('remoteURI')); - } - - public function adjustRemoteURIPage(PHUIFormPageView $page) { - $form = $page->getForm(); - - $is_git = false; - $is_svn = false; - $is_mercurial = false; - - if ($this->getRepository()) { - $vcs = $this->getRepository()->getVersionControlSystem(); - } else { - $vcs = $form->getPage('vcs')->getControl('vcs')->getValue(); - } - - switch ($vcs) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $is_git = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $is_svn = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $is_mercurial = true; - break; - default: - throw new Exception(pht('Unsupported VCS!')); - } - - $has_local = ($is_git || $is_mercurial); - if ($is_git) { - $uri_label = pht('Remote URI'); - $instructions = pht( - 'Enter the URI to clone this Git repository from. It should usually '. - 'look like one of these examples:'. - "\n\n". - "| Example Git Remote URIs |\n". - "| ----------------------- |\n". - "| `git@github.com:example/example.git` |\n". - "| `ssh://user@host.com/git/example.git` |\n". - "| `https://example.com/repository.git` |\n"); - } else if ($is_mercurial) { - $uri_label = pht('Remote URI'); - $instructions = pht( - 'Enter the URI to clone this Mercurial repository from. It should '. - 'usually look like one of these examples:'. - "\n\n". - "| Example Mercurial Remote URIs |\n". - "| ----------------------- |\n". - "| `ssh://hg@bitbucket.org/example/repository` |\n". - "| `https://bitbucket.org/example/repository` |\n"); - } else if ($is_svn) { - $uri_label = pht('Repository Root'); - $instructions = pht( - 'Enter the **Repository Root** for this Subversion repository. '. - 'You can figure this out by running `svn info` in a working copy '. - 'and looking at the value in the `Repository Root` field. It '. - 'should be a URI and will usually look like these:'. - "\n\n". - "| Example Subversion Repository Root URIs |\n". - "| ------------------------------ |\n". - "| `http://svn.example.org/svnroot/` |\n". - "| `svn+ssh://svn.example.com/svnroot/` |\n". - "| `svn://svn.example.net/svnroot/` |\n". - "\n\n". - "You **MUST** specify the root of the repository, not a ". - "subdirectory. (If you want to import only part of a Subversion ". - "repository, use the //Import Only// option at the end of this ". - "workflow.)"); - } else { - throw new Exception(pht('Unsupported VCS!')); - } - - $page->addRemarkupInstructions($instructions, 'remoteURI'); - $page->getControl('remoteURI')->setLabel($uri_label); - } - - public function validateRemoteURIPage(PHUIFormPageView $page) { - $c_remote = $page->getControl('remoteURI'); - $v_remote = $c_remote->getValue(); - - if (!strlen($v_remote)) { - $c_remote->setError(pht('Required')); - $page->addPageError( - pht('You must specify a URI.')); - } else { - try { - PhabricatorRepository::assertValidRemoteURI($v_remote); - } catch (Exception $ex) { - $c_remote->setError(pht('Invalid')); - $page->addPageError($ex->getMessage()); - } - } - - return $c_remote->isValid(); - } - - -/* -( Page: Authentication )----------------------------------------------- */ - - - public function buildAuthPage() { - return id(new PHUIFormPageView()) - ->setPageName(pht('Authentication')) - ->setUser($this->getRequest()->getUser()) - ->setAdjustFormPageCallback(array($this, 'adjustAuthPage')) - ->addControl( - id(new PassphraseCredentialControl()) - ->setName('credential')); - } - - - public function adjustAuthPage($page) { - $form = $page->getForm(); - - if ($this->getRepository()) { - $vcs = $this->getRepository()->getVersionControlSystem(); - } else { - $vcs = $form->getPage('vcs')->getControl('vcs')->getValue(); - } - - $remote_uri = $form->getPage('remote-uri') - ->getControl('remoteURI') - ->getValue(); - - $proto = PhabricatorRepository::getRemoteURIProtocol($remote_uri); - $remote_user = $this->getRemoteURIUser($remote_uri); - - $c_credential = $page->getControl('credential'); - $c_credential->setDefaultUsername($remote_user); - - if ($this->isSSHProtocol($proto)) { - $c_credential->setLabel(pht('SSH Key')); - $c_credential->setCredentialType( - PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE); - $provides_type = PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE; - - $page->addRemarkupInstructions( - pht( - 'Choose or add the SSH credentials to use to connect to the '. - 'repository hosted at:'. - "\n\n". - " lang=text\n". - " %s", - $remote_uri), - 'credential'); - } else if ($this->isUsernamePasswordProtocol($proto)) { - $c_credential->setLabel(pht('Password')); - $c_credential->setAllowNull(true); - $c_credential->setCredentialType( - PassphrasePasswordCredentialType::CREDENTIAL_TYPE); - $provides_type = PassphrasePasswordCredentialType::PROVIDES_TYPE; - - $page->addRemarkupInstructions( - pht( - 'Choose the username and password used to connect to the '. - 'repository hosted at:'. - "\n\n". - " lang=text\n". - " %s". - "\n\n". - "If this repository does not require a username or password, ". - "you can continue to the next step.", - $remote_uri), - 'credential'); - } else { - throw new Exception(pht('Unknown URI protocol!')); - } - - if ($provides_type) { - $viewer = $this->getRequest()->getUser(); - - $options = id(new PassphraseCredentialQuery()) - ->setViewer($viewer) - ->withIsDestroyed(false) - ->withProvidesTypes(array($provides_type)) - ->execute(); - - $c_credential->setOptions($options); - } - - } - - public function validateAuthPage(PHUIFormPageView $page) { - $form = $page->getForm(); - $remote_uri = $form->getPage('remote')->getControl('remoteURI')->getValue(); - $proto = $this->getRemoteURIProtocol($remote_uri); - - $c_credential = $page->getControl('credential'); - $v_credential = $c_credential->getValue(); - - // NOTE: We're using the omnipotent user here because the viewer might be - // editing a repository they're allowed to edit which uses a credential they - // are not allowed to see. This is fine, as long as they don't change it. - $credential = id(new PassphraseCredentialQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($v_credential)) - ->executeOne(); - - if ($this->isSSHProtocol($proto)) { - if (!$credential) { - $c_credential->setError(pht('Required')); - $page->addPageError( - pht('You must choose an SSH credential to connect over SSH.')); - } - - $ssh_type = PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE; - if ($credential->getProvidesType() !== $ssh_type) { - $c_credential->setError(pht('Invalid')); - $page->addPageError( - pht( - 'You must choose an SSH credential, not some other type '. - 'of credential.')); - } - - } else if ($this->isUsernamePasswordProtocol($proto)) { - if ($credential) { - $password_type = PassphrasePasswordCredentialType::PROVIDES_TYPE; - if ($credential->getProvidesType() !== $password_type) { - $c_credential->setError(pht('Invalid')); - $page->addPageError( - pht( - 'You must choose a username/password credential, not some other '. - 'type of credential.')); - } - } - - return $c_credential->isValid(); - } else { - return true; - } - } - - -/* -( Page: Policy )------------------------------------------------------- */ - - - private function buildPolicyPage() { - $viewer = $this->getRequest()->getUser(); - if ($this->getRepository()) { - $repository = $this->getRepository(); - } else { - $repository = PhabricatorRepository::initializeNewRepository($viewer); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($repository) - ->execute(); - - $view_policy = id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($repository) - ->setPolicies($policies) - ->setName('viewPolicy'); - - $edit_policy = id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($repository) - ->setPolicies($policies) - ->setName('editPolicy'); - - $push_policy = id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(DiffusionPushCapability::CAPABILITY) - ->setPolicyObject($repository) - ->setPolicies($policies) - ->setName('pushPolicy'); - - return id(new PHUIFormPageView()) - ->setPageName(pht('Policies')) - ->setValidateFormPageCallback(array($this, 'validatePolicyPage')) - ->setAdjustFormPageCallback(array($this, 'adjustPolicyPage')) - ->setUser($viewer) - ->addRemarkupInstructions( - pht('Select access policies for this repository.')) - ->addControl($view_policy) - ->addControl($edit_policy) - ->addControl($push_policy); - } - - public function adjustPolicyPage(PHUIFormPageView $page) { - if ($this->getRepository()) { - $repository = $this->getRepository(); - $show_push = $repository->isHosted(); - } else { - $show_push = ($this->edit == 'create'); - } - - if (!$show_push) { - $c_push = $page->getControl('pushPolicy'); - $c_push->setHidden(true); - } - } - - public function validatePolicyPage(PHUIFormPageView $page) { - $form = $page->getForm(); - $viewer = $this->getRequest()->getUser(); - - $c_view = $page->getControl('viewPolicy'); - $c_edit = $page->getControl('editPolicy'); - $c_push = $page->getControl('pushPolicy'); - $v_view = $c_view->getValue(); - $v_edit = $c_edit->getValue(); - $v_push = $c_push->getValue(); - - if ($this->getRepository()) { - $repository = $this->getRepository(); - } else { - $repository = PhabricatorRepository::initializeNewRepository($viewer); - } - - $proxy = clone $repository; - $proxy->setViewPolicy($v_view); - $proxy->setEditPolicy($v_edit); - - $can_view = PhabricatorPolicyFilter::hasCapability( - $viewer, - $proxy, - PhabricatorPolicyCapability::CAN_VIEW); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $proxy, - PhabricatorPolicyCapability::CAN_EDIT); - - if (!$can_view) { - $c_view->setError(pht('Invalid')); - $page->addPageError( - pht( - 'You can not use the selected policy, because you would be unable '. - 'to see the repository.')); - } - - if (!$can_edit) { - $c_edit->setError(pht('Invalid')); - $page->addPageError( - pht( - 'You can not use the selected edit policy, because you would be '. - 'unable to edit the repository.')); - } - - return $c_view->isValid() && - $c_edit->isValid(); - } - -/* -( Page: Done )--------------------------------------------------------- */ - - - private function buildDonePage() { - - $is_create = ($this->edit == 'create'); - if ($is_create) { - $now_label = pht('Create Repository Now'); - $now_caption = pht( - 'Create the repository right away. This will create the repository '. - 'using default settings.'); - - $wait_label = pht('Configure More Options First'); - $wait_caption = pht( - 'Configure more options before creating the repository. '. - 'This will let you fine-tune settings. You can create the repository '. - 'whenever you are ready.'); - } else { - $now_label = pht('Start Import Now'); - $now_caption = pht( - 'Start importing the repository right away. This will import '. - 'the entire repository using default settings.'); - - $wait_label = pht('Configure More Options First'); - $wait_caption = pht( - 'Configure more options before beginning the repository '. - 'import. This will let you fine-tune settings. You can '. - 'start the import whenever you are ready.'); - } - - return id(new PHUIFormPageView()) - ->setPageName(pht('Repository Ready!')) - ->setValidateFormPageCallback(array($this, 'validateDonePage')) - ->setUser($this->getRequest()->getUser()) - ->addControl( - id(new AphrontFormRadioButtonControl()) - ->setName('activate') - ->setLabel(pht('Start Now')) - ->addButton( - 'start', - $now_label, - $now_caption) - ->addButton( - 'wait', - $wait_label, - $wait_caption)); - } - - public function validateDonePage(PHUIFormPageView $page) { - $c_activate = $page->getControl('activate'); - $v_activate = $c_activate->getValue(); - - if ($v_activate != 'start' && $v_activate != 'wait') { - $c_activate->setError(pht('Required')); - $page->addPageError( - pht('Make a choice about repository activation.')); - } - - return $c_activate->isValid(); - } - - -/* -( Internal )----------------------------------------------------------- */ - - private function getRemoteURIUser($raw_uri) { - $uri = new PhutilURI($raw_uri); - if ($uri->getUser()) { - return $uri->getUser(); - } - - $git_uri = new PhutilGitURI($raw_uri); - if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) { - return $git_uri->getUser(); - } - - return null; - } - - private function isSSHProtocol($proto) { - return ($proto == 'git' || $proto == 'ssh' || $proto == 'svn+ssh'); - } - - private function isUsernamePasswordProtocol($proto) { - return ($proto == 'http' || $proto == 'https' || $proto == 'svn'); - } - - private function setRepository(PhabricatorRepository $repository) { - $this->repository = $repository; - return $this; - } - - private function getRepository() { - return $this->repository; - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php deleted file mode 100644 index b8f95f36fc..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php +++ /dev/null @@ -1,121 +0,0 @@ -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. - $v_notify = !$repository->getHumanReadableDetail('herald-disabled'); - $v_autoclose = !$repository->getHumanReadableDetail('disable-autoclose'); - - if ($request->isFormPost()) { - $v_notify = $request->getBool('notify'); - $v_autoclose = $request->getBool('autoclose'); - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_notify = PhabricatorRepositoryTransaction::TYPE_NOTIFY; - $type_autoclose = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE; - - $xactions[] = id(clone $template) - ->setTransactionType($type_notify) - ->setNewValue($v_notify); - - $xactions[] = id(clone $template) - ->setTransactionType($type_autoclose) - ->setNewValue($v_autoclose); - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } - - $content = array(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Actions')); - - $title = pht('Edit Actions (%s)', $repository->getName()); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($repository) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht( - "Normally, Phabricator publishes notifications when it discovers ". - "new commits. You can disable publishing for this repository by ". - "turning off **Notify/Publish**. This will disable notifications, ". - "feed, and Herald (including audits and build plans) for this ". - "repository.\n\n". - "When Phabricator discovers a new commit, it can automatically ". - "close associated revisions and tasks. If you don't want ". - "Phabricator to close objects when it discovers new commits in ". - "this repository, you can disable **Autoclose**.")) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('notify') - ->setLabel(pht('Notify/Publish')) - ->setValue((int)$v_notify) - ->setOptions( - array( - 1 => pht('Enable Notifications, Feed and Herald'), - 0 => pht('Disable Notifications, Feed and Herald'), - ))) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('autoclose') - ->setLabel(pht('Autoclose')) - ->setValue((int)$v_autoclose) - ->setOptions( - array( - 1 => pht('Enable Autoclose'), - 0 => pht('Disable Autoclose'), - ))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Actions')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Actions')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php index 55caaa59b9..b383333fbc 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php @@ -1,7 +1,7 @@ loadDiffusionContextForEdit(); @@ -13,7 +13,9 @@ final class DiffusionRepositoryEditActivateController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); if ($request->isFormPost()) { if (!$repository->isTracked()) { @@ -33,24 +35,44 @@ final class DiffusionRepositoryEditActivateController ->setActor($viewer) ->applyTransactions($repository, array($xaction)); - return id(new AphrontReloadResponse())->setURI($edit_uri); + return id(new AphrontReloadResponse())->setURI($panel_uri); } if ($repository->isTracked()) { - return $this->newDialog() - ->setTitle(pht('Deactivate Repository?')) - ->appendChild( - pht('Deactivate this repository?')) - ->addSubmitButton(pht('Deactivate Repository')) - ->addCancelButton($edit_uri); + $title = pht('Deactivate Repository'); + $body = pht( + 'If you deactivate this repository, it will no longer be updated. '. + 'Observation and mirroring will cease, and pushing and pulling will '. + 'be disabled. You can reactivate the repository later.'); + $submit = pht('Deactivate Repository'); } else { - return $this->newDialog() - ->setTitle(pht('Activate Repository?')) - ->appendChild( - pht('Activate this repository?')) - ->addSubmitButton(pht('Activate Repository')) - ->addCancelButton($edit_uri); + $title = pht('Activate Repository'); + + $is_new = $repository->isNewlyInitialized(); + if ($is_new) { + if ($repository->isHosted()) { + $body = pht( + 'This repository will become a new hosted repository. '. + 'It will begin serving read and write traffic.'); + } else { + $body = pht( + 'This repository will observe an existing remote repository. '. + 'It will begin fetching changes from the remote.'); + } + } else { + $body = pht( + 'This repository will resume updates, observation, mirroring, '. + 'and serving any configured read and write traffic.'); + } + + $submit = pht('Activate Repository'); } + + return $this->newDialog() + ->setTitle($title) + ->appendChild($body) + ->addSubmitButton($submit) + ->addCancelButton($panel_uri); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php deleted file mode 100644 index 038cc0f0cd..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php +++ /dev/null @@ -1,93 +0,0 @@ -loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - if (!$repository->supportsAutomation()) { - return new Aphront404Response(); - } - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - - $v_blueprints = $repository->getHumanReadableDetail( - 'automation.blueprintPHIDs'); - - if ($request->isFormPost()) { - $v_blueprints = $request->getArr('blueprintPHIDs'); - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_blueprints = - PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS; - - $xactions[] = id(clone $template) - ->setTransactionType($type_blueprints) - ->setNewValue($v_blueprints); - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Automation')); - - $title = pht('Edit %s', $repository->getName()); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht( - "Configure **Repository Automation** to allow Phabricator to ". - "write to this repository.". - "\n\n". - "IMPORTANT: This feature is new, experimental, and not supported. ". - "Use it at your own risk.")) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Use Blueprints')) - ->setName('blueprintPHIDs') - ->setValue($v_blueprints) - ->setDatasource(new DrydockBlueprintDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Automation')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php deleted file mode 100644 index 10f4b13ef9..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ /dev/null @@ -1,193 +0,0 @@ -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_slug = $repository->getRepositorySlug(); - $v_callsign = $repository->getCallsign(); - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $repository->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $e_name = true; - $e_slug = null; - $e_callsign = null; - $errors = array(); - - $validation_exception = null; - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_desc = $request->getStr('description'); - $v_projects = $request->getArr('projectPHIDs'); - $v_slug = $request->getStr('slug'); - $v_callsign = $request->getStr('callsign'); - - if (!strlen($v_name)) { - $e_name = pht('Required'); - $errors[] = pht('Repository name is required.'); - } else { - $e_name = null; - } - - if (!$errors) { - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_name = PhabricatorRepositoryTransaction::TYPE_NAME; - $type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; - $type_edge = PhabricatorTransactions::TYPE_EDGE; - $type_slug = PhabricatorRepositoryTransaction::TYPE_SLUG; - $type_callsign = PhabricatorRepositoryTransaction::TYPE_CALLSIGN; - - $xactions[] = id(clone $template) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(clone $template) - ->setTransactionType($type_desc) - ->setNewValue($v_desc); - - $xactions[] = id(clone $template) - ->setTransactionType($type_slug) - ->setNewValue($v_slug); - - $xactions[] = id(clone $template) - ->setTransactionType($type_callsign) - ->setNewValue($v_callsign); - - $xactions[] = id(clone $template) - ->setTransactionType($type_edge) - ->setMetadataValue( - 'edge:type', - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) - ->setNewValue( - array( - '=' => array_fuse($v_projects), - )); - - $editor = id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer); - - try { - $editor->applyTransactions($repository, $xactions); - - // The preferred edit URI may have changed if the callsign or slug - // were adjusted, so grab a fresh copy. - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_slug = $ex->getShortMessage($type_slug); - $e_callsign = $ex->getShortMessage($type_callsign); - } - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Basics')); - - $title = pht('Edit %s', $repository->getName()); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $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('slug') - ->setLabel(pht('Short Name')) - ->setValue($v_slug) - ->setError($e_slug)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('callsign') - ->setLabel(pht('Callsign')) - ->setValue($v_callsign) - ->setError($e_callsign)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('description') - ->setLabel(pht('Description')) - ->setValue($v_desc)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectDatasource()) - ->setName('projectPHIDs') - ->setLabel(pht('Projects')) - ->setValue($v_projects)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton($edit_uri)) - ->appendChild(id(new PHUIFormDividerControl())) - ->appendRemarkupInstructions($this->getReadmeInstructions()); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Basic Information')) - ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form) - ->setFormErrors($errors); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function getReadmeInstructions() { - return pht(<<loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $is_git = false; - $is_hg = false; - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $is_git = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $is_hg = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - throw new Exception( - pht('Subversion does not support branches!')); - default: - throw new Exception( - pht('Repository has unknown version control system!')); - } - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - - $v_default = $repository->getHumanReadableDetail('default-branch'); - $v_track = $repository->getDetail( - 'branch-filter', - array()); - $v_track = array_keys($v_track); - $v_autoclose = $repository->getDetail( - 'close-commits-filter', - array()); - $v_autoclose = array_keys($v_autoclose); - - $e_track = null; - $e_autoclose = null; - - $validation_exception = null; - if ($request->isFormPost()) { - $v_default = $request->getStr('default'); - - $v_track = $this->processBranches($request->getStr('track')); - if (!$is_hg) { - $v_autoclose = $this->processBranches($request->getStr('autoclose')); - } - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_default = PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH; - $type_track = PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY; - $type_autoclose = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY; - - $xactions[] = id(clone $template) - ->setTransactionType($type_default) - ->setNewValue($v_default); - - $xactions[] = id(clone $template) - ->setTransactionType($type_track) - ->setNewValue($v_track); - - if (!$is_hg) { - $xactions[] = id(clone $template) - ->setTransactionType($type_autoclose) - ->setNewValue($v_autoclose); - } - - $editor = id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer); - - try { - $editor->applyTransactions($repository, $xactions); - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_track = $validation_exception->getShortMessage($type_track); - $e_autoclose = $validation_exception->getShortMessage($type_autoclose); - } - } - - $content = array(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Branches')); - - $title = pht('Edit Branches (%s)', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($repository) - ->execute(); - - $rows = array(); - $rows[] = array( - array( - 'master', - ), - pht('Select only master.'), - ); - $rows[] = array( - array( - 'master', - 'develop', - 'release', - ), - pht('Select %s, %s, and %s.', 'master', 'develop', 'release'), - ); - $rows[] = array( - array( - 'master', - 'regexp(/^release-/)', - ), - pht('Select master, and all branches which start with "%s".', 'release-'), - ); - $rows[] = array( - array( - 'regexp(/^(?!temp-)/)', - ), - pht('Select all branches which do not start with "%s".', 'temp-'), - ); - - foreach ($rows as $k => $row) { - $rows[$k][0] = phutil_tag( - 'pre', - array(), - implode("\n", $row[0])); - } - - $example_table = id(new AphrontTableView($rows)) - ->setHeaders( - array( - pht('Example'), - pht('Effect'), - )) - ->setColumnClasses( - array( - '', - 'wide', - )); - - $v_track = implode("\n", $v_track); - $v_autoclose = implode("\n", $v_autoclose); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht('You can choose a **Default Branch** for viewing this repository.')) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('default') - ->setLabel(pht('Default Branch')) - ->setValue($v_default)) - ->appendRemarkupInstructions( - pht( - 'If you want to import only some branches into Diffusion, you can '. - 'list them in **Track Only**. Other branches will be ignored. If '. - 'you do not specify any branches, all branches are tracked.')); - - if (!$is_hg) { - $form->appendRemarkupInstructions( - pht( - 'If you have **Autoclose** enabled for this repository, Phabricator '. - 'can close tasks and revisions when corresponding commits are '. - 'pushed to the repository. If you want to autoclose objects only '. - 'when commits appear on specific branches, you can list those '. - 'branches in **Autoclose Only**. By default, all tracked branches '. - 'will autoclose objects.')); - } - - $form - ->appendRemarkupInstructions( - pht( - 'When specifying branches, you should enter one branch name per '. - 'line. You can use regular expressions to match branches by '. - 'wrapping an expression in `%s`. For example:', - 'regexp(...)')) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setValue($example_table)) - ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setName('track') - ->setLabel(pht('Track Only')) - ->setError($e_track) - ->setValue($v_track)); - - if (!$is_hg) { - $form->appendChild( - id(new AphrontFormTextAreaControl()) - ->setName('autoclose') - ->setLabel(pht('Autoclose Only')) - ->setError($e_autoclose) - ->setValue($v_autoclose)); - } - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Branches')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Branches')) - ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function processBranches($string) { - $lines = phutil_split_lines($string, $retain_endings = false); - foreach ($lines as $key => $line) { - $lines[$key] = trim($line); - if (!strlen($lines[$key])) { - unset($lines[$key]); - } - } - - return array_values($lines); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 714568f187..b8892a94ea 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -1,28 +1,69 @@ setController($this); - if ($this->hasDiffusionRequest()) { - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - $repo_uri = $repository->getURI(); - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + $id = $request->getURIData('id'); + if (!$id) { + $this->requireApplicationCapability( + DiffusionCreateRepositoriesCapability::CAPABILITY); - $crumbs->addTextCrumb($repository->getDisplayname(), $repo_uri); - - if ($is_main) { - $crumbs->addTextCrumb(pht('Edit Repository')); - } else { - $crumbs->addTextCrumb(pht('Edit'), $edit_uri); + $vcs = $request->getStr('vcs'); + $vcs_types = PhabricatorRepositoryType::getRepositoryTypeMap(); + if (empty($vcs_types[$vcs])) { + return $this->buildVCSTypeResponse(); } + + $engine + ->addContextParameter('vcs', $vcs) + ->setVersionControlSystem($vcs); } + + return $engine->buildResponse(); + } + + private function buildVCSTypeResponse() { + $vcs_types = PhabricatorRepositoryType::getRepositoryTypeMap(); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Create Repository')); $crumbs->setBorder(true); - return $crumbs; + $title = pht('Choose Repository Type'); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Create Repository')) + ->setHeaderIcon('fa-plus-square'); + + $layout = id(new AphrontMultiColumnView()) + ->setFluidLayout(true); + + $create_uri = $request->getRequestURI(); + + foreach ($vcs_types as $vcs_key => $vcs_type) { + $action = id(new PHUIActionPanelView()) + ->setIcon(idx($vcs_type, 'icon')) + ->setHeader(idx($vcs_type, 'create.header')) + ->setHref($create_uri->alter('vcs', $vcs_key)) + ->setSubheader(idx($vcs_type, 'create.subheader')); + + $layout->addColumn($action); + } + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($layout); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php index 26d8b57f33..d0dd2cc2e9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php @@ -1,7 +1,7 @@ loadDiffusionContextForEdit(); @@ -13,11 +13,31 @@ final class DiffusionRepositoryEditDangerousController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - if (!$repository->canAllowDangerousChanges()) { - return new Aphront400Response(); - } + $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + if (!$repository->canAllowDangerousChanges()) { + if ($repository->isSVN()) { + return $this->newDialog() + ->setTitle(pht('Not in Danger')) + ->appendParagraph( + pht( + 'It is not possible for users to push any dangerous changes '. + 'to a Subversion repository. Pushes to a Subversion repository '. + 'can always be reverted and never destroy data.')) + ->addCancelButton($panel_uri); + } else { + return $this->newDialog() + ->setTitle(pht('Unprotectable Repository')) + ->appendParagraph( + pht( + 'This repository can not be protected from dangerous changes '. + 'because Phabricator does not control what users are allowed '. + 'to push to it.')) + ->addCancelButton($panel_uri); + } + } if ($request->isFormPost()) { $xaction = id(new PhabricatorRepositoryTransaction()) @@ -30,33 +50,33 @@ final class DiffusionRepositoryEditDangerousController ->setActor($viewer) ->applyTransactions($repository, array($xaction)); - return id(new AphrontReloadResponse())->setURI($edit_uri); + return id(new AphrontReloadResponse())->setURI($panel_uri); } $force = phutil_tag('tt', array(), '--force'); if ($repository->shouldAllowDangerousChanges()) { - return $this->newDialog() - ->setTitle(pht('Prevent Dangerous changes?')) - ->appendChild( - pht( - 'It will no longer be possible to delete branches from this '. - 'repository, or %s push to this repository.', - $force)) - ->addSubmitButton(pht('Prevent Dangerous Changes')) - ->addCancelButton($edit_uri); + $title = pht('Prevent Dangerous Changes'); + $body = pht( + 'It will no longer be possible to delete branches from this '. + 'repository, or %s push to this repository.', + $force); + $submit = pht('Prevent Dangerous Changes'); } else { - return $this->newDialog() - ->setTitle(pht('Allow Dangerous Changes?')) - ->appendChild( - pht( - 'If you allow dangerous changes, it will be possible to delete '. - 'branches and %s push this repository. These operations can '. - 'alter a repository in a way that is difficult to recover from.', - $force)) - ->addSubmitButton(pht('Allow Dangerous Changes')) - ->addCancelButton($edit_uri); + $title = pht('Allow Dangerous Changes'); + $body = pht( + 'If you allow dangerous changes, it will be possible to delete '. + 'branches and %s push this repository. These operations can '. + 'alter a repository in a way that is difficult to recover from.', + $force); + $submit = pht('Allow Dangerous Changes'); } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addSubmitButton($submit) + ->addCancelButton($panel_uri); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php index 074e79445b..0380b99f0f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php @@ -1,7 +1,7 @@ loadDiffusionContextForEdit(); @@ -13,7 +13,9 @@ final class DiffusionRepositoryEditDeleteController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); $dialog = new AphrontDialogView(); $text_1 = pht( @@ -40,7 +42,7 @@ final class DiffusionRepositoryEditDeleteController return $this->newDialog() ->setTitle(pht('Really want to delete the repository?')) ->appendChild($body) - ->addCancelButton($edit_uri, pht('Okay')); + ->addCancelButton($panel_uri, pht('Okay')); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php deleted file mode 100644 index 256e69ac59..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php +++ /dev/null @@ -1,107 +0,0 @@ -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'); - $e_encoding = null; - $errors = array(); - - if ($request->isFormPost()) { - $v_encoding = $request->getStr('encoding'); - - if (!$errors) { - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_encoding = PhabricatorRepositoryTransaction::TYPE_ENCODING; - - $xactions[] = id(clone $template) - ->setTransactionType($type_encoding) - ->setNewValue($v_encoding); - - try { - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($user) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } catch (Exception $ex) { - $errors[] = $ex->getMessage(); - } - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Encoding')); - - $title = pht('Edit %s', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendRemarkupInstructions($this->getEncodingInstructions()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('encoding') - ->setLabel(pht('Text Encoding')) - ->setValue($v_encoding) - ->setError($e_encoding)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Encoding')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Encoding')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form) - ->setFormErrors($errors); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function getEncodingInstructions() { - return pht(<<loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $this->serve = $request->getURIData('serve'); - - if (!$this->serve) { - return $this->handleHosting($repository); - } else { - return $this->handleProtocols($repository); - } - } - - public function handleHosting(PhabricatorRepository $repository) { - $request = $this->getRequest(); - $user = $request->getUser(); - - $v_hosting = $repository->isHosted(); - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - $next_uri = $this->getRepositoryControllerURI($repository, 'edit/serve/'); - - if ($request->isFormPost()) { - $v_hosting = $request->getBool('hosting'); - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_hosting = PhabricatorRepositoryTransaction::TYPE_HOSTING; - - $xactions[] = id(clone $template) - ->setTransactionType($type_hosting) - ->setNewValue($v_hosting); - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($user) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($next_uri); - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Hosting')); - - $title = pht('Edit Hosting (%s)', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $hosted_control = id(new AphrontFormRadioButtonControl()) - ->setName('hosting') - ->setLabel(pht('Hosting')) - ->addButton( - true, - pht('Host Repository on Phabricator'), - pht( - 'Phabricator will host this repository. Users will be able to '. - 'push commits to Phabricator. Phabricator will not pull '. - 'changes from elsewhere.')) - ->addButton( - false, - pht('Host Repository Elsewhere'), - pht( - 'Phabricator will pull updates to this repository from a master '. - 'repository elsewhere (for example, on GitHub or Bitbucket). '. - 'Users will not be able to push commits to this repository.')) - ->setValue($v_hosting); - - $doc_href = PhabricatorEnv::getDoclink( - 'Diffusion User Guide: Repository Hosting'); - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendRemarkupInstructions( - pht( - 'Phabricator can host repositories, or it can track repositories '. - 'hosted elsewhere (like on GitHub or Bitbucket). For information '. - 'on configuring hosting, see [[ %s | Diffusion User Guide: '. - 'Repository Hosting]]', - $doc_href)) - ->appendChild($hosted_control) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save and Continue')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Hosting')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - public function handleProtocols(PhabricatorRepository $repository) { - $request = $this->getRequest(); - $user = $request->getUser(); - - $type = $repository->getVersionControlSystem(); - $is_svn = ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); - - $v_http_mode = $repository->getDetail( - 'serve-over-http', - PhabricatorRepository::SERVE_OFF); - $v_ssh_mode = $repository->getDetail( - 'serve-over-ssh', - PhabricatorRepository::SERVE_OFF); - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - $prev_uri = $this->getRepositoryControllerURI($repository, 'edit/hosting/'); - - if ($request->isFormPost()) { - $v_http_mode = $request->getStr('http'); - $v_ssh_mode = $request->getStr('ssh'); - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_http = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; - $type_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; - - if (!$is_svn) { - $xactions[] = id(clone $template) - ->setTransactionType($type_http) - ->setNewValue($v_http_mode); - } - - $xactions[] = id(clone $template) - ->setTransactionType($type_ssh) - ->setNewValue($v_ssh_mode); - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($user) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Protocols')); - - $title = pht('Edit Protocols (%s)', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $rw_message = pht( - 'Phabricator will serve a read-write copy of this repository.'); - - if (!$repository->isHosted()) { - $rw_message = array( - $rw_message, - phutil_tag('br'), - phutil_tag('br'), - pht( - '%s: This repository is hosted elsewhere, so Phabricator can not '. - 'perform writes. This mode will act like "Read Only" for '. - 'repositories hosted elsewhere.', - phutil_tag('strong', array(), pht('WARNING'))), - ); - } - - $ssh_control = - id(new AphrontFormRadioButtonControl()) - ->setName('ssh') - ->setLabel(pht('SSH')) - ->setValue($v_ssh_mode) - ->addButton( - PhabricatorRepository::SERVE_OFF, - PhabricatorRepository::getProtocolAvailabilityName( - PhabricatorRepository::SERVE_OFF), - pht('Phabricator will not serve this repository over SSH.')) - ->addButton( - PhabricatorRepository::SERVE_READONLY, - PhabricatorRepository::getProtocolAvailabilityName( - PhabricatorRepository::SERVE_READONLY), - pht( - 'Phabricator will serve a read-only copy of this repository '. - 'over SSH.')) - ->addButton( - PhabricatorRepository::SERVE_READWRITE, - PhabricatorRepository::getProtocolAvailabilityName( - PhabricatorRepository::SERVE_READWRITE), - $rw_message); - - $http_control = - id(new AphrontFormRadioButtonControl()) - ->setName('http') - ->setLabel(pht('HTTP')) - ->setValue($v_http_mode) - ->addButton( - PhabricatorRepository::SERVE_OFF, - PhabricatorRepository::getProtocolAvailabilityName( - PhabricatorRepository::SERVE_OFF), - pht('Phabricator will not serve this repository over HTTP.')) - ->addButton( - PhabricatorRepository::SERVE_READONLY, - PhabricatorRepository::getProtocolAvailabilityName( - PhabricatorRepository::SERVE_READONLY), - pht( - 'Phabricator will serve a read-only copy of this repository '. - 'over HTTP.')) - ->addButton( - PhabricatorRepository::SERVE_READWRITE, - PhabricatorRepository::getProtocolAvailabilityName( - PhabricatorRepository::SERVE_READWRITE), - $rw_message); - - if ($is_svn) { - $http_control = id(new AphrontFormMarkupControl()) - ->setLabel(pht('HTTP')) - ->setValue( - phutil_tag( - 'em', - array(), - pht( - 'Phabricator does not currently support HTTP access to '. - 'Subversion repositories.'))); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendRemarkupInstructions( - pht( - 'Phabricator can serve repositories over various protocols. You can '. - 'configure server protocols here.')) - ->appendChild($ssh_control); - - if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) { - $form->appendRemarkupInstructions( - pht( - 'NOTE: The configuration setting [[ %s | %s ]] is currently '. - 'disabled. You must enable it to activate authenticated access '. - 'to repositories over HTTP.', - '/config/edit/diffusion.allow-http-auth/', - 'diffusion.allow-http-auth')); - } - - $form - ->appendChild($http_control) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Changes')) - ->addCancelButton($prev_uri, pht('Back'))); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Protocols')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php deleted file mode 100644 index d8a4cf40ba..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ /dev/null @@ -1,1338 +0,0 @@ -loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $is_svn = false; - $is_git = false; - $is_hg = false; - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $is_git = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $is_svn = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $is_hg = true; - break; - } - - $has_branches = ($is_git || $is_hg); - $has_local = $repository->usesLocalWorkingCopy(); - $supports_staging = $repository->supportsStaging(); - $supports_automation = $repository->supportsAutomation(); - - $crumbs = $this->buildApplicationCrumbs($is_main = true); - - $title = pht('Edit %s', $repository->getName()); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - if ($repository->isTracked()) { - $header->setStatus('fa-check', 'bluegrey', pht('Active')); - } else { - $header->setStatus('fa-ban', 'dark', pht('Inactive')); - } - - $curtain = $this->buildCurtain($repository); - $basic_properties = $this->buildBasicProperties($repository); - - $policy_actions = $this->buildPolicyActions($repository); - $policy_properties = - $this->buildPolicyProperties($repository, $policy_actions); - - $remote_properties = null; - if (!$repository->isHosted()) { - $remote_properties = $this->buildRemoteProperties( - $repository, - $this->buildRemoteActions($repository)); - } - - $encoding_actions = $this->buildEncodingActions($repository); - $encoding_properties = - $this->buildEncodingProperties($repository, $encoding_actions); - - $symbols_actions = $this->buildSymbolsActions($repository); - $symbols_properties = - $this->buildSymbolsProperties($repository, $symbols_actions); - - $hosting_properties = $this->buildHostingProperties( - $repository, - $this->buildHostingActions($repository)); - - - $branches_properties = null; - if ($has_branches) { - $branches_properties = $this->buildBranchesProperties( - $repository, - $this->buildBranchesActions($repository)); - } - - $subversion_properties = null; - if ($is_svn) { - $subversion_properties = $this->buildSubversionProperties( - $repository, - $this->buildSubversionActions($repository)); - } - - $storage_properties = null; - if ($has_local) { - $storage_properties = $this->buildStorageProperties( - $repository, - $this->buildStorageActions($repository)); - } - - $staging_properties = null; - if ($supports_staging) { - $staging_properties = $this->buildStagingProperties( - $repository, - $this->buildStagingActions($repository)); - } - - $automation_properties = null; - if ($supports_automation) { - $automation_properties = $this->buildAutomationProperties( - $repository, - $this->buildAutomationActions($repository)); - } - - $actions_properties = $this->buildActionsProperties( - $repository, - $this->buildActionsActions($repository)); - - $timeline = $this->buildTransactionTimeline( - $repository, - new PhabricatorRepositoryTransactionQuery()); - $timeline->setShouldTerminate(true); - - $boxes = array(); - - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Policies')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($policy_properties); - - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Hosting')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($hosting_properties); - - if ($repository->canMirror()) { - $mirror_actions = $this->buildMirrorActions($repository); - $mirror_properties = $this->buildMirrorProperties( - $repository, - $mirror_actions); - - $mirrors = id(new PhabricatorRepositoryMirrorQuery()) - ->setViewer($viewer) - ->withRepositoryPHIDs(array($repository->getPHID())) - ->execute(); - - $mirror_list = $this->buildMirrorList($repository, $mirrors); - - $boxes[] = id(new PhabricatorAnchorView())->setAnchorName('mirrors'); - - $mirror_info = array(); - if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { - $mirror_info[] = pht( - 'Phabricator is running in silent mode, so changes will not '. - 'be pushed to mirrors.'); - } - - $boxes[] = id(new PHUIObjectBoxView()) - ->setFormErrors($mirror_info) - ->setHeaderText(pht('Mirrors')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($mirror_properties); - - $boxes[] = $mirror_list; - } - - if ($remote_properties) { - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Remote')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($remote_properties); - } - - if ($storage_properties) { - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Storage')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($storage_properties); - } - - if ($staging_properties) { - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Staging')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($staging_properties); - } - - if ($automation_properties) { - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Automation')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($automation_properties); - } - - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Text Encoding')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($encoding_properties); - - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Symbols')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($symbols_properties); - - if ($branches_properties) { - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Branches')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($branches_properties); - } - - if ($subversion_properties) { - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Subversion')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($subversion_properties); - } - - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Actions')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($actions_properties); - - $crumbs->setBorder(true); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->addPropertySection(pht('Properties'), $basic_properties) - ->setMainColumn(array( - $boxes, - $timeline, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - - } - - private function buildCurtain(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $curtain = $this->newCurtainView($repository); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Basic Information')) - ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')); - $curtain->addAction($edit); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-refresh') - ->setName(pht('Update Now')) - ->setWorkflow(true) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/update/')); - $curtain->addAction($edit); - - $activate = id(new PhabricatorActionView()) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/activate/')) - ->setWorkflow(true); - - if ($repository->isTracked()) { - $activate - ->setIcon('fa-pause') - ->setName(pht('Deactivate Repository')); - } else { - $activate - ->setIcon('fa-play') - ->setName(pht('Activate Repository')); - } - - $curtain->addAction($activate); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Delete Repository')) - ->setIcon('fa-times') - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/delete/')) - ->setDisabled(true) - ->setWorkflow(true)); - - return $curtain; - } - - private function buildBasicProperties( - PhabricatorRepository $repository) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $type = PhabricatorRepositoryType::getNameForRepositoryType( - $repository->getVersionControlSystem()); - - $view->addProperty(pht('Type'), $type); - - $callsign = $repository->getCallsign(); - if (!strlen($callsign)) { - $callsign = phutil_tag('em', array(), pht('No Callsign')); - } - $view->addProperty(pht('Callsign'), $callsign); - - $short_name = $repository->getRepositorySlug(); - if ($short_name === null) { - $short_name = $repository->getCloneName(); - $short_name = phutil_tag('em', array(), $short_name); - } - $view->addProperty(pht('Short Name'), $short_name); - - $view->invokeWillRenderEvent(); - - $view->addProperty( - pht('Status'), - $this->buildRepositoryStatus($repository)); - - $view->addProperty( - pht('Update Frequency'), - $this->buildRepositoryUpdateInterval($repository)); - - $description = $repository->getDetail('description'); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); - if (!strlen($description)) { - $description = phutil_tag('em', array(), pht('No description provided.')); - } else { - $description = new PHUIRemarkupView($viewer, $description); - } - $view->addTextContent($description); - - return $view; - } - - private function buildEncodingActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Text Encoding')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/encoding/')); - $view->addAction($edit); - - return $view; - } - - private function buildEncodingProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $encoding = $repository->getDetail('encoding'); - if (!$encoding) { - $encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)')); - } - - $view->addProperty(pht('Encoding'), $encoding); - - return $view; - } - - private function buildPolicyActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Policies')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/policy/')); - $view->addAction($edit); - - return $view; - } - - private function buildPolicyProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $repository); - - $view_parts = array(); - if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) { - $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( - $repository); - $view_parts[] = $viewer->renderHandle($space_phid); - } - $view_parts[] = $descriptions[PhabricatorPolicyCapability::CAN_VIEW]; - - $view->addProperty( - pht('Visible To'), - phutil_implode_html(" \xC2\xB7 ", $view_parts)); - - $view->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - - $pushable = $repository->isHosted() - ? $descriptions[DiffusionPushCapability::CAPABILITY] - : phutil_tag('em', array(), pht('Not a Hosted Repository')); - $view->addProperty(pht('Pushable By'), $pushable); - - return $view; - } - - private function buildBranchesActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Branches')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/branches/')); - $view->addAction($edit); - - return $view; - } - - private function buildBranchesProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $default_branch = nonempty( - $repository->getHumanReadableDetail('default-branch'), - phutil_tag('em', array(), $repository->getDefaultBranch())); - $view->addProperty(pht('Default Branch'), $default_branch); - - $track_only = nonempty( - $repository->getHumanReadableDetail('branch-filter', array()), - phutil_tag('em', array(), pht('Track All Branches'))); - $view->addProperty(pht('Track Only'), $track_only); - - $autoclose_only = nonempty( - $repository->getHumanReadableDetail('close-commits-filter', array()), - phutil_tag('em', array(), pht('Autoclose On All Branches'))); - - if ($repository->getDetail('disable-autoclose')) { - $autoclose_only = phutil_tag('em', array(), pht('Disabled')); - } - - $view->addProperty(pht('Autoclose Only'), $autoclose_only); - - return $view; - } - - private function buildSubversionActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Subversion Info')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/subversion/')); - $view->addAction($edit); - - return $view; - } - - private function buildSubversionProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $svn_uuid = nonempty( - $repository->getUUID(), - phutil_tag('em', array(), pht('Not Configured'))); - $view->addProperty(pht('Subversion UUID'), $svn_uuid); - - $svn_subpath = nonempty( - $repository->getHumanReadableDetail('svn-subpath'), - phutil_tag('em', array(), pht('Import Entire Repository'))); - $view->addProperty(pht('Import Only'), $svn_subpath); - - return $view; - } - - private function buildActionsActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Actions')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/actions/')); - $view->addAction($edit); - - return $view; - } - - private function buildActionsProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $notify = $repository->getDetail('herald-disabled') - ? pht('Off') - : pht('On'); - $notify = phutil_tag('em', array(), $notify); - $view->addProperty(pht('Publish/Notify'), $notify); - - $autoclose = $repository->getDetail('disable-autoclose') - ? pht('Off') - : pht('On'); - $autoclose = phutil_tag('em', array(), $autoclose); - $view->addProperty(pht('Autoclose'), $autoclose); - - return $view; - } - - private function buildRemoteActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Remote')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/remote/')); - $view->addAction($edit); - - return $view; - } - - private function buildRemoteProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $view->addProperty( - pht('Remote URI'), - $repository->getHumanReadableDetail('remote-uri')); - - $credential_phid = $repository->getCredentialPHID(); - if ($credential_phid) { - $view->addProperty( - pht('Credential'), - $viewer->renderHandle($credential_phid)); - } - - return $view; - } - - private function buildStorageActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Storage')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/storage/')); - $view->addAction($edit); - - return $view; - } - - private function buildStorageProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $service_phid = $repository->getAlmanacServicePHID(); - if ($service_phid) { - $v_service = $viewer->renderHandle($service_phid); - } else { - $v_service = phutil_tag( - 'em', - array(), - pht('Local')); - } - - $view->addProperty( - pht('Storage Service'), - $v_service); - - $view->addProperty( - pht('Storage Path'), - $repository->getHumanReadableDetail('local-path')); - - return $view; - } - - private function buildStagingActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Staging')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/staging/')); - $view->addAction($edit); - - return $view; - } - - private function buildStagingProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $staging_uri = $repository->getStagingURI(); - if (!$staging_uri) { - $staging_uri = phutil_tag('em', array(), pht('No Staging Area')); - } - - $view->addProperty( - pht('Staging Area'), - $staging_uri); - - return $view; - } - - private function buildAutomationActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Automation')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/automation/')); - $view->addAction($edit); - - $can_test = $repository->canPerformAutomation(); - - $test = id(new PhabricatorActionView()) - ->setIcon('fa-gamepad') - ->setName(pht('Test Configuration')) - ->setWorkflow(true) - ->setDisabled(!$can_test) - ->setHref( - $this->getRepositoryControllerURI( - $repository, - 'edit/testautomation/')); - $view->addAction($test); - - return $view; - } - - private function buildAutomationProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); - if (!$blueprint_phids) { - $blueprint_view = phutil_tag('em', array(), pht('Not Configured')); - } else { - $blueprint_view = id(new DrydockObjectAuthorizationView()) - ->setUser($viewer) - ->setObjectPHID($repository->getPHID()) - ->setBlueprintPHIDs($blueprint_phids); - } - - $view->addProperty(pht('Automation'), $blueprint_view); - - return $view; - } - - private function buildHostingActions(PhabricatorRepository $repository) { - $user = $this->getRequest()->getUser(); - - $view = id(new PhabricatorActionListView()) - ->setUser($user); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Hosting')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/hosting/')); - $view->addAction($edit); - - if ($repository->canAllowDangerousChanges()) { - if ($repository->shouldAllowDangerousChanges()) { - $changes = id(new PhabricatorActionView()) - ->setIcon('fa-shield') - ->setName(pht('Prevent Dangerous Changes')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/dangerous/')) - ->setWorkflow(true); - } else { - $changes = id(new PhabricatorActionView()) - ->setIcon('fa-bullseye') - ->setName(pht('Allow Dangerous Changes')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/dangerous/')) - ->setWorkflow(true); - } - $view->addAction($changes); - } - - return $view; - } - - private function buildHostingProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $user = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($user) - ->setActionList($actions); - - $hosting = $repository->isHosted() - ? pht('Hosted on Phabricator') - : pht('Hosted Elsewhere'); - $view->addProperty(pht('Hosting'), phutil_tag('em', array(), $hosting)); - - $view->addProperty( - pht('Serve over HTTP'), - phutil_tag( - 'em', - array(), - PhabricatorRepository::getProtocolAvailabilityName( - $repository->getServeOverHTTP()))); - - $view->addProperty( - pht('Serve over SSH'), - phutil_tag( - 'em', - array(), - PhabricatorRepository::getProtocolAvailabilityName( - $repository->getServeOverSSH()))); - - if ($repository->canAllowDangerousChanges()) { - if ($repository->shouldAllowDangerousChanges()) { - $description = pht('Allowed'); - } else { - $description = pht('Not Allowed'); - } - - $view->addProperty( - pht('Dangerous Changes'), - $description); - } - - return $view; - } - - private function buildRepositoryStatus( - PhabricatorRepository $repository) { - - $viewer = $this->getViewer(); - $is_cluster = $repository->getAlmanacServicePHID(); - - $view = new PHUIStatusListView(); - - $messages = id(new PhabricatorRepositoryStatusMessage()) - ->loadAllWhere('repositoryID = %d', $repository->getID()); - $messages = mpull($messages, null, 'getStatusType'); - - if ($repository->isTracked()) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Repository Active'))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey') - ->setTarget(pht('Repository Inactive')) - ->setNote( - pht('Activate this repository to begin or resume import.'))); - return $view; - } - - $binaries = array(); - $svnlook_check = false; - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $binaries[] = 'git'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $binaries[] = 'svn'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $binaries[] = 'hg'; - break; - } - - if ($repository->isHosted()) { - if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) { - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $binaries[] = 'git-http-backend'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $binaries[] = 'svnserve'; - $binaries[] = 'svnadmin'; - $binaries[] = 'svnlook'; - $svnlook_check = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $binaries[] = 'hg'; - break; - } - } - if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) { - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $binaries[] = 'git-receive-pack'; - $binaries[] = 'git-upload-pack'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $binaries[] = 'svnserve'; - $binaries[] = 'svnadmin'; - $binaries[] = 'svnlook'; - $svnlook_check = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $binaries[] = 'hg'; - break; - } - } - } - - $binaries = array_unique($binaries); - if (!$is_cluster) { - // We're only checking for binaries if we aren't running with a cluster - // configuration. In theory, we could check for binaries on the - // repository host machine, but we'd need to make this more complicated - // to do that. - - foreach ($binaries as $binary) { - $where = Filesystem::resolveBinary($binary); - if (!$where) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget( - pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) - ->setNote(pht( - "Unable to find this binary in the webserver's PATH. You may ". - "need to configure %s.", - $this->getEnvConfigLink()))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget( - pht('Found Binary %s', phutil_tag('tt', array(), $binary))) - ->setNote(phutil_tag('tt', array(), $where))); - } - } - - // This gets checked generically above. However, for svn commit hooks, we - // need this to be in environment.append-paths because subversion strips - // PATH. - if ($svnlook_check) { - $where = Filesystem::resolveBinary('svnlook'); - if ($where) { - $path = substr($where, 0, strlen($where) - strlen('svnlook')); - $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); - $in_path = false; - foreach ($dirs as $dir) { - if (Filesystem::isDescendant($path, $dir)) { - $in_path = true; - break; - } - } - if (!$in_path) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget( - pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) - ->setNote(pht( - 'Unable to find this binary in `%s`. '. - 'You need to configure %s and include %s.', - 'environment.append-paths', - $this->getEnvConfigLink(), - $path))); - } - } - } - } - - $doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd'); - - $daemon_instructions = pht( - 'Use %s to start daemons. See %s.', - phutil_tag('tt', array(), 'bin/phd start'), - phutil_tag( - 'a', - array( - 'href' => $doc_href, - ), - pht('Managing Daemons with phd'))); - - - $pull_daemon = id(new PhabricatorDaemonLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) - ->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon')) - ->setLimit(1) - ->execute(); - - if ($pull_daemon) { - - // TODO: In a cluster environment, we need a daemon on this repository's - // host, specifically, and we aren't checking for that right now. This - // is a reasonable proxy for things being more-or-less correctly set up, - // though. - - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Pull Daemon Running'))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Pull Daemon Not Running')) - ->setNote($daemon_instructions)); - } - - - $task_daemon = id(new PhabricatorDaemonLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) - ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) - ->setLimit(1) - ->execute(); - if ($task_daemon) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Task Daemon Running'))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Task Daemon Not Running')) - ->setNote($daemon_instructions)); - } - - - if ($is_cluster) { - // Just omit this status check for now in cluster environments. We - // could make a service call and pull it from the repository host - // eventually. - } else if ($repository->usesLocalWorkingCopy()) { - $local_parent = dirname($repository->getLocalPath()); - if (Filesystem::pathExists($local_parent)) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Storage Directory OK')) - ->setNote(phutil_tag('tt', array(), $local_parent))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('No Storage Directory')) - ->setNote( - pht( - 'Storage directory %s does not exist, or is not readable by '. - 'the webserver. Create this directory or make it readable.', - phutil_tag('tt', array(), $local_parent)))); - return $view; - } - - $local_path = $repository->getLocalPath(); - $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); - if ($message) { - switch ($message->getStatusCode()) { - case PhabricatorRepositoryStatusMessage::CODE_ERROR: - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Initialization Error')) - ->setNote($message->getParameter('message'))); - return $view; - case PhabricatorRepositoryStatusMessage::CODE_OKAY: - if (Filesystem::pathExists($local_path)) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Working Copy OK')) - ->setNote(phutil_tag('tt', array(), $local_path))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Working Copy Error')) - ->setNote( - pht( - 'Working copy %s has been deleted, or is not '. - 'readable by the webserver. Make this directory '. - 'readable. If it has been deleted, the daemons should '. - 'restore it automatically.', - phutil_tag('tt', array(), $local_path)))); - return $view; - } - break; - case PhabricatorRepositoryStatusMessage::CODE_WORKING: - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') - ->setTarget(pht('Initializing Working Copy')) - ->setNote(pht('Daemons are initializing the working copy.'))); - return $view; - default: - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Unknown Init Status')) - ->setNote($message->getStatusCode())); - return $view; - } - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') - ->setTarget(pht('No Working Copy Yet')) - ->setNote( - pht('Waiting for daemons to build a working copy.'))); - return $view; - } - } - - $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); - if ($message) { - switch ($message->getStatusCode()) { - case PhabricatorRepositoryStatusMessage::CODE_ERROR: - $message = $message->getParameter('message'); - - $suggestion = null; - if (preg_match('/Permission denied \(publickey\)./', $message)) { - $suggestion = pht( - 'Public Key Error: This error usually indicates that the '. - 'keypair you have configured does not have permission to '. - 'access the repository.'); - } - - $message = phutil_escape_html_newlines($message); - - if ($suggestion !== null) { - $message = array( - phutil_tag('strong', array(), $suggestion), - phutil_tag('br'), - phutil_tag('br'), - phutil_tag('em', array(), pht('Raw Error')), - phutil_tag('br'), - $message, - ); - } - - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Update Error')) - ->setNote($message)); - return $view; - case PhabricatorRepositoryStatusMessage::CODE_OKAY: - $ago = (PhabricatorTime::getNow() - $message->getEpoch()); - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Updates OK')) - ->setNote( - pht( - 'Last updated %s (%s ago).', - phabricator_datetime($message->getEpoch(), $viewer), - phutil_format_relative_time_detailed($ago)))); - break; - } - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') - ->setTarget(pht('Waiting For Update')) - ->setNote( - pht('Waiting for daemons to read updates.'))); - } - - if ($repository->isImporting()) { - $ratio = $repository->loadImportProgress(); - $percentage = sprintf('%.2f%%', 100 * $ratio); - - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') - ->setTarget(pht('Importing')) - ->setNote( - pht('%s Complete', $percentage))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Fully Imported'))); - } - - if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_UP, 'indigo') - ->setTarget(pht('Prioritized')) - ->setNote(pht('This repository will be updated soon!'))); - } - - return $view; - } - - private function buildRepositoryUpdateInterval( - PhabricatorRepository $repository) { - - $smart_wait = $repository->loadUpdateInterval(); - - $doc_href = PhabricatorEnv::getDoclink( - 'Diffusion User Guide: Repository Updates'); - - return array( - phutil_format_relative_time_detailed($smart_wait), - " \xC2\xB7 ", - phutil_tag( - 'a', - array( - 'href' => $doc_href, - 'target' => '_blank', - ), - pht('Learn More')), - ); - } - - - private function buildMirrorActions( - PhabricatorRepository $repository) { - - $viewer = $this->getViewer(); - - $mirror_actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $new_mirror_uri = $this->getRepositoryControllerURI( - $repository, - 'mirror/edit/'); - - $mirror_actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Add Mirror')) - ->setIcon('fa-plus') - ->setHref($new_mirror_uri) - ->setWorkflow(true)); - - return $mirror_actions; - } - - private function buildMirrorProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $mirror_properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $mirror_properties->addProperty( - '', - phutil_tag( - 'em', - array(), - pht('Automatically push changes into other remotes.'))); - - return $mirror_properties; - } - - private function buildMirrorList( - PhabricatorRepository $repository, - array $mirrors) { - assert_instances_of($mirrors, 'PhabricatorRepositoryMirror'); - - $mirror_list = id(new PHUIObjectItemListView()) - ->setNoDataString(pht('This repository has no configured mirrors.')); - - foreach ($mirrors as $mirror) { - $item = id(new PHUIObjectItemView()) - ->setHeader($mirror->getRemoteURI()); - - $edit_uri = $this->getRepositoryControllerURI( - $repository, - 'mirror/edit/'.$mirror->getID().'/'); - - $delete_uri = $this->getRepositoryControllerURI( - $repository, - 'mirror/delete/'.$mirror->getID().'/'); - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setWorkflow(true)); - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-times') - ->setHref($delete_uri) - ->setWorkflow(true)); - - $mirror_list->addItem($item); - } - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Configured Mirrors')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($mirror_list); - } - - private function buildSymbolsActions(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $edit = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Symbols')) - ->setHref( - $this->getRepositoryControllerURI($repository, 'edit/symbol/')); - $view->addAction($edit); - - return $view; - } - - private function buildSymbolsProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $languages = $repository->getSymbolLanguages(); - - if ($languages) { - $languages = implode(', ', $languages); - } else { - $languages = phutil_tag('em', array(), pht('Any')); - } - $view->addProperty(pht('Languages'), $languages); - - $sources = $repository->getSymbolSources(); - if ($sources) { - $handles = $viewer->loadHandles($sources); - $sources = $handles->renderList(); - } else { - $sources = phutil_tag('em', array(), pht('This Repository Only')); - } - $view->addProperty(pht('Use Symbols From'), $sources); - return $view; - } - - private function getEnvConfigLink() { - $config_href = '/config/edit/environment.append-paths/'; - return phutil_tag( - 'a', - array( - 'href' => $config_href, - ), - 'environment.append-paths'); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php deleted file mode 100644 index 2d158e61ea..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php +++ /dev/null @@ -1,91 +0,0 @@ -loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - - if (!$repository->supportsStaging()) { - return new Aphront404Response(); - } - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - - $v_area = $repository->getHumanReadableDetail('staging-uri'); - if ($request->isFormPost()) { - $v_area = $request->getStr('area'); - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_encoding = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; - - $xactions[] = id(clone $template) - ->setTransactionType($type_encoding) - ->setNewValue($v_area); - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Staging')); - - $title = pht('Edit Staging (%s)', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht( - "To make it easier to run integration tests and builds on code ". - "under review, you can configure a **Staging Area**. When `arc` ". - "creates a diff, it will push a copy of the changes to the ". - "configured staging area with a corresponding tag.". - "\n\n". - "IMPORTANT: This feature is new, experimental, and not supported. ". - "Use it at your own risk.")) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Staging Area URI')) - ->setName('area') - ->setValue($v_area)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Staging')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php deleted file mode 100644 index f3a492e3b3..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ /dev/null @@ -1,80 +0,0 @@ -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'); - $errors = array(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Storage')); - - $title = pht('Edit %s', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $service_phid = $repository->getAlmanacServicePHID(); - if ($service_phid) { - $handles = $this->loadViewerHandles(array($service_phid)); - $v_service = $handles[$service_phid]->renderLink(); - } else { - $v_service = phutil_tag( - 'em', - array(), - pht('Local')); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Storage Service')) - ->setValue($v_service)) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setName('local') - ->setLabel(pht('Storage Path')) - ->setValue($v_local)) - ->appendRemarkupInstructions( - pht( - "You can not adjust the local path for this repository from the ". - "web interface. To edit it, run this command:\n\n %s", - sprintf( - 'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...', - $repository->getMonogram(), - $viewer->getUsername()))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($edit_uri, pht('Done'))); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Storage')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php deleted file mode 100644 index 6fcc316135..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php +++ /dev/null @@ -1,118 +0,0 @@ -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: - throw new Exception( - pht('Git and Mercurial do not support editing SVN properties!')); - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - break; - default: - throw new Exception( - pht('Repository has unknown version control system!')); - } - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - - $v_subpath = $repository->getHumanReadableDetail('svn-subpath'); - $v_uuid = $repository->getUUID(); - - if ($request->isFormPost()) { - $v_subpath = $request->getStr('subpath'); - $v_uuid = $request->getStr('uuid'); - - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_subpath = PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH; - $type_uuid = PhabricatorRepositoryTransaction::TYPE_UUID; - - $xactions[] = id(clone $template) - ->setTransactionType($type_subpath) - ->setNewValue($v_subpath); - - $xactions[] = id(clone $template) - ->setTransactionType($type_uuid) - ->setNewValue($v_uuid); - - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } - - $content = array(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Subversion Info')); - - $title = pht('Edit Subversion Info (%s)', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($repository) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht( - "You can set the **Repository UUID**, which will help Phabriactor ". - "provide better context in some cases. You can find the UUID of a ". - "repository by running `%s`.\n\n". - "If you want to import only part of a repository, like `trunk/`, ". - "you can set a path in **Import Only**. Phabricator will ignore ". - "commits which do not affect this path.", - 'svn info')) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('uuid') - ->setLabel(pht('Repository UUID')) - ->setValue($v_uuid)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('subpath') - ->setLabel(pht('Import Only')) - ->setValue($v_subpath)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Subversion Info')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Subversion')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php index 303959d098..4cb6ffda67 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php @@ -1,7 +1,7 @@ loadDiffusionContextForEdit(); @@ -13,7 +13,9 @@ final class DiffusionRepositoryEditUpdateController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + $panel_uri = id(new DiffusionRepositoryStatusManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); if ($request->isFormPost()) { $params = array( @@ -26,7 +28,7 @@ final class DiffusionRepositoryEditUpdateController ->setUser($viewer) ->execute(); - return id(new AphrontRedirectResponse())->setURI($edit_uri); + return id(new AphrontRedirectResponse())->setURI($panel_uri); } $doc_name = 'Diffusion User Guide: Repository Updates'; @@ -58,7 +60,7 @@ final class DiffusionRepositoryEditUpdateController 'To learn more about how Phabricator updates repositories, '. 'read %s in the documentation.', $doc_link)) - ->addCancelButton($edit_uri) + ->addCancelButton($panel_uri) ->addSubmitButton(pht('Schedule Update')); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditproController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditproController.php deleted file mode 100644 index 79febc2cb7..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditproController.php +++ /dev/null @@ -1,69 +0,0 @@ -setController($this); - - $id = $request->getURIData('id'); - if (!$id) { - $this->requireApplicationCapability( - DiffusionCreateRepositoriesCapability::CAPABILITY); - - $vcs = $request->getStr('vcs'); - $vcs_types = PhabricatorRepositoryType::getRepositoryTypeMap(); - if (empty($vcs_types[$vcs])) { - return $this->buildVCSTypeResponse(); - } - - $engine - ->addContextParameter('vcs', $vcs) - ->setVersionControlSystem($vcs); - } - - return $engine->buildResponse(); - } - - private function buildVCSTypeResponse() { - $vcs_types = PhabricatorRepositoryType::getRepositoryTypeMap(); - - $request = $this->getRequest(); - $viewer = $this->getViewer(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Create Repository')); - $crumbs->setBorder(true); - - $title = pht('Choose Repository Type'); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Create Repository')) - ->setHeaderIcon('fa-plus-square'); - - $layout = id(new AphrontMultiColumnView()) - ->setFluidLayout(true); - - $create_uri = $request->getRequestURI(); - - foreach ($vcs_types as $vcs_key => $vcs_type) { - $action = id(new PHUIActionPanelView()) - ->setIcon(idx($vcs_type, 'icon')) - ->setHeader(idx($vcs_type, 'create.header')) - ->setHref($create_uri->alter('vcs', $vcs_key)) - ->setSubheader(idx($vcs_type, 'create.subheader')); - - $layout->addColumn($action); - } - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($layout); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryListController.php b/src/applications/diffusion/controller/DiffusionRepositoryListController.php index 5897d2f162..eebe0bf91f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -15,16 +15,9 @@ final class DiffusionRepositoryListController extends DiffusionController { protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - DiffusionCreateRepositoriesCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Repository')) - ->setHref($this->getApplicationURI('new/')) - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create) - ->setIcon('fa-plus-square')); + id(new DiffusionRepositoryEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php index aa34f8f909..8b0740c540 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php @@ -1,129 +1,25 @@ navigation) { - return $this->navigation->getMenu(); + if ($this->hasDiffusionRequest()) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $crumbs->addTextCrumb( + $repository->getDisplayName(), + $repository->getURI()); + + $crumbs->addTextCrumb( + pht('Manage'), + $repository->getPathURI('manage/')); } - return parent::buildApplicationMenu(); + + return $crumbs; } - - public function handleRequest(AphrontRequest $request) { - $response = $this->loadDiffusionContext(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $panels = DiffusionRepositoryManagementPanel::getAllPanels(); - - foreach ($panels as $panel) { - $panel - ->setViewer($viewer) - ->setRepository($repository) - ->setController($this); - } - - $selected = $request->getURIData('panel'); - if (!strlen($selected)) { - $selected = head_key($panels); - } - - if (empty($panels[$selected])) { - return new Aphront404Response(); - } - - $nav = $this->renderSideNav($repository, $panels, $selected); - $this->navigation = $nav; - - $panel = $panels[$selected]; - - $content = $panel->buildManagementPanelContent(); - - $title = array( - $panel->getManagementPanelLabel(), - $repository->getDisplayName(), - ); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - $repository->getDisplayName(), - $repository->getURI()); - $crumbs->addTextCrumb( - pht('Manage'), - $repository->getPathURI('manage/')); - $crumbs->addTextCrumb($panel->getManagementPanelLabel()); - - $header_text = pht( - '%s: %s', - $repository->getDisplayName(), - $panel->getManagementPanelLabel()); - - $header = id(new PHUIHeaderView()) - ->setHeader($header_text) - ->setHeaderIcon('fa-pencil'); - if ($repository->isTracked()) { - $header->setStatus('fa-check', 'bluegrey', pht('Active')); - } else { - $header->setStatus('fa-ban', 'dark', pht('Inactive')); - } - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setNavigation($nav) - ->setMainColumn($content); - - $curtain = $panel->buildManagementPanelCurtain(); - if ($curtain) { - $view->setCurtain($curtain); - } - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function renderSideNav( - PhabricatorRepository $repository, - array $panels, - $selected) { - - $base_uri = $repository->getPathURI('manage/'); - $base_uri = new PhutilURI($base_uri); - - $nav = id(new AphrontSideNavFilterView()) - ->setBaseURI($base_uri); - - foreach ($panels as $panel) { - $nav->addFilter( - $panel->getManagementPanelKey(), - $panel->getManagementPanelLabel()); - } - - $nav->selectFilter($selected); - - return $nav; - } - - public function newTimeline(PhabricatorRepository $repository) { - $timeline = $this->buildTransactionTimeline( - $repository, - new PhabricatorRepositoryTransactionQuery()); - $timeline->setShouldTerminate(true); - - return $timeline; - } - - } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php new file mode 100644 index 0000000000..22c03145e0 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php @@ -0,0 +1,146 @@ +navigation) { + return $this->navigation->getMenu(); + } + return parent::buildApplicationMenu(); + } + + + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $panels = DiffusionRepositoryManagementPanel::getAllPanels(); + + foreach ($panels as $key => $panel) { + $panel + ->setViewer($viewer) + ->setRepository($repository) + ->setController($this); + + if (!$panel->shouldEnableForRepository($repository)) { + unset($panels[$key]); + continue; + } + } + + $selected = $request->getURIData('panel'); + if (!strlen($selected)) { + $selected = head_key($panels); + } + + if (empty($panels[$selected])) { + return new Aphront404Response(); + } + + $nav = $this->renderSideNav($repository, $panels, $selected); + $this->navigation = $nav; + + $panel = $panels[$selected]; + + $content = $panel->buildManagementPanelContent(); + + $title = array( + $panel->getManagementPanelLabel(), + $repository->getDisplayName(), + ); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($panel->getManagementPanelLabel()); + $crumbs->setBorder(true); + + $header_text = pht( + '%s: %s', + $repository->getDisplayName(), + $panel->getManagementPanelLabel()); + + $header = id(new PHUIHeaderView()) + ->setHeader($header_text) + ->setHeaderIcon('fa-pencil'); + if ($repository->isTracked()) { + $header->setStatus('fa-check', 'bluegrey', pht('Active')); + } else { + $header->setStatus('fa-ban', 'dark', pht('Inactive')); + } + + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View Repository')) + ->setHref($repository->getURI()) + ->setIcon('fa-code')); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setNavigation($nav) + ->setMainColumn($content); + + $curtain = $panel->buildManagementPanelCurtain(); + if ($curtain) { + $view->setCurtain($curtain); + } + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function renderSideNav( + PhabricatorRepository $repository, + array $panels, + $selected) { + + $base_uri = $repository->getPathURI('manage/'); + $base_uri = new PhutilURI($base_uri); + + $nav = id(new AphrontSideNavFilterView()) + ->setBaseURI($base_uri); + + foreach ($panels as $panel) { + $key = $panel->getManagementPanelKey(); + $label = $panel->getManagementPanelLabel(); + $icon = $panel->getManagementPanelIcon(); + $href = $panel->getPanelNavigationURI(); + + $item = id(new PHUIListItemView()) + ->setKey($key) + ->setName($label) + ->setType(PHUIListItemView::TYPE_LINK) + ->setHref($href) + ->setIcon($icon); + + $nav->addMenuItem($item); + } + + $nav->selectFilter($selected); + + return $nav; + } + + public function newTimeline(PhabricatorRepository $repository) { + $timeline = $this->buildTransactionTimeline( + $repository, + new PhabricatorRepositoryTransactionQuery()); + $timeline->setShouldTerminate(true); + + return $timeline; + } + + +} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryNewController.php b/src/applications/diffusion/controller/DiffusionRepositoryNewController.php deleted file mode 100644 index 560d896aff..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryNewController.php +++ /dev/null @@ -1,79 +0,0 @@ -getViewer(); - - $this->requireApplicationCapability( - DiffusionCreateRepositoriesCapability::CAPABILITY); - - if ($request->isFormPost()) { - if ($request->getStr('type')) { - switch ($request->getStr('type')) { - case 'create': - $uri = $this->getApplicationURI('create/'); - break; - case 'import': - default: - $uri = $this->getApplicationURI('import/'); - break; - } - - return id(new AphrontRedirectResponse())->setURI($uri); - } - } - - $doc_href = PhabricatorEnv::getDoclink( - 'Diffusion User Guide: Repository Hosting'); - - $doc_link = phutil_tag( - 'a', - array( - 'href' => $doc_href, - 'target' => '_blank', - ), - pht('Diffusion User Guide: Repository Hosting')); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormRadioButtonControl()) - ->setName('type') - ->addButton( - 'create', - pht('Create a New Hosted Repository'), - array( - pht( - 'Create a new, empty repository which Phabricator will host. '. - 'For instructions on configuring repository hosting, see %s.', - $doc_link), - )) - ->addButton( - 'import', - pht('Import an Existing External Repository'), - pht( - "Import a repository hosted somewhere else, like GitHub, ". - "Bitbucket, or your organization's existing servers. ". - "Phabricator will read changes from the repository but will ". - "not host or manage it. The authoritative master version of ". - "the repository will stay where it is now."))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($this->getApplicationURI())); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('New Repository')); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Create or Import Repository')) - ->setForm($form); - - 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 deleted file mode 100644 index 06ce23032a..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php +++ /dev/null @@ -1,120 +0,0 @@ -loadDiffusionContextForEdit(); - if ($response) { - return $response; - } - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - - $v_sources = $repository->getSymbolSources(); - $v_languages = $repository->getSymbolLanguages(); - if ($v_languages) { - $v_languages = implode(', ', $v_languages); - } - $errors = array(); - - if ($request->isFormPost()) { - $v_sources = $request->getArr('sources'); - $v_languages = $request->getStrList('languages'); - $v_languages = array_map('phutil_utf8_strtolower', $v_languages); - - if (!$errors) { - $xactions = array(); - $template = id(new PhabricatorRepositoryTransaction()); - - $type_sources = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; - $type_lang = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; - - $xactions[] = id(clone $template) - ->setTransactionType($type_sources) - ->setNewValue($v_sources); - - $xactions[] = id(clone $template) - ->setTransactionType($type_lang) - ->setNewValue($v_languages); - - try { - id(new PhabricatorRepositoryEditor()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->applyTransactions($repository, $xactions); - - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } catch (Exception $ex) { - $errors[] = $ex->getMessage(); - } - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Symbols')); - - $title = pht('Edit Symbols (%s)', $repository->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions($this->getInstructions()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('languages') - ->setLabel(pht('Indexed Languages')) - ->setCaption(pht( - 'File extensions, separate with commas, for example: php, py. '. - 'Leave blank for "any".')) - ->setValue($v_languages)) - - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setName('sources') - ->setLabel(pht('Uses Symbols From')) - ->setDatasource(new DiffusionRepositoryDatasource()) - ->setValue($v_sources)) - - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton($edit_uri)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Symbols')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form) - ->setFormErrors($errors); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function getInstructions() { - return pht(<<loadDiffusionContextForEdit(); @@ -13,7 +13,9 @@ final class DiffusionRepositoryTestAutomationController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + $panel_uri = id(new DiffusionRepositoryAutomationManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); if (!$repository->canPerformAutomation()) { return $this->newDialog() @@ -23,7 +25,7 @@ final class DiffusionRepositoryTestAutomationController 'You can not run a configuration test for this repository '. 'because you have not configured repository automation yet. '. 'Configure it first, then test the configuration.')) - ->addCancelButton($edit_uri); + ->addCancelButton($panel_uri); } if ($request->isFormPost()) { @@ -63,7 +65,7 @@ final class DiffusionRepositoryTestAutomationController 'If you run into write failures despite passing this test, '. 'it suggests that your setup is nearly correct but authentication '. 'is probably not fully configured.')) - ->addCancelButton($edit_uri) + ->addCancelButton($panel_uri) ->addSubmitButton(pht('Start Test')); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php b/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php new file mode 100644 index 0000000000..96b97673ce --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php @@ -0,0 +1,159 @@ +loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $id = $request->getURIData('id'); + $uri = id(new PhabricatorRepositoryURIQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->withRepositories(array($repository)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$uri) { + return new Aphront404Response(); + } + + $is_builtin = $uri->isBuiltin(); + $has_credential = (bool)$uri->getCredentialPHID(); + $view_uri = $uri->getViewURI(); + $is_remove = ($request->getURIData('action') == 'remove'); + + if ($is_builtin) { + return $this->newDialog() + ->setTitle(pht('Builtin URIs Do Not Use Credentials')) + ->appendParagraph( + pht( + 'You can not set a credential for builtin URIs which Phabricator '. + 'hosts and serves. Phabricator does not fetch from these URIs or '. + 'push to these URIs, and does not need credentials to '. + 'authenticate any activity against them.')) + ->addCancelButton($view_uri); + } + + if ($request->isFormPost()) { + $xactions = array(); + + if ($is_remove) { + $new_phid = null; + } else { + $new_phid = $request->getStr('credentialPHID'); + } + + $type_credential = PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL; + + $xactions[] = id(new PhabricatorRepositoryURITransaction()) + ->setTransactionType($type_credential) + ->setNewValue($new_phid); + + $editor = id(new DiffusionURIEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request) + ->applyTransactions($uri, $xactions); + + return id(new AphrontRedirectResponse())->setURI($view_uri); + } + + $command_engine = $uri->newCommandEngine(); + $is_supported = $command_engine->isCredentialSupported(); + + $body = null; + $form = null; + $width = AphrontDialogView::WIDTH_DEFAULT; + if ($is_remove) { + if ($has_credential) { + $title = pht('Remove Credential'); + $body = pht( + 'This credential will no longer be used to authenticate activity '. + 'against this URI.'); + $button = pht('Remove Credential'); + } else { + $title = pht('No Credential'); + $body = pht( + 'This URI does not have an associated credential.'); + $button = null; + } + } else if (!$is_supported) { + $title = pht('Unauthenticated Protocol'); + $body = pht( + 'The protocol for this URI ("%s") does not use authentication, so '. + 'you can not provide a credential.', + $command_engine->getDisplayProtocol()); + $button = null; + } else { + $effective_uri = $uri->getEffectiveURI(); + + $label = $command_engine->getPassphraseCredentialLabel(); + $credential_type = $command_engine->getPassphraseDefaultCredentialType(); + + $provides_type = $command_engine->getPassphraseProvidesCredentialType(); + $options = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withIsDestroyed(false) + ->withProvidesTypes(array($provides_type)) + ->execute(); + + $control = id(new PassphraseCredentialControl()) + ->setName('credentialPHID') + ->setLabel($label) + ->setValue($uri->getCredentialPHID()) + ->setCredentialType($credential_type) + ->setOptions($options); + + $default_user = $effective_uri->getUser(); + if (strlen($default_user)) { + $control->setDefaultUsername($default_user); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl($control); + + if ($has_credential) { + $title = pht('Update Credential'); + $button = pht('Update Credential'); + } else { + $title = pht('Set Credential'); + $button = pht('Set Credential'); + } + + $width = AphrontDialogView::WIDTH_FORM; + } + + $dialog = $this->newDialog() + ->setWidth($width) + ->setTitle($title) + ->addCancelButton($view_uri); + + if ($body) { + $dialog->appendParagraph($body); + } + + if ($form) { + $dialog->appendForm($form); + } + + if ($button) { + $dialog->addSubmitButton($button); + } + + return $dialog; + } + +} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURIDisableController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIDisableController.php index b846b3f4e2..466a0fd388 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryURIDisableController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryURIDisableController.php @@ -31,6 +31,16 @@ final class DiffusionRepositoryURIDisableController $is_disabled = $uri->getIsDisabled(); $view_uri = $uri->getViewURI(); + if ($uri->isBuiltin()) { + return $this->newDialog() + ->setTitle(pht('Builtin URI')) + ->appendParagraph( + pht( + 'You can not manually disable builtin URIs. To hide a builtin '. + 'URI, configure its "Display" behavior instead.')) + ->addCancelButton($view_uri); + } + if ($request->isFormPost()) { $xactions = array(); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php index 8cd143c5a1..61335d20d0 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php @@ -82,6 +82,7 @@ final class DiffusionRepositoryURIViewController private function buildCurtain(PhabricatorRepositoryURI $uri) { $viewer = $this->getViewer(); + $repository = $uri->getRepository(); $id = $uri->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -89,7 +90,6 @@ final class DiffusionRepositoryURIViewController $uri, PhabricatorPolicyCapability::CAN_EDIT); - $curtain = $this->newCurtainView($uri); $edit_uri = $uri->getEditURI(); @@ -102,6 +102,43 @@ final class DiffusionRepositoryURIViewController ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); + $credential_uri = $repository->getPathURI("uri/credential/{$id}/edit/"); + $remove_uri = $repository->getPathURI("uri/credential/{$id}/remove/"); + $has_credential = (bool)$uri->getCredentialPHID(); + + if ($uri->isBuiltin()) { + $can_credential = false; + } else if (!$uri->newCommandEngine()->isCredentialSupported()) { + $can_credential = false; + } else { + $can_credential = true; + } + + $can_update = ($can_edit && $can_credential); + $can_remove = ($can_edit && $has_credential); + + if ($has_credential) { + $credential_name = pht('Update Credential'); + } else { + $credential_name = pht('Set Credential'); + } + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-key') + ->setName($credential_name) + ->setHref($credential_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-times') + ->setName(pht('Remove Credential')) + ->setHref($remove_uri) + ->setWorkflow(true) + ->setDisabled(!$can_remove)); + if ($uri->getIsDisabled()) { $disable_name = pht('Enable URI'); $disable_icon = 'fa-check'; @@ -110,7 +147,9 @@ final class DiffusionRepositoryURIViewController $disable_icon = 'fa-ban'; } - $disable_uri = $uri->getRepository()->getPathURI("uri/disable/{$id}/"); + $can_disable = ($can_edit && !$uri->isBuiltin()); + + $disable_uri = $repository->getPathURI("uri/disable/{$id}/"); $curtain->addAction( id(new PhabricatorActionView()) @@ -118,7 +157,7 @@ final class DiffusionRepositoryURIViewController ->setName($disable_name) ->setHref($disable_uri) ->setWorkflow(true) - ->setDisabled(!$can_edit)); + ->setDisabled(!$can_disable)); return $curtain; } @@ -130,7 +169,84 @@ final class DiffusionRepositoryURIViewController ->setUser($viewer); $properties->addProperty(pht('URI'), $uri->getDisplayURI()); - $properties->addProperty(pht('Credential'), 'TODO'); + + $credential_phid = $uri->getCredentialPHID(); + $command_engine = $uri->newCommandEngine(); + $is_optional = $command_engine->isCredentialOptional(); + $is_supported = $command_engine->isCredentialSupported(); + $is_builtin = $uri->isBuiltin(); + + if ($is_builtin) { + $credential_icon = 'fa-circle-o'; + $credential_color = 'grey'; + $credential_label = pht('Builtin'); + $credential_note = pht('Builtin URIs do not use credentials.'); + } else if (!$is_supported) { + $credential_icon = 'fa-circle-o'; + $credential_color = 'grey'; + $credential_label = pht('Not Supported'); + $credential_note = pht('This protocol does not support authentication.'); + } else if (!$credential_phid) { + if ($is_optional) { + $credential_icon = 'fa-circle-o'; + $credential_color = 'green'; + $credential_label = pht('No Credential'); + $credential_note = pht('Configured for anonymous access.'); + } else { + $credential_icon = 'fa-times'; + $credential_color = 'red'; + $credential_label = pht('Required'); + $credential_note = pht('Credential required but not configured.'); + } + } else { + // Don't raise a policy exception if we can't see the credential. + $credentials = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withPHIDs(array($credential_phid)) + ->execute(); + $credential = head($credentials); + + if (!$credential) { + $handles = $viewer->loadHandles(array($credential_phid)); + $handle = $handles[$credential_phid]; + if ($handle->getPolicyFiltered()) { + $credential_icon = 'fa-lock'; + $credential_color = 'grey'; + $credential_label = pht('Restricted'); + $credential_note = pht( + 'You do not have permission to view the configured '. + 'credential.'); + } else { + $credential_icon = 'fa-times'; + $credential_color = 'red'; + $credential_label = pht('Invalid'); + $credential_note = pht('Configured credential is invalid.'); + } + } else { + $provides = $credential->getProvidesType(); + $needs = $command_engine->getPassphraseProvidesCredentialType(); + if ($provides != $needs) { + $credential_icon = 'fa-times'; + $credential_color = 'red'; + $credential_label = pht('Wrong Type'); + } else { + $credential_icon = 'fa-check'; + $credential_color = 'green'; + $credential_label = $command_engine->getPassphraseCredentialLabel(); + } + $credential_note = $viewer->renderHandle($credential_phid); + } + } + + $credential_item = id(new PHUIStatusItemView()) + ->setIcon($credential_icon, $credential_color) + ->setTarget(phutil_tag('strong', array(), $credential_label)) + ->setNote($credential_note); + + $credential_view = id(new PHUIStatusListView()) + ->addItem($credential_item); + + $properties->addProperty(pht('Credential'), $credential_view); $io_type = $uri->getEffectiveIOType(); diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index f1703655fa..69b8c64b17 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -219,6 +219,7 @@ final class DiffusionServeController extends DiffusionController { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withIdentifiers(array($identifier)) + ->needURIs(true) ->executeOne(); if (!$repository) { return new PhabricatorVCSResponse( @@ -266,22 +267,32 @@ final class DiffusionServeController extends DiffusionController { // token from SSH. If they're using HTTP username + password auth, they // have to obey the normal HTTP rules. } else { - switch ($repository->getServeOverHTTP()) { - case PhabricatorRepository::SERVE_READONLY: - if ($is_push) { - return new PhabricatorVCSResponse( - 403, - pht('This repository is read-only over HTTP.')); - } - break; - case PhabricatorRepository::SERVE_READWRITE: - // We'll check for push capability below. - break; - case PhabricatorRepository::SERVE_OFF: - default: + // For now, we don't distinguish between HTTP and HTTPS-originated + // requests that are proxied within the cluster, so the user can connect + // with HTTPS but we may be on HTTP by the time we reach this part of + // the code. Allow things to move forward as long as either protocol + // can be served. + $proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; + $proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; + + $can_read = + $repository->canServeProtocol($proto_https, false) || + $repository->canServeProtocol($proto_http, false); + if (!$can_read) { + return new PhabricatorVCSResponse( + 403, + pht('This repository is not available over HTTP.')); + } + + if ($is_push) { + $can_write = + $repository->canServeProtocol($proto_https, true) || + $repository->canServeProtocol($proto_http, true); + if (!$can_write) { return new PhabricatorVCSResponse( 403, - pht('This repository is not available over HTTP.')); + pht('This repository is read-only over HTTP.')); + } } } diff --git a/src/applications/diffusion/data/DiffusionLocalRepositoryFilter.php b/src/applications/diffusion/data/DiffusionLocalRepositoryFilter.php new file mode 100644 index 0000000000..b443352206 --- /dev/null +++ b/src/applications/diffusion/data/DiffusionLocalRepositoryFilter.php @@ -0,0 +1,184 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setDevice(AlmanacDevice $device = null) { + $this->device = $device; + return $this; + } + + public function getDevice() { + return $this->device; + } + + public function setRepositories(array $repositories) { + $this->repositories = $repositories; + return $this; + } + + public function getRepositories() { + return $this->repositories; + } + + public function setRejectionReasons($rejection_reasons) { + $this->rejectionReasons = $rejection_reasons; + return $this; + } + + public function getRejectionReasons() { + return $this->rejectionReasons; + } + + public function execute() { + $repositories = $this->getRepositories(); + $device = $this->getDevice(); + $viewer = $this->getViewer(); + + $reasons = array(); + + $service_phids = array(); + foreach ($repositories as $key => $repository) { + $service_phid = $repository->getAlmanacServicePHID(); + + // If the repository is bound to a service but this host is not a + // recognized device, or vice versa, don't pull the repository unless + // we're sure it's safe because the repository has no local working copy + // or the working copy already exists on disk. + $is_cluster_repo = (bool)$service_phid; + $is_cluster_device = (bool)$device; + if ($is_cluster_repo != $is_cluster_device) { + $has_working_copy = $repository->hasLocalWorkingCopy(); + if ($is_cluster_device) { + if (!$has_working_copy) { + $reasons[$key] = pht( + 'Repository "%s" is not a cluster repository, but the current '. + 'host is a cluster device ("%s") and updating this repository '. + 'would create a new local working copy. This is dangerous, so '. + 'the repository will not be updated on this host.', + $repository->getDisplayName(), + $device->getName()); + unset($repositories[$key]); + continue; + } + } else { + $reasons[$key] = pht( + 'Repository "%s" is a cluster repository, but the current host '. + 'is not a cluster device (it has no device ID), so the '. + 'repository will not be updated on this host.', + $repository->getDisplayName()); + unset($repositories[$key]); + continue; + } + } + + if ($service_phid) { + $service_phids[] = $service_phid; + } + } + + if (!$device) { + $this->rejectionReasons = $reasons; + return $repositories; + } + + $device_phid = $device->getPHID(); + + if ($service_phids) { + // We could include `withDevicePHIDs()` here to pull a smaller result + // set, but we can provide more helpful diagnostic messages below if + // we fetch a little more data. + $services = id(new AlmanacServiceQuery()) + ->setViewer($viewer) + ->withPHIDs($service_phids) + ->withServiceTypes( + array( + AlmanacClusterRepositoryServiceType::SERVICETYPE, + )) + ->needBindings(true) + ->execute(); + $services = mpull($services, null, 'getPHID'); + } else { + $services = array(); + } + + foreach ($repositories as $key => $repository) { + $service_phid = $repository->getAlmanacServicePHID(); + + if (!$service_phid) { + continue; + } + + $service = idx($services, $service_phid); + if (!$service) { + $reasons[$key] = pht( + 'Repository "%s" is on cluster service "%s", but that service '. + 'could not be loaded, so the repository will not be updated on '. + 'this host.', + $repository->getDisplayName(), + $service_phid); + unset($repositories[$key]); + continue; + } + + $bindings = $service->getBindings(); + $bindings = mgroup($bindings, 'getDevicePHID'); + $bindings = idx($bindings, $device_phid); + if (!$bindings) { + $reasons[$key] = pht( + 'Repository "%s" is on cluster service "%s", but that service is '. + 'not bound to this device ("%s"), so the repository will not be '. + 'updated on this host.', + $repository->getDisplayName(), + $service->getName(), + $device->getName()); + unset($repositories[$key]); + continue; + } + + $all_disabled = true; + foreach ($bindings as $binding) { + if (!$binding->getIsDisabled()) { + $all_disabled = false; + break; + } + } + + if ($all_disabled) { + $reasons[$key] = pht( + 'Repository "%s" is on cluster service "%s", but the binding '. + 'between that service and this device ("%s") is disabled, so it '. + 'can not be updated on this host.', + $repository->getDisplayName(), + $service->getName(), + $device->getName()); + unset($repositories[$key]); + continue; + } + + // We have a valid service that is actively bound to the current host + // device, so we're good to go. + } + + $this->rejectionReasons = $reasons; + return $repositories; + } + +} diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index 1ebbabe2ed..933cdbb597 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -40,11 +40,51 @@ final class DiffusionRepositoryEditEngine $viewer = $this->getViewer(); $repository = PhabricatorRepository::initializeNewRepository($viewer); + $repository->setDetail('newly-initialized', true); + $vcs = $this->getVersionControlSystem(); if ($vcs) { $repository->setVersionControlSystem($vcs); } + // Pick a random open service to allocate this repository on, if any exist. + // If there are no services, we aren't in cluster mode and will allocate + // locally. If there are services but none permit allocations, we fail. + + // Eventually we can make this more flexible, but this rule is a reasonable + // starting point as we begin to deploy cluster services. + + $services = id(new AlmanacServiceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withServiceTypes( + array( + AlmanacClusterRepositoryServiceType::SERVICETYPE, + )) + ->needProperties(true) + ->execute(); + if ($services) { + // Filter out services which do not permit new allocations. + foreach ($services as $key => $possible_service) { + if ($possible_service->getAlmanacPropertyValue('closed')) { + unset($services[$key]); + } + } + + if (!$services) { + throw new Exception( + pht( + 'This install is configured in cluster mode, but all available '. + 'repository cluster services are closed to new allocations. '. + 'At least one service must be open to allow new allocations to '. + 'take place.')); + } + + shuffle($services); + $service = head($services); + + $repository->setAlmanacServicePHID($service->getPHID()); + } + return $repository; } @@ -85,6 +125,77 @@ final class DiffusionRepositoryEditEngine DiffusionCreateRepositoriesCapability::CAPABILITY); } + protected function newPages($object) { + $panels = DiffusionRepositoryManagementPanel::getAllPanels(); + + $pages = array(); + $uris = array(); + foreach ($panels as $panel_key => $panel) { + $panel->setRepository($object); + + $uris[$panel_key] = $panel->getPanelURI(); + + $page = $panel->newEditEnginePage(); + if (!$page) { + continue; + } + $pages[] = $page; + } + + $basics_key = DiffusionRepositoryBasicsManagementPanel::PANELKEY; + $basics_uri = $uris[$basics_key]; + + $more_pages = array( + id(new PhabricatorEditPage()) + ->setKey('encoding') + ->setLabel(pht('Text Encoding')) + ->setViewURI($basics_uri) + ->setFieldKeys( + array( + 'encoding', + )), + id(new PhabricatorEditPage()) + ->setKey('extensions') + ->setLabel(pht('Extensions')) + ->setIsDefault(true), + ); + + foreach ($more_pages as $page) { + $pages[] = $page; + } + + return $pages; + } + + protected function willConfigureFields($object, array $fields) { + // Change the default field order so related fields are adjacent. + $after = array( + 'policy.edit' => array('policy.push'), + ); + + $result = array(); + foreach ($fields as $key => $value) { + $result[$key] = $value; + + if (!isset($after[$key])) { + continue; + } + + foreach ($after[$key] as $next_key) { + if (!isset($fields[$next_key])) { + continue; + } + + unset($result[$next_key]); + $result[$next_key] = $fields[$next_key]; + unset($fields[$next_key]); + } + } + + return $result; + } + + protected function buildCustomEditFields($object) { $viewer = $this->getViewer(); @@ -99,6 +210,27 @@ final class DiffusionRepositoryEditEngine $autoclose_value = $object->getDetail('close-commits-filter', array()); $autoclose_value = array_keys($autoclose_value); + $automation_instructions = pht( + "Configure **Repository Automation** to allow Phabricator to ". + "write to this repository.". + "\n\n". + "IMPORTANT: This feature is new, experimental, and not supported. ". + "Use it at your own risk."); + + $staging_instructions = pht( + "To make it easier to run integration tests and builds on code ". + "under review, you can configure a **Staging Area**. When `arc` ". + "creates a diff, it will push a copy of the changes to the ". + "configured staging area with a corresponding tag.". + "\n\n". + "IMPORTANT: This feature is new, experimental, and not supported. ". + "Use it at your own risk."); + + $subpath_instructions = pht( + 'If you want to import only part of a repository, like `trunk/`, '. + 'you can set a path in **Import Only**. Phabricator will ignore '. + 'commits which do not affect this path.'); + return array( id(new PhabricatorSelectEditField()) ->setKey('vcs') @@ -211,6 +343,17 @@ final class DiffusionRepositoryEditEngine ->setConduitDescription(pht('Set the autoclose branches.')) ->setConduitTypeDescription(pht('New default tracked branchs.')) ->setValue($autoclose_value), + id(new PhabricatorTextEditField()) + ->setKey('importOnly') + ->setLabel(pht('Import Only')) + ->setTransactionType( + PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH) + ->setIsCopyable(true) + ->setDescription(pht('Subpath to selectively import.')) + ->setConduitDescription(pht('Set the subpath to import.')) + ->setConduitTypeDescription(pht('New subpath to import.')) + ->setValue($object->getDetail('svn-subpath')) + ->setControlInstructions($subpath_instructions), id(new PhabricatorTextEditField()) ->setKey('stagingAreaURI') ->setLabel(pht('Staging Area URI')) @@ -220,7 +363,8 @@ final class DiffusionRepositoryEditEngine ->setDescription(pht('Staging area URI.')) ->setConduitDescription(pht('Set the staging area URI.')) ->setConduitTypeDescription(pht('New staging area URI.')) - ->setValue($object->getStagingURI()), + ->setValue($object->getStagingURI()) + ->setControlInstructions($staging_instructions), id(new PhabricatorDatasourceEditField()) ->setKey('automationBlueprintPHIDs') ->setLabel(pht('Use Blueprints')) @@ -231,7 +375,8 @@ final class DiffusionRepositoryEditEngine ->setDescription(pht('Automation blueprints.')) ->setConduitDescription(pht('Change automation blueprints.')) ->setConduitTypeDescription(pht('New blueprint PHIDs.')) - ->setValue($object->getAutomationBlueprintPHIDs()), + ->setValue($object->getAutomationBlueprintPHIDs()) + ->setControlInstructions($automation_instructions), id(new PhabricatorStringListEditField()) ->setKey('symbolLanguages') ->setLabel(pht('Languages')) diff --git a/src/applications/diffusion/editor/DiffusionURIEditEngine.php b/src/applications/diffusion/editor/DiffusionURIEditEngine.php index 6d351219dc..fdc91cff5f 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionURIEditEngine.php @@ -83,6 +83,67 @@ final class DiffusionURIEditEngine protected function buildCustomEditFields($object) { $viewer = $this->getViewer(); + $uri_instructions = null; + if ($object->isBuiltin()) { + $is_builtin = true; + $uri_value = (string)$object->getDisplayURI(); + + switch ($object->getBuiltinProtocol()) { + case PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH: + $uri_instructions = pht( + " - Configure [[ %s | %s ]] to change the SSH username.\n". + " - Configure [[ %s | %s ]] to change the SSH host.\n". + " - Configure [[ %s | %s ]] to change the SSH port.", + '/config/edit/diffusion.ssh-user/', + 'diffusion.ssh-user', + '/config/edit/diffusion.ssh-host/', + 'diffusion.ssh-host', + '/config/edit/diffusion.ssh-port/', + 'diffusion.ssh-port'); + break; + } + } else { + $is_builtin = false; + $uri_value = $object->getURI(); + + if ($object->getRepositoryPHID()) { + $repository = $object->getRepository(); + if ($repository->isGit()) { + $uri_instructions = pht( + "Provide the URI of a Git repository. It should usually look ". + "like one of these examples:\n". + "\n". + "| Example Git URIs\n". + "| -----------------------\n". + "| `git@github.com:example/example.git`\n". + "| `ssh://user@host.com/git/example.git`\n". + "| `https://example.com/repository.git`"); + } else if ($repository->isHg()) { + $uri_instructions = pht( + "Provide the URI of a Mercurial repository. It should usually ". + "look like one of these examples:\n". + "\n". + "| Example Mercurial URIs\n". + "|-----------------------\n". + "| `ssh://hg@bitbucket.org/example/repository`\n". + "| `https://bitbucket.org/example/repository`"); + } else if ($repository->isSVN()) { + $uri_instructions = pht( + "Provide the **Repository Root** of a Subversion repository. ". + "You can identify this by running `svn info` in a working ". + "copy. It should usually look like one of these examples:\n". + "\n". + "| Example Subversion URIs\n". + "|-----------------------\n". + "| `http://svn.example.org/svnroot/`\n". + "| `svn+ssh://svn.example.com/svnroot/`\n". + "| `svn://svn.example.net/svnroot/`\n\n". + "You **MUST** specify the root of the repository, not a ". + "subdirectory."); + } + } + } + return array( id(new PhabricatorHandlesEditField()) ->setKey('repository') @@ -104,12 +165,14 @@ final class DiffusionURIEditEngine id(new PhabricatorTextEditField()) ->setKey('uri') ->setLabel(pht('URI')) - ->setIsRequired(true) ->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_URI) ->setDescription(pht('The repository URI.')) ->setConduitDescription(pht('Change the repository URI.')) ->setConduitTypeDescription(pht('New repository URI.')) - ->setValue($object->getURI()), + ->setIsRequired(!$is_builtin) + ->setIsLocked($is_builtin) + ->setValue($uri_value) + ->setControlInstructions($uri_instructions), id(new PhabricatorSelectEditField()) ->setKey('io') ->setLabel(pht('I/O Type')) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 3d2164ad0b..020275a367 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -3,6 +3,9 @@ final class DiffusionURIEditor extends PhabricatorApplicationTransactionEditor { + private $repository; + private $repositoryPHID; + public function getEditorApplicationClass() { return 'PhabricatorDiffusionApplication'; } @@ -70,7 +73,42 @@ final class DiffusionURIEditor switch ($xaction->getTransactionType()) { case PhabricatorRepositoryURITransaction::TYPE_URI: + if (!$this->getIsNewObject()) { + $old_uri = $object->getEffectiveURI(); + } else { + $old_uri = null; + } + $object->setURI($xaction->getNewValue()); + + // If we've changed the domain or protocol of the URI, remove the + // current credential. This improves behavior in several cases: + + // If a user switches between protocols with different credential + // types, like HTTP and SSH, the old credential won't be valid anyway. + // It's cleaner to remove it than leave a bad credential in place. + + // If a user switches hosts, the old credential is probably not + // correct (and potentially confusing/misleading). Removing it forces + // users to double check that they have the correct credentials. + + // If an attacker can't see a symmetric credential like a username and + // password, they could still potentially capture it by changing the + // host for a URI that uses it to `evil.com`, a server they control, + // then observing the requests. Removing the credential prevents this + // kind of escalation. + + // Since port and path changes are less likely to fall among these + // cases, they don't trigger a credential wipe. + + $new_uri = $object->getEffectiveURI(); + if ($old_uri) { + $new_proto = ($old_uri->getProtocol() != $new_uri->getProtocol()); + $new_domain = ($old_uri->getDomain() != $new_uri->getDomain()); + if ($new_proto || $new_domain) { + $object->setCredentialPHID(null); + } + } break; case PhabricatorRepositoryURITransaction::TYPE_IO: $object->setIOType($xaction->getNewValue()); @@ -80,6 +118,7 @@ final class DiffusionURIEditor break; case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: $object->setRepositoryPHID($xaction->getNewValue()); + $object->attachRepository($this->repository); break; case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: $object->setCredentialPHID($xaction->getNewValue()); @@ -116,6 +155,9 @@ final class DiffusionURIEditor switch ($type) { case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: + // Save this, since we need it to validate TYPE_IO transactions. + $this->repositoryPHID = $object->getRepositoryPHID(); + $missing = $this->validateIsEmptyTextField( $object->getRepositoryPHID(), $xactions); @@ -173,6 +215,9 @@ final class DiffusionURIEditor $xaction); continue; } + + $this->repository = $repository; + $this->repositoryPHID = $repository_phid; } break; case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: @@ -184,6 +229,11 @@ final class DiffusionURIEditor continue; } + // Anyone who can edit a URI can remove the credential. + if ($credential_phid === null) { + continue; + } + $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withPHIDs(array($credential_phid)) @@ -275,7 +325,7 @@ final class DiffusionURIEditor if ($no_observers || $no_readwrite) { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($object->getRepositoryPHID())) + ->withPHIDs(array($this->repositoryPHID)) ->needURIs(true) ->executeOne(); $uris = $repository->getURIs(); @@ -365,9 +415,69 @@ final class DiffusionURIEditor } } break; + + case PhabricatorRepositoryURITransaction::TYPE_DISABLE: + $old = $object->getIsDisabled(); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if ($old == $new) { + continue; + } + + if (!$object->isBuiltin()) { + continue; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not manually disable builtin URIs.')); + } + break; } return $errors; } + protected function applyFinalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + // Synchronize the repository state based on the presence of an "Observe" + // URI. + $repository = $object->getRepository(); + + $uris = id(new PhabricatorRepositoryURIQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withRepositories(array($repository)) + ->execute(); + + $observe_uri = null; + foreach ($uris as $uri) { + if ($uri->getIoType() != PhabricatorRepositoryURI::IO_OBSERVE) { + continue; + } + + $observe_uri = $uri; + break; + } + + if ($observe_uri) { + $repository + ->setHosted(false) + ->setDetail('remote-uri', (string)$observe_uri->getEffectiveURI()) + ->setCredentialPHID($observe_uri->getCredentialPHID()); + } else { + $repository + ->setHosted(true) + ->setDetail('remote-uri', null) + ->setCredentialPHID(null); + } + + $repository->save(); + + return $xactions; + } + } diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 54fa32a6a3..50fc828364 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -172,14 +172,7 @@ final class DiffusionCommitHookEngine extends Phobject { if ($this->isInitialImport($all_updates)) { $repository = $this->getRepository(); - - $repository->openTransaction(); - $repository->beginReadLocking(); - $repository = $repository->reload(); - $repository->setDetail('importing', true); - $repository->save(); - $repository->endReadLocking(); - $repository->saveTransaction(); + $repository->markImporting(); } if ($this->emailPHIDs) { @@ -1244,7 +1237,7 @@ final class DiffusionCommitHookEngine extends Phobject { $commit_count++; } - if ($commit_count <= 7) { + if ($commit_count <= PhabricatorRepository::IMPORT_THRESHOLD) { // If this pushes a very small number of commits, assume it's an // initial commit or stack of a few initial commits. return false; diff --git a/src/applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php index 2c1e49b3da..7fb12b6b6a 100644 --- a/src/applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php @@ -16,6 +16,18 @@ final class DiffusionRepositoryURIsIndexEngineExtension public function indexObject( PhabricatorIndexEngine $engine, $object) { + + // Reload the repository to pick up URIs, which we need in order to update + // the URI index. + $object = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($object->getPHID())) + ->needURIs(true) + ->executeOne(); + if (!$object) { + return; + } + $object->updateURIIndex(); } diff --git a/src/applications/diffusion/exception/DiffusionDaemonLockException.php b/src/applications/diffusion/exception/DiffusionDaemonLockException.php new file mode 100644 index 0000000000..f2f5be0018 --- /dev/null +++ b/src/applications/diffusion/exception/DiffusionDaemonLockException.php @@ -0,0 +1,3 @@ +getRepository(); + + $has_any = + $repository->getDetail('herald-disabled') || + $repository->getDetail('disable-autoclose'); + + // NOTE: Any value here really means something is disabled, so try to + // hint that a little bit with the icon. + + if ($has_any) { + return 'fa-comment-o'; + } else { + return 'fa-commenting grey'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'publish', + 'autoclose', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +46,7 @@ final class DiffusionRepositoryActionsManagementPanel $repository, PhabricatorPolicyCapability::CAN_EDIT); - $actions_uri = $repository->getPathURI('edit/actions/'); + $actions_uri = $this->getEditPageURI(); return array( id(new PhabricatorActionView()) diff --git a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php index beed039c4f..14182a0444 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php @@ -13,6 +13,39 @@ final class DiffusionRepositoryAutomationManagementPanel return 800; } + public function shouldEnableForRepository( + PhabricatorRepository $repository) { + return $repository->isGit(); + } + + protected function getEditEngineFieldKeys() { + return array( + 'automationBlueprintPHIDs', + ); + } + + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + if (!$repository->canPerformAutomation()) { + return 'fa-truck grey'; + } + + $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); + if (!$blueprint_phids) { + return 'fa-truck grey'; + } + + $is_authorized = DrydockAuthorizationQuery::isFullyAuthorized( + $repository->getPHID(), + $blueprint_phids); + if (!$is_authorized) { + return 'fa-exclamation-triangle yellow'; + } + + return 'fa-truck'; + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -24,7 +57,7 @@ final class DiffusionRepositoryAutomationManagementPanel $can_test = $can_edit && $repository->canPerformAutomation(); - $automation_uri = $repository->getPathURI('edit/automation/'); + $automation_uri = $this->getEditPageURI(); $test_uri = $repository->getPathURI('edit/testautomation/'); return array( diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 7ddb38d34c..6a2f70d693 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -13,6 +13,26 @@ final class DiffusionRepositoryBasicsManagementPanel return 100; } + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + if (!$repository->isTracked()) { + return 'fa-ban indigo'; + } else { + return 'fa-code'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'name', + 'callsign', + 'shortName', + 'description', + 'projectPHIDs', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,10 +42,10 @@ final class DiffusionRepositoryBasicsManagementPanel $repository, PhabricatorPolicyCapability::CAN_EDIT); - $edit_uri = $repository->getPathURI('manage/'); + $edit_uri = $this->getEditPageURI(); $activate_uri = $repository->getPathURI('edit/activate/'); $delete_uri = $repository->getPathURI('edit/delete/'); - $encoding_uri = $repository->getPathURI('edit/encoding/'); + $encoding_uri = $this->getEditPageURI('encoding'); $dangerous_uri = $repository->getPathURI('edit/dangerous/'); if ($repository->isTracked()) { @@ -84,7 +104,38 @@ final class DiffusionRepositoryBasicsManagementPanel public function buildManagementPanelContent() { $result = array(); - $result[] = $this->newBox(pht('Repository Basics'), $this->buildBasics()); + $basics = $this->newBox(pht('Repository Basics'), $this->buildBasics()); + + $repository = $this->getRepository(); + $is_new = $repository->isNewlyInitialized(); + if ($is_new) { + $messages = array(); + + $messages[] = pht( + 'This newly created repository is not active yet. Configure policies, '. + 'options, and URIs. When ready, %s the repository.', + phutil_tag('strong', array(), pht('Activate'))); + + if ($repository->isHosted()) { + $messages[] = pht( + 'If activated now, this repository will become a new hosted '. + 'repository. To observe an existing repository instead, configure '. + 'it in the %s panel.', + phutil_tag('strong', array(), pht('URIs'))); + } else { + $messages[] = pht( + 'If activated now, this repository will observe an existing remote '. + 'repository and begin importing changes.'); + } + + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setErrors($messages); + + $basics->setInfoView($info_view); + } + + $result[] = $basics; $description = $this->buildDescription(); if ($description) { diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index 54184cd8ba..b3e7926cfc 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -13,6 +13,34 @@ final class DiffusionRepositoryBranchesManagementPanel return 1000; } + public function shouldEnableForRepository( + PhabricatorRepository $repository) { + return ($repository->isGit() || $repository->isHg()); + } + + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + $has_any = + $repository->getDetail('default-branch') || + $repository->getDetail('branch-filter') || + $repository->getDetail('close-commits-filter'); + + if ($has_any) { + return 'fa-code-fork'; + } else { + return 'fa-code-fork grey'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'defaultBranch', + 'trackOnly', + 'autocloseOnly', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +50,7 @@ final class DiffusionRepositoryBranchesManagementPanel $repository, PhabricatorPolicyCapability::CAN_EDIT); - $branches_uri = $repository->getPathURI('edit/branches/'); + $branches_uri = $this->getEditPageURI(); return array( id(new PhabricatorActionView()) diff --git a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php new file mode 100644 index 0000000000..ca2c577b38 --- /dev/null +++ b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php @@ -0,0 +1,29 @@ +newTimeline(); } diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index f278c3323a..34047cdb89 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -38,10 +38,19 @@ abstract class DiffusionRepositoryManagementPanel abstract public function getManagementPanelOrder(); abstract public function buildManagementPanelContent(); + public function getManagementPanelIcon() { + return 'fa-pencil'; + } + protected function buildManagementPanelActions() { return array(); } + public function shouldEnableForRepository( + PhabricatorRepository $repository) { + return true; + } + final protected function newActions() { $actions = $this->buildManagementPanelActions(); if (!$actions) { @@ -98,4 +107,45 @@ abstract class DiffusionRepositoryManagementPanel return $this->controller->newTimeline($this->getRepository()); } + final public function getPanelURI() { + $repository = $this->getRepository(); + $key = $this->getManagementPanelKey(); + return $repository->getPathURI("manage/{$key}/"); + } + + final public function newEditEnginePage() { + $field_keys = $this->getEditEngineFieldKeys(); + if (!$field_keys) { + return null; + } + + $key = $this->getManagementPanelKey(); + $label = $this->getManagementPanelLabel(); + $panel_uri = $this->getPanelURI(); + + return id(new PhabricatorEditPage()) + ->setKey($key) + ->setLabel($label) + ->setViewURI($panel_uri) + ->setFieldKeys($field_keys); + } + + protected function getEditEngineFieldKeys() { + return array(); + } + + protected function getEditPageURI($page = null) { + if ($page === null) { + $page = $this->getManagementPanelKey(); + } + + $repository = $this->getRepository(); + $id = $repository->getID(); + return "/diffusion/edit/{$id}/page/{$page}/"; + } + + public function getPanelNavigationURI() { + return $this->getPanelURI(); + } + } diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php index 9bfe572070..081869fbaa 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php @@ -13,6 +13,47 @@ final class DiffusionRepositoryPoliciesManagementPanel return 300; } + public function getManagementPanelIcon() { + $viewer = $this->getViewer(); + $repository = $this->getRepository(); + + $can_view = PhabricatorPolicyCapability::CAN_VIEW; + $can_edit = PhabricatorPolicyCapability::CAN_EDIT; + $can_push = DiffusionPushCapability::CAPABILITY; + + $actual_values = array( + 'spacePHID' => $repository->getSpacePHID(), + 'view' => $repository->getPolicy($can_view), + 'edit' => $repository->getPolicy($can_edit), + 'push' => $repository->getPolicy($can_push), + ); + + $default = PhabricatorRepository::initializeNewRepository( + $viewer); + + $default_values = array( + 'spacePHID' => $default->getSpacePHID(), + 'view' => $default->getPolicy($can_view), + 'edit' => $default->getPolicy($can_edit), + 'push' => $default->getPolicy($can_push), + ); + + if ($actual_values === $default_values) { + return 'fa-lock grey'; + } else { + return 'fa-lock'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'policy.view', + 'policy.edit', + 'spacePHID', + 'policy.push', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +63,7 @@ final class DiffusionRepositoryPoliciesManagementPanel $repository, PhabricatorPolicyCapability::CAN_EDIT); - $edit_uri = $repository->getPathURI('manage/'); + $edit_uri = $this->getEditPageURI(); return array( id(new PhabricatorActionView()) diff --git a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php index bff0420193..79b716e82a 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php @@ -13,6 +13,30 @@ final class DiffusionRepositoryStagingManagementPanel return 700; } + public function shouldEnableForRepository( + PhabricatorRepository $repository) { + return $repository->isGit(); + } + + + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + $staging_uri = $repository->getStagingURI(); + + if ($staging_uri) { + return 'fa-upload'; + } else { + return 'fa-upload grey'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'stagingAreaURI', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +46,7 @@ final class DiffusionRepositoryStagingManagementPanel $repository, PhabricatorPolicyCapability::CAN_EDIT); - $staging_uri = $repository->getPathURI('edit/staging/'); + $staging_uri = $this->getEditPageURI(); return array( id(new PhabricatorActionView()) diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php index 1666749980..573162b927 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php @@ -13,6 +13,21 @@ final class DiffusionRepositoryStatusManagementPanel return 200; } + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + // TODO: We could try to show a warning icon in more cases, but just + // raise in the most serious cases for now. + $messages = $this->loadStatusMessages($repository); + + $raw_error = $this->buildRepositoryRawError($repository, $messages); + if ($raw_error) { + return 'fa-exclamation-triangle red'; + } + + return 'fa-check grey'; + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -46,9 +61,7 @@ final class DiffusionRepositoryStatusManagementPanel pht('Update Frequency'), $this->buildRepositoryUpdateInterval($repository)); - $messages = id(new PhabricatorRepositoryStatusMessage()) - ->loadAllWhere('repositoryID = %d', $repository->getID()); - $messages = mpull($messages, null, 'getStatusType'); + $messages = $this->loadStatusMessages($repository); $status = $this->buildRepositoryStatus($repository, $messages); $raw_error = $this->buildRepositoryRawError($repository, $messages); @@ -122,7 +135,12 @@ final class DiffusionRepositoryStatusManagementPanel } if ($repository->isHosted()) { - if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) { + $proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; + $proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; + $can_http = $repository->canServeProtocol($proto_http, false) || + $repository->canServeProtocol($proto_https, false); + + if ($can_http) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git-http-backend'; @@ -138,7 +156,12 @@ final class DiffusionRepositoryStatusManagementPanel break; } } - if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) { + + + $proto_ssh = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; + $can_ssh = $repository->canServeProtocol($proto_ssh, false); + + if ($can_ssh) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git-receive-pack'; @@ -469,5 +492,22 @@ final class DiffusionRepositoryStatusManagementPanel return $raw_message; } + private function loadStatusMessages(PhabricatorRepository $repository) { + $messages = id(new PhabricatorRepositoryStatusMessage()) + ->loadAllWhere('repositoryID = %d', $repository->getID()); + $messages = mpull($messages, null, 'getStatusType'); + + return $messages; + } + + private function getEnvConfigLink() { + $config_href = '/config/edit/environment.append-paths/'; + return phutil_tag( + 'a', + array( + 'href' => $config_href, + ), + 'environment.append-paths'); + } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php index 54c446b9fe..0c723bd9e6 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php @@ -13,6 +13,18 @@ final class DiffusionRepositoryStorageManagementPanel return 600; } + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + if ($repository->getAlmanacServicePHID()) { + return 'fa-sitemap'; + } else if ($repository->isHosted()) { + return 'fa-folder'; + } else { + return 'fa-download'; + } + } + public function buildManagementPanelContent() { return array( $this->buildStorageStatusPanel(), @@ -28,7 +40,7 @@ final class DiffusionRepositoryStorageManagementPanel ->setViewer($viewer); if ($repository->usesLocalWorkingCopy()) { - $storage_path = $repository->getHumanReadableDetail('local-path'); + $storage_path = $repository->getLocalPath(); } else { $storage_path = phutil_tag('em', array(), pht('No Local Working Copy')); } diff --git a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php new file mode 100644 index 0000000000..c6b236d5b9 --- /dev/null +++ b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php @@ -0,0 +1,77 @@ +isSVN(); + } + + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + $has_any = (bool)$repository->getDetail('svn-subpath'); + + if ($has_any) { + return 'fa-database'; + } else { + return 'fa-database grey'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'importOnly', + ); + } + + protected function buildManagementPanelActions() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $subversion_uri = $this->getEditPageURI(); + + return array( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Properties')) + ->setHref($subversion_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit), + ); + } + + public function buildManagementPanelContent() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setViewer($viewer) + ->setActionList($this->newActions()); + + $default_branch = nonempty( + $repository->getHumanReadableDetail('svn-subpath'), + phutil_tag('em', array(), pht('Import Entire Repository'))); + $view->addProperty(pht('Import Only'), $default_branch); + + + return $this->newBox(pht('Subversion'), $view); + } + +} diff --git a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php index 4e934c67af..996473f4f1 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php @@ -13,6 +13,27 @@ final class DiffusionRepositorySymbolsManagementPanel return 900; } + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + $has_any = + $repository->getSymbolLanguages() || + $repository->getSymbolSources(); + + if ($has_any) { + return 'fa-link'; + } else { + return 'fa-link grey'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'symbolLanguages', + 'symbolRepositoryPHIDs', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +43,7 @@ final class DiffusionRepositorySymbolsManagementPanel $repository, PhabricatorPolicyCapability::CAN_EDIT); - $symbols_uri = $repository->getPathURI('edit/symbols/'); + $symbols_uri = $this->getEditPageURI(); return array( id(new PhabricatorActionView()) diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php index 634535fef4..333c2f03c8 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php @@ -6,11 +6,15 @@ final class DiffusionRepositoryURIsManagementPanel const PANELKEY = 'uris'; public function getManagementPanelLabel() { - return pht('Clone / Fetch / Mirror'); + return pht('URIs'); + } + + public function getManagementPanelIcon() { + return 'fa-cogs'; } public function getManagementPanelOrder() { - return 300; + return 400; } public function buildManagementPanelContent() { @@ -109,8 +113,44 @@ final class DiffusionRepositoryURIsManagementPanel ->setTag('a') ->setText(pht('Documentation'))); + $is_new = $repository->isNewlyInitialized(); + + $messages = array(); + if ($repository->isHosted()) { + if ($is_new) { + $host_message = pht('Phabricator will host this repository.'); + } else { + $host_message = pht('Phabricator is hosting this repository.'); + } + + $messages[] = array( + id(new PHUIIconView())->setIcon('fa-folder'), + ' ', + $host_message, + ); + } else { + if ($is_new) { + $observe_message = pht( + 'Phabricator will observe a remote repository.'); + } else { + $observe_message = pht( + 'This repository is hosted remotely. Phabricator is observing it.'); + } + + $messages[] = array( + id(new PHUIIconView())->setIcon('fa-download'), + ' ', + $observe_message, + ); + } + + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setErrors($messages); + return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setInfoView($info_view) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/applications/diffusion/protocol/DiffusionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionCommandEngine.php index 552ca813a2..5a18595049 100644 --- a/src/applications/diffusion/protocol/DiffusionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionCommandEngine.php @@ -57,6 +57,10 @@ abstract class DiffusionCommandEngine extends Phobject { return $this->protocol; } + public function getDisplayProtocol() { + return $this->getProtocol().'://'; + } + public function setCredentialPHID($credential_phid) { $this->credentialPHID = $credential_phid; return $this; @@ -197,34 +201,82 @@ abstract class DiffusionCommandEngine extends Phobject { return $env; } - protected function isSSHProtocol() { + public function isSSHProtocol() { return ($this->getProtocol() == 'ssh'); } - protected function isSVNProtocol() { + public function isSVNProtocol() { return ($this->getProtocol() == 'svn'); } - protected function isSVNSSHProtocol() { + public function isSVNSSHProtocol() { return ($this->getProtocol() == 'svn+ssh'); } - protected function isHTTPProtocol() { + public function isHTTPProtocol() { return ($this->getProtocol() == 'http'); } - protected function isHTTPSProtocol() { + public function isHTTPSProtocol() { return ($this->getProtocol() == 'https'); } - protected function isAnyHTTPProtocol() { + public function isAnyHTTPProtocol() { return ($this->isHTTPProtocol() || $this->isHTTPSProtocol()); } - protected function isAnySSHProtocol() { + public function isAnySSHProtocol() { return ($this->isSSHProtocol() || $this->isSVNSSHProtocol()); } + public function isCredentialSupported() { + return ($this->getPassphraseProvidesCredentialType() !== null); + } + + public function isCredentialOptional() { + if ($this->isAnySSHProtocol()) { + return false; + } + + return true; + } + + public function getPassphraseCredentialLabel() { + if ($this->isAnySSHProtocol()) { + return pht('SSH Key'); + } + + if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { + return pht('Password'); + } + + return null; + } + + public function getPassphraseDefaultCredentialType() { + if ($this->isAnySSHProtocol()) { + return PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE; + } + + if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { + return PassphrasePasswordCredentialType::CREDENTIAL_TYPE; + } + + return null; + } + + public function getPassphraseProvidesCredentialType() { + if ($this->isAnySSHProtocol()) { + return PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE; + } + + if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { + return PassphrasePasswordCredentialType::PROVIDES_TYPE; + } + + return null; + } + protected function getSSHWrapper() { $root = dirname(phutil_get_library_root('phabricator')); return $root.'/bin/ssh-connect'; diff --git a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php index 3458d27ed6..5eae19e030 100644 --- a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php @@ -24,8 +24,7 @@ final class DiffusionGitCommandEngine // really silly, but seems like the least damaging approach to // mitigating the issue. - $root = dirname(phutil_get_library_root('phabricator')); - $env['HOME'] = $root.'/support/empty/'; + $env['HOME'] = PhabricatorEnv::getEmptyCWD(); if ($this->isAnySSHProtocol()) { $env['GIT_SSH'] = $this->getSSHWrapper(); diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php index 53dc6b30da..4e9ed246d9 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php @@ -31,6 +31,11 @@ final class DiffusionLowLevelResolveRefsQuery return array(); } + $repository = $this->getRepository(); + if (!$repository->hasLocalWorkingCopy()) { + return array(); + } + switch ($this->getRepository()->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $result = $this->resolveGitRefs(); diff --git a/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php index 17d3badaa5..76f8d3c837 100644 --- a/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php @@ -15,7 +15,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { protected function executeRepositoryOperations() { $repository = $this->getRepository(); - $viewer = $this->getViewer(); + $viewer = $this->getUser(); $device = AlmanacKeys::getLiveDevice(); // This is a write, and must have write access. diff --git a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php index 60b929f7c4..78252fb8d9 100644 --- a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php @@ -185,24 +185,19 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withIdentifiers(array($identifier)) + ->needURIs(true) ->executeOne(); if (!$repository) { throw new Exception( pht('No repository "%s" exists!', $identifier)); } - switch ($repository->getServeOverSSH()) { - case PhabricatorRepository::SERVE_READONLY: - case PhabricatorRepository::SERVE_READWRITE: - // If we have read or read/write access, proceed for now. We will - // check write access when the user actually issues a write command. - break; - case PhabricatorRepository::SERVE_OFF: - default: - throw new Exception( - pht( - 'This repository ("%s") is not available over SSH.', - $repository->getDisplayName())); + $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; + if (!$repository->canServeProtocol($protocol, false)) { + throw new Exception( + pht( + 'This repository ("%s") is not available over SSH.', + $repository->getDisplayName())); } return $repository; @@ -224,35 +219,27 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow { 'user account.')); } - switch ($repository->getServeOverSSH()) { - case PhabricatorRepository::SERVE_READONLY: - if ($protocol_command !== null) { - throw new Exception( - pht( - 'This repository is read-only over SSH (tried to execute '. - 'protocol command "%s").', - $protocol_command)); - } else { - throw new Exception( - pht('This repository is read-only over SSH.')); - } - break; - case PhabricatorRepository::SERVE_READWRITE: - $can_push = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - DiffusionPushCapability::CAPABILITY); - if (!$can_push) { - throw new Exception( - pht('You do not have permission to push to this repository.')); - } - break; - case PhabricatorRepository::SERVE_OFF: - default: - // This shouldn't be reachable because we don't get this far if the - // repository isn't enabled, but kick them out anyway. + $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; + if ($repository->canServeProtocol($protocol, true)) { + $can_push = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + DiffusionPushCapability::CAPABILITY); + if (!$can_push) { throw new Exception( - pht('This repository is not available over SSH.')); + pht('You do not have permission to push to this repository.')); + } + } else { + if ($protocol_command !== null) { + throw new Exception( + pht( + 'This repository is read-only over SSH (tried to execute '. + 'protocol command "%s").', + $protocol_command)); + } else { + throw new Exception( + pht('This repository is read-only over SSH.')); + } } $this->hasWriteAccess = true; diff --git a/src/applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php index 820a380856..57a44d4707 100644 --- a/src/applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php @@ -148,15 +148,25 @@ final class DiffusionSubversionServeSSHWorkflow if ($this->shouldProxy()) { $command = $this->getProxyCommand(); $this->isProxying = true; + $cwd = null; } else { $command = csprintf( 'svnserve -t --tunnel-user=%s', $this->getUser()->getUsername()); + $cwd = PhabricatorEnv::getEmptyCWD(); } $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); $future = new ExecFuture('%C', $command); + // If we're receiving a commit, svnserve will fail to execute the commit + // hook with an unhelpful error if the CWD isn't readable by the user we + // are sudoing to. Switch to a readable, empty CWD before running + // svnserve. See T10941. + if ($cwd !== null) { + $future->setCWD($cwd); + } + $this->inProtocol = new DiffusionSubversionWireProtocol(); $this->outProtocol = new DiffusionSubversionWireProtocol(); diff --git a/src/applications/diffusion/view/DiffusionCloneURIView.php b/src/applications/diffusion/view/DiffusionCloneURIView.php new file mode 100644 index 0000000000..fc861ed183 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionCloneURIView.php @@ -0,0 +1,131 @@ +repository = $repository; + return $this; + } + + public function getRepository() { + return $this->repository; + } + + public function setRepositoryURI(PhabricatorRepositoryURI $repository_uri) { + $this->repositoryURI = $repository_uri; + return $this; + } + + public function getRepositoryURI() { + return $this->repositoryURI; + } + + public function setDisplayURI($display_uri) { + $this->displayURI = $display_uri; + return $this; + } + + public function getDisplayURI() { + return $this->displayURI; + } + + public function render() { + require_celerity_resource('diffusion-icons-css'); + + Javelin::initBehavior('select-content'); + + $uri_id = celerity_generate_unique_node_id(); + + $display = $this->getDisplayURI(); + + $input = javelin_tag( + 'input', + array( + 'id' => $uri_id, + 'type' => 'text', + 'value' => $display, + 'class' => 'diffusion-clone-uri', + 'readonly' => 'true', + )); + + $uri = $this->getRepositoryURI(); + switch ($uri->getEffectiveIOType()) { + case PhabricatorRepositoryURI::IO_READ: + $io_icon = 'fa-eye'; + $io_tip = pht('Read-Only'); + break; + case PhabricatorRepositoryURI::IO_READWRITE: + $io_icon = 'fa-download'; + $io_tip = pht('Read / Write'); + break; + default: + $io_icon = 'fa-cloud'; + $io_tip = pht('External'); + break; + } + + $io = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::GREY) + ->setIcon($io_icon) + ->setHref('#') + ->addSigil('select-content') + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => $io_tip, + 'selectID' => $uri_id, + )); + + switch ($uri->getEffectiveIOType()) { + case PhabricatorRepositoryURI::IO_READ: + case PhabricatorRepositoryURI::IO_READWRITE: + switch ($uri->getBuiltinProtocol()) { + case PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH: + $auth_uri = '/settings/panel/ssh/'; + $auth_tip = pht('Manage SSH Keys'); + $auth_disabled = false; + break; + default: + $auth_uri = '/settings/panel/vcspassword'; + $auth_tip = pht('Manage Password'); + $auth_disabled = false; + break; + } + break; + default: + $auth_disabled = true; + $auth_tip = pht('External'); + $auth_uri = '#'; + break; + } + + $credentials = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::GREY) + ->setIcon('fa-key') + ->setTooltip($auth_tip) + ->setHref($auth_uri) + ->setDisabled($auth_disabled); + + $cells = array(); + $cells[] = phutil_tag('td', array(), $input); + $cells[] = phutil_tag('th', array(), $io); + $cells[] = phutil_tag('th', array(), $credentials); + + $row = phutil_tag('tr', array(), $cells); + + return phutil_tag( + 'table', + array( + 'class' => 'diffusion-clone-uri-table', + ), + $row); + } + +} diff --git a/src/applications/drydock/query/DrydockAuthorizationQuery.php b/src/applications/drydock/query/DrydockAuthorizationQuery.php index 6d2cddcf8a..d6436fc93a 100644 --- a/src/applications/drydock/query/DrydockAuthorizationQuery.php +++ b/src/applications/drydock/query/DrydockAuthorizationQuery.php @@ -9,6 +9,35 @@ final class DrydockAuthorizationQuery extends DrydockQuery { private $blueprintStates; private $objectStates; + public static function isFullyAuthorized( + $object_phid, + array $blueprint_phids) { + + if (!$blueprint_phids) { + return true; + } + + $authorizations = id(new self()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withObjectPHIDs(array($object_phid)) + ->withBlueprintPHIDs($blueprint_phids) + ->execute(); + $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); + + foreach ($blueprint_phids as $phid) { + $authorization = idx($authorizations, $phid); + if (!$authorization) { + return false; + } + + if (!$authorization->isAuthorized()) { + return false; + } + } + + return true; + } + public function withIDs(array $ids) { $this->ids = $ids; return $this; diff --git a/src/applications/drydock/storage/DrydockAuthorization.php b/src/applications/drydock/storage/DrydockAuthorization.php index ea1160b23b..9503a03767 100644 --- a/src/applications/drydock/storage/DrydockAuthorization.php +++ b/src/applications/drydock/storage/DrydockAuthorization.php @@ -93,6 +93,11 @@ final class DrydockAuthorization extends DrydockDAO return idx($map, $state, pht('', $state)); } + public function isAuthorized() { + $state = $this->getBlueprintAuthorizationState(); + return ($state == self::BLUEPRINTAUTH_AUTHORIZED); + } + /** * Apply external authorization effects after a user chagnes the value of a * blueprint selector control an object. diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php index ecb0e4fd09..22459a34ad 100644 --- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php +++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php @@ -109,6 +109,14 @@ final class PhabricatorEmbedFileRemarkupRule ); $image_class = 'phabricator-remarkup-embed-image-full'; break; + // Displays "full" in normal Remarkup, "wide" in Documents + case 'wide': + $attrs += array( + 'src' => $file->getBestURI(), + 'width' => $file->getImageWidth(), + ); + $image_class = 'phabricator-remarkup-embed-image-wide'; + break; case 'thumb': default: $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW; diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 6c9a7d0089..da0d12db1c 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -868,23 +868,9 @@ final class ManiphestTransactionEditor switch ($type) { case PhabricatorTransactions::TYPE_COLUMNS: try { - $this->buildMoveTransaction($object, $xaction); - - // Implicilty add the task to any boards that we're moving it - // on, since moves on a board the task isn't part of are not - // meaningful. - $board_phids = ipull($xaction->getNewValue(), 'boardPHID'); - if ($board_phids) { - $results[] = id(new ManiphestTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) - ->setIgnoreOnNoEffect(true) - ->setNewValue( - array( - '+' => array_fuse($board_phids), - )); + $more_xactions = $this->buildMoveTransaction($object, $xaction); + foreach ($more_xactions as $more_xaction) { + $results[] = $more_xaction; } } catch (Exception $ex) { $error = new PhabricatorApplicationTransactionValidationError( @@ -1098,6 +1084,89 @@ final class ManiphestTransactionEditor $new = array_values($new); $xaction->setNewValue($new); + + + $more = array(); + + // If we're moving the object into a column and it does not already belong + // in the column, add the appropriate board. For normal columns, this + // is the board PHID. For proxy columns, it is the proxy PHID, unless the + // object is already a member of some descendant of the proxy PHID. + + // The major case where this can happen is moves via the API, but it also + // happens when a user drags a task from the "Backlog" to a milestone + // column. + + if ($object_phid) { + $current_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object_phid, + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + $current_phids = array_fuse($current_phids); + } else { + $current_phids = array(); + } + + $add_boards = array(); + foreach ($new as $move) { + $column_phid = $move['columnPHID']; + $board_phid = $move['boardPHID']; + $column = $columns[$column_phid]; + $proxy_phid = $column->getProxyPHID(); + + // If this is a normal column, add the board if the object isn't already + // associated. + if (!$proxy_phid) { + if (!isset($current_phids[$board_phid])) { + $add_boards[] = $board_phid; + } + continue; + } + + // If this is a proxy column but the object is already associated with + // the proxy board, we don't need to do anything. + if (isset($current_phids[$proxy_phid])) { + continue; + } + + // If this a proxy column and the object is already associated with some + // descendant of the proxy board, we also don't need to do anything. + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withAncestorProjectPHIDs(array($proxy_phid)) + ->execute(); + + $found_descendant = false; + foreach ($descendants as $descendant) { + if (isset($current_phids[$descendant->getPHID()])) { + $found_descendant = true; + break; + } + } + + if ($found_descendant) { + continue; + } + + // Otherwise, we're moving the object to a proxy column which it is not + // a member of yet, so add an association to the column's proxy board. + + $add_boards[] = $proxy_phid; + } + + if ($add_boards) { + $more[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setIgnoreOnNoEffect(true) + ->setNewValue( + array( + '+' => array_fuse($add_boards), + )); + } + + return $more; } private function applyBoardMove($object, array $move) { diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index 743082f47f..8a21a75cfb 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -111,8 +111,11 @@ final class PhabricatorMetaMTAMailBody extends Phobject { return $this; } - public function addPlaintextSection($header, $text) { - $this->sections[] = $header."\n".$this->indent($text); + public function addPlaintextSection($header, $text, $indent = true) { + if ($indent) { + $text = $this->indent($text); + } + $this->sections[] = $header."\n".$text; return $this; } diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index ad7db5cc6e..55aeb11b60 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -82,7 +82,7 @@ final class PhabricatorOwnersPathsController } } - $repos = mpull($repos, 'getMonogram', 'getPHID'); + $repos = mpull($repos, 'getDisplayName', 'getPHID'); asort($repos); $template = new AphrontTypeaheadTemplateView(); diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 4e1493d458..015dce1f40 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -42,10 +42,50 @@ final class PassphraseCredentialControl extends AphrontFormControl { foreach ($this->options as $option) { $options_map[$option->getPHID()] = pht( '%s %s', - 'K'.$option->getID(), + $option->getMonogram(), $option->getName()); } + // The user editing the form may not have permission to see the current + // credential. Populate it into the menu to allow them to save the form + // without making any changes. + $current_phid = $this->getValue(); + if (strlen($current_phid) && empty($options_map[$current_phid])) { + $viewer = $this->getViewer(); + + $user_credential = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current_phid)) + ->executeOne(); + if (!$user_credential) { + // Pull the credential with the ominipotent viewer so we can look up + // the ID and tell if it's restricted or invalid. + $omnipotent_credential = id(new PassphraseCredentialQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($current_phid)) + ->executeOne(); + if ($omnipotent_credential) { + $current_name = pht( + '%s (Restricted Credential)', + $omnipotent_credential->getMonogram()); + } else { + $current_name = pht( + 'Invalid Credential ("%s")', + $current_phid); + } + } else { + $current_name = pht( + '%s %s', + $user_credential->getMonogram(), + $user_credential->getName()); + } + + $options_map = array( + $current_phid => $current_name, + ) + $options_map; + } + + $disabled = $this->getDisabled(); if ($this->allowNull) { $options_map = array('' => pht('(No Credentials)')) + $options_map; diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 5ead896035..0c3f51ef6b 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -1011,6 +1011,39 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $column->getPHID(), ); $this->assertColumns($expect, $user, $board, $task); + + + // Move the task within the "Milestone" column. This should not affect + // the projects the task is tagged with. See T10912. + $task_a = $task; + + $task_b = $this->newTask($user, array($backlog)); + $this->moveToColumn($user, $board, $task_b, $backlog, $column); + + $a_options = array( + 'beforePHID' => $task_b->getPHID(), + ); + + $b_options = array( + 'beforePHID' => $task_a->getPHID(), + ); + + $old_projects = $this->getTaskProjects($task); + + // Move the target task to the top. + $this->moveToColumn($user, $board, $task_a, $column, $column, $a_options); + $new_projects = $this->getTaskProjects($task_a); + $this->assertEqual($old_projects, $new_projects); + + // Move the other task. + $this->moveToColumn($user, $board, $task_b, $column, $column, $b_options); + $new_projects = $this->getTaskProjects($task_a); + $this->assertEqual($old_projects, $new_projects); + + // Move the target task again. + $this->moveToColumn($user, $board, $task_a, $column, $column, $a_options); + $new_projects = $this->getTaskProjects($task_a); + $this->assertEqual($old_projects, $new_projects); } public function testColumnExtendedPolicies() { diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index e529fab61e..c92c843204 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -96,32 +96,6 @@ final class PhabricatorProjectMoveController } } - $proxy = $column->getProxy(); - if ($proxy) { - // We're moving the task into a subproject or milestone column, so add - // the subproject or milestone. - $add_projects = array($proxy->getPHID()); - } else if ($project->getHasSubprojects() || $project->getHasMilestones()) { - // We're moving the task into the "Backlog" column on the parent project, - // so add the parent explicitly. This gets rid of any subproject or - // milestone tags. - $add_projects = array($project->getPHID()); - } else { - $add_projects = array(); - } - - if ($add_projects) { - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - - $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $project_type) - ->setNewValue( - array( - '+' => array_fuse($add_projects), - )); - } - $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) ->setContinueOnMissingFields(true) diff --git a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php deleted file mode 100644 index ad4b62104e..0000000000 --- a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php +++ /dev/null @@ -1,140 +0,0 @@ -formatStringConstants(array('git', 'hg', 'svn')); - - return array( - 'name' => 'required string', - 'vcs' => 'required '.$vcs_const, - 'callsign' => 'required string', - 'description' => 'optional string', - 'encoding' => 'optional string', - 'tracking' => 'optional bool', - 'uri' => 'required string', - 'credentialPHID' => 'optional string', - 'svnSubpath' => 'optional string', - 'branchFilter' => 'optional list', - 'closeCommitsFilter' => 'optional list', - 'pullFrequency' => 'optional int', - 'defaultBranch' => 'optional string', - 'heraldEnabled' => 'optional bool, default = true', - 'autocloseEnabled' => 'optional bool, default = true', - 'svnUUID' => 'optional string', - ); - } - - protected function defineReturnType() { - return 'nonempty dict'; - } - - protected function defineErrorTypes() { - return array( - 'ERR-DUPLICATE' => pht('Duplicate repository callsign.'), - 'ERR-BAD-CALLSIGN' => pht( - 'Callsign is required and must be ALL UPPERCASE LETTERS.'), - 'ERR-UNKNOWN-REPOSITORY-VCS' => pht('Unknown repository VCS type.'), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $application = id(new PhabricatorApplicationQuery()) - ->setViewer($request->getUser()) - ->withClasses(array('PhabricatorDiffusionApplication')) - ->executeOne(); - - PhabricatorPolicyFilter::requireCapability( - $request->getUser(), - $application, - DiffusionCreateRepositoriesCapability::CAPABILITY); - - // TODO: This has some duplication with (and lacks some of the validation - // of) the web workflow; refactor things so they can share more code as this - // stabilizes. Specifically, this should move to transactions since they - // work properly now. - - $repository = PhabricatorRepository::initializeNewRepository( - $request->getUser()); - - $repository->setName($request->getValue('name')); - - $callsign = $request->getValue('callsign'); - if (!preg_match('/^[A-Z]+\z/', $callsign)) { - throw new ConduitException('ERR-BAD-CALLSIGN'); - } - $repository->setCallsign($callsign); - - $local_path = PhabricatorEnv::getEnvConfig( - 'repository.default-local-path'); - - $local_path = rtrim($local_path, '/'); - $local_path = $local_path.'/'.$callsign.'/'; - - $vcs = $request->getValue('vcs'); - - $map = array( - 'git' => PhabricatorRepositoryType::REPOSITORY_TYPE_GIT, - 'hg' => PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL, - 'svn' => PhabricatorRepositoryType::REPOSITORY_TYPE_SVN, - ); - if (empty($map[$vcs])) { - throw new ConduitException('ERR-UNKNOWN-REPOSITORY-VCS'); - } - $repository->setVersionControlSystem($map[$vcs]); - - $repository->setCredentialPHID($request->getValue('credentialPHID')); - - $remote_uri = $request->getValue('uri'); - PhabricatorRepository::assertValidRemoteURI($remote_uri); - - $details = array( - 'encoding' => $request->getValue('encoding'), - 'description' => $request->getValue('description'), - 'tracking-enabled' => (bool)$request->getValue('tracking', true), - 'remote-uri' => $remote_uri, - 'local-path' => $local_path, - 'branch-filter' => array_fill_keys( - $request->getValue('branchFilter', array()), - true), - 'close-commits-filter' => array_fill_keys( - $request->getValue('closeCommitsFilter', array()), - true), - 'pull-frequency' => $request->getValue('pullFrequency'), - 'default-branch' => $request->getValue('defaultBranch'), - 'herald-disabled' => !$request->getValue('heraldEnabled', true), - 'svn-subpath' => $request->getValue('svnSubpath'), - 'disable-autoclose' => !$request->getValue('autocloseEnabled', true), - ); - - foreach ($details as $key => $value) { - $repository->setDetail($key, $value); - } - - try { - $repository->save(); - } catch (AphrontDuplicateKeyQueryException $ex) { - throw new ConduitException('ERR-DUPLICATE'); - } - - return $repository->toDictionary(); - } - -} diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index da8d9336d2..bbd73d2d90 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -354,117 +354,17 @@ final class PhabricatorRepositoryPullLocalDaemon } } - $service_phids = array(); - foreach ($repositories as $key => $repository) { - $service_phid = $repository->getAlmanacServicePHID(); + $viewer = $this->getViewer(); - // If the repository is bound to a service but this host is not a - // recognized device, or vice versa, don't pull the repository. - $is_cluster_repo = (bool)$service_phid; - $is_cluster_device = (bool)$device; - if ($is_cluster_repo != $is_cluster_device) { - if ($is_cluster_device) { - $this->log( - pht( - 'Repository "%s" is not a cluster repository, but the current '. - 'host is a cluster device ("%s"), so the repository will not '. - 'be updated on this host.', - $repository->getDisplayName(), - $device->getName())); - } else { - $this->log( - pht( - 'Repository "%s" is a cluster repository, but the current '. - 'host is not a cluster device (it has no device ID), so the '. - 'repository will not be updated on this host.', - $repository->getDisplayName())); - } - unset($repositories[$key]); - continue; - } + $filter = id(new DiffusionLocalRepositoryFilter()) + ->setViewer($viewer) + ->setDevice($device) + ->setRepositories($repositories); - if ($service_phid) { - $service_phids[] = $service_phid; - } - } + $repositories = $filter->execute(); - if ($device) { - $device_phid = $device->getPHID(); - - if ($service_phids) { - // We could include `withDevicePHIDs()` here to pull a smaller result - // set, but we can provide more helpful diagnostic messages below if - // we fetch a little more data. - $services = id(new AlmanacServiceQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($service_phids) - ->withServiceTypes( - array( - AlmanacClusterRepositoryServiceType::SERVICETYPE, - )) - ->needBindings(true) - ->execute(); - $services = mpull($services, null, 'getPHID'); - } else { - $services = array(); - } - - foreach ($repositories as $key => $repository) { - $service_phid = $repository->getAlmanacServicePHID(); - - $service = idx($services, $service_phid); - if (!$service) { - $this->log( - pht( - 'Repository "%s" is on cluster service "%s", but that service '. - 'could not be loaded, so the repository will not be updated '. - 'on this host.', - $repository->getDisplayName(), - $service_phid)); - unset($repositories[$key]); - continue; - } - - $bindings = $service->getBindings(); - $bindings = mgroup($bindings, 'getDevicePHID'); - $bindings = idx($bindings, $device_phid); - if (!$bindings) { - $this->log( - pht( - 'Repository "%s" is on cluster service "%s", but that service '. - 'is not bound to this device ("%s"), so the repository will '. - 'not be updated on this host.', - $repository->getDisplayName(), - $service->getName(), - $device->getName())); - unset($repositories[$key]); - continue; - } - - $all_disabled = true; - foreach ($bindings as $binding) { - if (!$binding->getIsDisabled()) { - $all_disabled = false; - break; - } - } - - if ($all_disabled) { - $this->log( - pht( - 'Repository "%s" is on cluster service "%s", but the binding '. - 'between that service and this device ("%s") is disabled, so '. - 'the not be updated on this host.', - $repository->getDisplayName(), - $service->getName(), - $device->getName())); - unset($repositories[$key]); - continue; - } - - // We have a valid service that is actively bound to the current host - // device, so we're good to go. - } + foreach ($filter->getRejectionReasons() as $reason) { + $this->log($reason); } // Shuffle the repositories, then re-key the array since shuffle() diff --git a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php index 3a9508e197..42224c9553 100644 --- a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php +++ b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php @@ -59,6 +59,14 @@ final class PhabricatorRepositoryURINormalizer extends Phobject { $this->uri = $uri; } + public static function getAllURITypes() { + return array( + self::TYPE_GIT, + self::TYPE_SVN, + self::TYPE_MERCURIAL, + ); + } + /* -( Normalizing URIs )--------------------------------------------------- */ @@ -91,6 +99,10 @@ final class PhabricatorRepositoryURINormalizer extends Phobject { } } + public function getNormalizedURI() { + return $this->getNormalizedDomain().'/'.$this->getNormalizedPath(); + } + /** * @task normal @@ -113,11 +125,32 @@ final class PhabricatorRepositoryURINormalizer extends Phobject { // example. $matches = null; - if (preg_match('@^(diffusion/[A-Z]+)@', $path, $matches)) { + if (preg_match('@^(diffusion/(?:[A-Z]+|\d+))@', $path, $matches)) { $path = $matches[1]; } return $path; } + public function getNormalizedDomain() { + $domain = null; + + $uri = new PhutilURI($this->uri); + if ($uri->getProtocol()) { + $domain = $uri->getDomain(); + } + + if (!strlen($domain)) { + $uri = new PhutilGitURI($this->uri); + $domain = $uri->getDomain(); + } + + if (!strlen($domain)) { + $domain = ''; + } + + return phutil_utf8_strtolower($domain); + } + + } diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 3dc842d94f..43c95f6b4a 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -26,18 +26,7 @@ final class PhabricatorRepositoryEditor $types[] = PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH; $types[] = PhabricatorRepositoryTransaction::TYPE_NOTIFY; $types[] = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE; - $types[] = PhabricatorRepositoryTransaction::TYPE_REMOTE_URI; - $types[] = PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN; - $types[] = PhabricatorRepositoryTransaction::TYPE_SSH_KEY; - $types[] = PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE; - $types[] = PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN; - $types[] = PhabricatorRepositoryTransaction::TYPE_HTTP_PASS; - $types[] = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH; - $types[] = PhabricatorRepositoryTransaction::TYPE_HOSTING; - $types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; - $types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; $types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY; - $types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL; $types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS; $types[] = PhabricatorRepositoryTransaction::TYPE_SLUG; $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; @@ -83,20 +72,8 @@ final class PhabricatorRepositoryEditor return (int)!$object->getDetail('herald-disabled'); case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: return (int)!$object->getDetail('disable-autoclose'); - case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: - return $object->getDetail('remote-uri'); - case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: - return $object->getDetail('local-path'); - case PhabricatorRepositoryTransaction::TYPE_HOSTING: - return $object->isHosted(); - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: - return $object->getServeOverHTTP(); - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: - return $object->getServeOverSSH(); case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: return $object->getPushPolicy(); - case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: - return $object->getCredentialPHID(); case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: return $object->shouldAllowDangerousChanges(); case PhabricatorRepositoryTransaction::TYPE_SLUG: @@ -130,19 +107,8 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: case PhabricatorRepositoryTransaction::TYPE_UUID: case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: - case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: - case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: - case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: - case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: - case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: - case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: - case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: case PhabricatorRepositoryTransaction::TYPE_VCS: - case PhabricatorRepositoryTransaction::TYPE_HOSTING: - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: - case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: @@ -172,7 +138,15 @@ final class PhabricatorRepositoryEditor $object->setVersionControlSystem($xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: - $object->setDetail('tracking-enabled', $xaction->getNewValue()); + $active = $xaction->getNewValue(); + + // The first time a repository is activated, clear the "new repository" + // flag so we stop showing setup hints. + if ($active) { + $object->setDetail('newly-initialized', false); + } + + $object->setDetail('tracking-enabled', $active); break; case PhabricatorRepositoryTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); @@ -205,22 +179,8 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: $object->setDetail('disable-autoclose', (int)!$xaction->getNewValue()); break; - case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: - $object->setDetail('remote-uri', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: - $object->setDetail('local-path', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_HOSTING: - return $object->setHosted($xaction->getNewValue()); - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: - return $object->setServeOverHTTP($xaction->getNewValue()); - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: - return $object->setServeOverSSH($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: return $object->setPushPolicy($xaction->getNewValue()); - case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: - return $object->setCredentialPHID($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: $object->setDetail('allow-dangerous-changes', $xaction->getNewValue()); return; @@ -258,27 +218,6 @@ final class PhabricatorRepositoryEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: - // Adjust the object <-> credential edge for this repository. - - $old_phid = $xaction->getOldValue(); - $new_phid = $xaction->getNewValue(); - - $editor = new PhabricatorEdgeEditor(); - - $edge_type = PhabricatorObjectUsesCredentialsEdgeType::EDGECONST; - $src_phid = $object->getPHID(); - - if ($old_phid) { - $editor->removeEdge($src_phid, $edge_type, $old_phid); - } - - if ($new_phid) { - $editor->addEdge($src_phid, $edge_type, $new_phid); - } - - $editor->save(); - break; case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: DrydockAuthorization::applyAuthorizationChanges( $this->getActor(), @@ -304,21 +243,10 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: case PhabricatorRepositoryTransaction::TYPE_UUID: case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: - case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: - case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: - case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: - case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: - case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: - case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: - case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: case PhabricatorRepositoryTransaction::TYPE_VCS: case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: - case PhabricatorRepositoryTransaction::TYPE_HOSTING: - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: - case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: - case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: case PhabricatorRepositoryTransaction::TYPE_SLUG: case PhabricatorRepositoryTransaction::TYPE_SERVICE: @@ -388,38 +316,6 @@ final class PhabricatorRepositoryEditor } break; - case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: - foreach ($xactions as $xaction) { - $new_uri = $xaction->getNewValue(); - try { - PhabricatorRepository::assertValidRemoteURI($new_uri); - } catch (Exception $ex) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $ex->getMessage(), - $xaction); - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: - $ok = PassphraseCredentialControl::validateTransactions( - $this->getActor(), - $xactions); - if (!$ok) { - foreach ($xactions as $xaction) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The selected credential does not exist, or you do not have '. - 'permission to use it.'), - $xaction); - } - } - break; - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: foreach ($xactions as $xaction) { $old = nonempty($xaction->getOldValue(), array()); @@ -706,7 +602,7 @@ final class PhabricatorRepositoryEditor // If the repository does not have a local path yet, assign it one based // on its ID. We can't do this earlier because we won't have an ID yet. - $local_path = $object->getDetail('local-path'); + $local_path = $object->getLocalPath(); if (!strlen($local_path)) { $local_key = 'repository.default-local-path'; @@ -716,11 +612,22 @@ final class PhabricatorRepositoryEditor $id = $object->getID(); $local_path = "{$local_root}/{$id}/"; - $object->setDetail('local-path', $local_path); + $object->setLocalPath($local_path); $object->save(); } if ($this->getIsNewObject()) { + // The default state of repositories is to be hosted, if they are + // enabled without configuring any "Observe" URIs. + $object->setHosted(true); + $object->save(); + + // Create this repository's builtin URIs. + $builtin_uris = $object->newBuiltinURIs(); + foreach ($builtin_uris as $uri) { + $uri->save(); + } + id(new DiffusionRepositoryClusterEngine()) ->setViewer($this->getActor()) ->setRepository($object) diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index 3c3a0526be..78a40986e3 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -37,6 +37,33 @@ final class PhabricatorRepositoryDiscoveryEngine public function discoverCommits() { $repository = $this->getRepository(); + $lock = $this->newRepositoryLock($repository, 'repo.look', false); + + try { + $lock->lock(); + } catch (PhutilLockException $ex) { + throw new DiffusionDaemonLockException( + pht( + 'Another process is currently discovering repository "%s", '. + 'skipping discovery.', + $repository->getDisplayName())); + } + + try { + $result = $this->discoverCommitsWithLock(); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return $result; + } + + private function discoverCommitsWithLock() { + $repository = $this->getRepository(); + $vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: @@ -52,6 +79,16 @@ final class PhabricatorRepositoryDiscoveryEngine throw new Exception(pht("Unknown VCS '%s'!", $vcs)); } + if ($this->isInitialImport($refs)) { + $this->log( + pht( + 'Discovered more than %s commit(s) in an empty repository, '. + 'marking repository as importing.', + new PhutilNumber(PhabricatorRepository::IMPORT_THRESHOLD))); + + $repository->markImporting(); + } + // Clear the working set cache. $this->workingSet = array(); @@ -579,4 +616,30 @@ final class PhabricatorRepositoryDiscoveryEngine PhabricatorWorker::scheduleTask($class, $data); } + private function isInitialImport(array $refs) { + $commit_count = count($refs); + + if ($commit_count <= PhabricatorRepository::IMPORT_THRESHOLD) { + // If we fetched a small number of commits, assume it's an initial + // commit or a stack of a few initial commits. + return false; + } + + $viewer = $this->getViewer(); + $repository = $this->getRepository(); + + $any_commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($repository) + ->setLimit(1) + ->execute(); + + if ($any_commits) { + // If the repository already has commits, this isn't an import. + return false; + } + + return true; + } + } diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index 42ad650a38..445357e783 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -51,6 +51,27 @@ abstract class PhabricatorRepositoryEngine extends Phobject { return PhabricatorUser::getOmnipotentUser(); } + protected function newRepositoryLock( + PhabricatorRepository $repository, + $lock_key, + $lock_device_only) { + + $lock_parts = array(); + $lock_parts[] = $lock_key; + $lock_parts[] = $repository->getID(); + + if ($lock_device_only) { + $device = AlmanacKeys::getLiveDevice(); + if ($device) { + $lock_parts[] = $device->getID(); + } + } + + $lock_name = implode(':', $lock_parts); + return PhabricatorGlobalLock::newLock($lock_name); + } + + /** * Verify that the "origin" remote exists, and points at the correct URI. * diff --git a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php index e4e65aab6f..f07c301bf0 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php @@ -7,6 +7,7 @@ final class PhabricatorRepositoryMirrorEngine extends PhabricatorRepositoryEngine { public function pushToMirrors() { + $viewer = $this->getViewer(); $repository = $this->getRepository(); if (!$repository->canMirror()) { @@ -19,13 +20,24 @@ final class PhabricatorRepositoryMirrorEngine return; } - $mirrors = id(new PhabricatorRepositoryMirrorQuery()) - ->setViewer($this->getViewer()) - ->withRepositoryPHIDs(array($repository->getPHID())) + $uris = id(new PhabricatorRepositoryURIQuery()) + ->setViewer($viewer) + ->withRepositories(array($repository)) ->execute(); + $io_mirror = PhabricatorRepositoryURI::IO_MIRROR; + $exceptions = array(); - foreach ($mirrors as $mirror) { + foreach ($uris as $mirror) { + if ($mirror->getIsDisabled()) { + continue; + } + + $io_type = $mirror->getEffectiveIOType(); + if ($io_type != $io_mirror) { + continue; + } + try { $this->pushRepositoryToMirror($repository, $mirror); } catch (Exception $ex) { @@ -44,54 +56,56 @@ final class PhabricatorRepositoryMirrorEngine private function pushRepositoryToMirror( PhabricatorRepository $repository, - PhabricatorRepositoryMirror $mirror) { + PhabricatorRepositoryURI $mirror_uri) { - // TODO: This is a little bit janky, but we don't have first-class - // infrastructure for running remote commands against an arbitrary remote - // right now. Just make an emphemeral copy of the repository and muck with - // it a little bit. In the medium term, we should pull this command stuff - // out and use it here and for "Land to ...". + $this->log( + pht( + 'Pushing to remote "%s"...', + $mirror_uri->getEffectiveURI())); - $proxy = clone $repository; - $proxy->makeEphemeral(); - - $proxy->setDetail('hosting-enabled', false); - $proxy->setDetail('remote-uri', $mirror->getRemoteURI()); - $proxy->setCredentialPHID($mirror->getCredentialPHID()); - - $this->log(pht('Pushing to remote "%s"...', $mirror->getRemoteURI())); - - if ($proxy->isGit()) { - $this->pushToGitRepository($proxy); - } else if ($proxy->isHg()) { - $this->pushToHgRepository($proxy); + if ($repository->isGit()) { + $this->pushToGitRepository($repository, $mirror_uri); + } else if ($repository->isHg()) { + $this->pushToHgRepository($repository, $mirror_uri); } else { throw new Exception(pht('Unsupported VCS!')); } } private function pushToGitRepository( - PhabricatorRepository $proxy) { + PhabricatorRepository $repository, + PhabricatorRepositoryURI $mirror_uri) { - $future = $proxy->getRemoteCommandFuture( + $argv = array( 'push --verbose --mirror -- %P', - $proxy->getRemoteURIEnvelope()); + $mirror_uri->getURIEnvelope(), + ); + + $future = $mirror_uri->newCommandEngine() + ->setArgv($argv) + ->newFuture(); $future - ->setCWD($proxy->getLocalPath()) + ->setCWD($repository->getLocalPath()) ->resolvex(); } private function pushToHgRepository( - PhabricatorRepository $proxy) { + PhabricatorRepository $repository, + PhabricatorRepositoryURI $mirror_uri) { - $future = $proxy->getRemoteCommandFuture( + $argv = array( 'push --verbose --rev tip -- %P', - $proxy->getRemoteURIEnvelope()); + $mirror_uri->getURIEnvelope(), + ); + + $future = $mirror_uri->newCommandEngine() + ->setArgv($argv) + ->newFuture(); try { $future - ->setCWD($proxy->getLocalPath()) + ->setCWD($repository->getLocalPath()) ->resolvex(); } catch (CommandException $ex) { if (preg_match('/no changes found/', $ex->getStdOut())) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 621c718577..9e151ecd81 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -23,6 +23,33 @@ final class PhabricatorRepositoryPullEngine public function pullRepository() { $repository = $this->getRepository(); + + $lock = $this->newRepositoryLock($repository, 'repo.pull', true); + + try { + $lock->lock(); + } catch (PhutilLockException $ex) { + throw new DiffusionDaemonLockException( + pht( + 'Another process is currently updating repository "%s", '. + 'skipping pull.', + $repository->getDisplayName())); + } + + try { + $result = $this->pullRepositoryWithLock(); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return $result; + } + + private function pullRepositoryWithLock() { + $repository = $this->getRepository(); $viewer = PhabricatorUser::getOmnipotentUser(); $is_hg = false; diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php index 5a9be99212..c489d69424 100644 --- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php +++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php @@ -65,13 +65,12 @@ abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase { ->setCallsign($callsign) ->setName(pht('Test Repo "%s"', $callsign)) ->setVersionControlSystem($vcs_type) - ->setDetail('local-path', dirname($local).'/'.$callsign) + ->setLocalPath(dirname($local).'/'.$callsign) ->setDetail('remote-uri', 'file://'.$dir->getPath().'/'); $this->didConstructRepository($repo); $repo->save(); - $repo->makeEphemeral(); // Keep the disk resources around until we exit. $this->dirs[] = $dir; diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php index 03c4745841..c534edf907 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php @@ -27,7 +27,7 @@ final class PhabricatorRepositoryManagementDiscoverWorkflow } public function execute(PhutilArgumentParser $args) { - $repos = $this->loadRepositories($args, 'repos'); + $repos = $this->loadLocalRepositories($args, 'repos'); if (!$repos) { throw new PhutilArgumentUsageException( diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php deleted file mode 100644 index cc33f1021c..0000000000 --- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php +++ /dev/null @@ -1,135 +0,0 @@ -setName('edit') - ->setExamples('**edit** --as __username__ __repository__ ...') - ->setSynopsis( - pht( - 'Edit __repository__ (will eventually be deprecated by Conduit).')) - ->setArguments( - array( - array( - 'name' => 'repos', - 'wildcard' => true, - ), - array( - 'name' => 'as', - 'param' => 'user', - 'help' => pht('Edit as user.'), - ), - array( - 'name' => 'local-path', - 'param' => 'path', - 'help' => pht('Edit the local path.'), - ), - array( - 'name' => 'serve-http', - 'param' => 'string', - 'help' => pht('Edit the http serving policy.'), - ), - array( - 'name' => 'serve-ssh', - 'param' => 'string', - 'help' => pht('Edit the ssh serving policy.'), - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $repos = $this->loadRepositories($args, 'repos'); - - if (!$repos) { - throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to edit.')); - } - - $console = PhutilConsole::getConsole(); - - // TODO: It would be nice to just take this action as "Administrator" or - // similar, since that would make it easier to use this script, harder to - // impersonate users, and more clear to viewers what happened. However, - // the omnipotent user doesn't have a PHID right now, can't be loaded, - // doesn't have a handle, etc. Adding all of that is fairly involved, and - // I want to wait for stronger use cases first. - - $username = $args->getArg('as'); - if (!$username) { - throw new PhutilArgumentUsageException( - pht( - 'Specify a user to edit as with %s.', - '--as ')); - } - - $actor = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getViewer()) - ->withUsernames(array($username)) - ->executeOne(); - - if (!$actor) { - throw new PhutilArgumentUsageException( - pht("No such user '%s'!", $username)); - } - - foreach ($repos as $repo) { - $console->writeOut( - "%s\n", - pht( - 'Editing "%s"...', - $repo->getDisplayName())); - - $xactions = array(); - - $type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH; - $type_protocol_http = - PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; - $type_protocol_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; - $allowed_serve_modes = array( - PhabricatorRepository::SERVE_OFF, - PhabricatorRepository::SERVE_READONLY, - PhabricatorRepository::SERVE_READWRITE, - ); - - if ($args->getArg('local-path')) { - $xactions[] = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType($type_local_path) - ->setNewValue($args->getArg('local-path')); - } - $serve_http = $args->getArg('serve-http'); - if ($serve_http && in_array($serve_http, $allowed_serve_modes)) { - $xactions[] = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType($type_protocol_http) - ->setNewValue($serve_http); - } - $serve_ssh = $args->getArg('serve-ssh'); - if ($serve_ssh && in_array($serve_ssh, $allowed_serve_modes)) { - $xactions[] = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType($type_protocol_ssh) - ->setNewValue($serve_ssh); - } - - - if (!$xactions) { - throw new PhutilArgumentUsageException( - pht('Specify one or more fields to edit!')); - } - - $content_source = $this->newContentSource(); - - $editor = id(new PhabricatorRepositoryEditor()) - ->setActor($actor) - ->setContentSource($content_source) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->applyTransactions($repo, $xactions); - } - - $console->writeOut("%s\n", pht('Done.')); - - return 0; - } - -} diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php index 99a2b08c8b..caa7574424 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php @@ -12,18 +12,18 @@ final class PhabricatorRepositoryManagementMirrorWorkflow ->setArguments( array( array( - 'name' => 'verbose', - 'help' => pht('Show additional debugging information.'), + 'name' => 'verbose', + 'help' => pht('Show additional debugging information.'), ), array( - 'name' => 'repos', - 'wildcard' => true, + 'name' => 'repos', + 'wildcard' => true, ), )); } public function execute(PhutilArgumentParser $args) { - $repos = $this->loadRepositories($args, 'repos'); + $repos = $this->loadLocalRepositories($args, 'repos'); if (!$repos) { throw new PhutilArgumentUsageException( @@ -31,12 +31,11 @@ final class PhabricatorRepositoryManagementMirrorWorkflow 'Specify one or more repositories to push to mirrors.')); } - $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { - $console->writeOut( + echo tsprintf( "%s\n", pht( - "Pushing '%s' to mirrors...", + 'Pushing "%s" to mirrors...', $repo->getDisplayName())); $engine = id(new PhabricatorRepositoryMirrorEngine()) @@ -45,7 +44,9 @@ final class PhabricatorRepositoryManagementMirrorWorkflow ->pushToMirrors(); } - $console->writeOut("%s\n", pht('Done.')); + echo tsprintf( + "%s\n", + pht('Done.')); return 0; } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php index f0146847ae..baee437883 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php @@ -128,14 +128,12 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow } $repo = $row['repository']; - $details = $repo->getDetails(); - $details['local-path'] = $row['dst']; queryfx( $repo->establishConnection('w'), - 'UPDATE %T SET details = %s WHERE id = %d', + 'UPDATE %T SET localPath = %s WHERE id = %d', $repo->getTableName(), - phutil_json_encode($details), + $row['dst'], $repo->getID()); } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php index f41f8b95a1..808986f9aa 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php @@ -22,7 +22,7 @@ final class PhabricatorRepositoryManagementPullWorkflow } public function execute(PhutilArgumentParser $args) { - $repos = $this->loadRepositories($args, 'repos'); + $repos = $this->loadLocalRepositories($args, 'repos'); if (!$repos) { throw new PhutilArgumentUsageException( diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php index ad9bbbd0db..8c806edac8 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php @@ -22,7 +22,7 @@ final class PhabricatorRepositoryManagementRefsWorkflow } public function execute(PhutilArgumentParser $args) { - $repos = $this->loadRepositories($args, 'repos'); + $repos = $this->loadLocalRepositories($args, 'repos'); if (!$repos) { throw new PhutilArgumentUsageException( diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php index 9d987636b6..f435861c60 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php @@ -44,7 +44,7 @@ final class PhabricatorRepositoryManagementUpdateWorkflow $this->setVerbose($args->getArg('verbose')); $console = PhutilConsole::getConsole(); - $repos = $this->loadRepositories($args, 'repos'); + $repos = $this->loadLocalRepositories($args, 'repos'); if (count($repos) !== 1) { throw new PhutilArgumentUsageException( pht('Specify exactly one repository to update.')); @@ -53,60 +53,42 @@ final class PhabricatorRepositoryManagementUpdateWorkflow $repository = head($repos); try { - $lock_name = 'repository.update:'.$repository->getID(); - $lock = PhabricatorGlobalLock::newLock($lock_name); + id(new PhabricatorRepositoryPullEngine()) + ->setRepository($repository) + ->setVerbose($this->getVerbose()) + ->pullRepository(); - try { - $lock->lock(); - } catch (PhutilLockException $ex) { - throw new PhutilProxyException( - pht( - 'Another process is currently holding the update lock for '. - 'repository "%s". Repositories may only be updated by one '. - 'process at a time. This can happen if you are running multiple '. - 'copies of the daemons. This can also happen if you manually '. - 'update a repository while the daemons are also updating it '. - '(in this case, just try again in a few moments).', - $repository->getMonogram()), - $ex); + $no_discovery = $args->getArg('no-discovery'); + if ($no_discovery) { + return 0; } - try { - $no_discovery = $args->getArg('no-discovery'); + // TODO: It would be nice to discover only if we pulled something, but + // this isn't totally trivial. It's slightly more complicated with + // hosted repositories, too. - id(new PhabricatorRepositoryPullEngine()) - ->setRepository($repository) - ->setVerbose($this->getVerbose()) - ->pullRepository(); + $repository->writeStatusMessage( + PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, + null); - if ($no_discovery) { - $lock->unlock(); - return; - } + $this->discoverRepository($repository); - // TODO: It would be nice to discover only if we pulled something, but - // this isn't totally trivial. It's slightly more complicated with - // hosted repositories, too. + $this->checkIfRepositoryIsFullyImported($repository); - $repository->writeStatusMessage( - PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, - null); + $this->updateRepositoryRefs($repository); - $this->discoverRepository($repository); + $this->mirrorRepository($repository); - $this->checkIfRepositoryIsFullyImported($repository); - - $this->updateRepositoryRefs($repository); - - $this->mirrorRepository($repository); - - $repository->writeStatusMessage( - PhabricatorRepositoryStatusMessage::TYPE_FETCH, - PhabricatorRepositoryStatusMessage::CODE_OKAY); - } catch (Exception $ex) { - $lock->unlock(); - throw $ex; - } + $repository->writeStatusMessage( + PhabricatorRepositoryStatusMessage::TYPE_FETCH, + PhabricatorRepositoryStatusMessage::CODE_OKAY); + } catch (DiffusionDaemonLockException $ex) { + // If we miss a pull or discover because some other process is already + // doing the work, just bail out. + echo tsprintf( + "%s\n", + $ex->getMessage()); + return 0; } catch (Exception $ex) { $repository->writeStatusMessage( PhabricatorRepositoryStatusMessage::TYPE_FETCH, @@ -118,12 +100,11 @@ final class PhabricatorRepositoryManagementUpdateWorkflow throw $ex; } - $lock->unlock(); - - $console->writeOut( + echo tsprintf( + "%s\n", pht( - 'Updated repository **%s**.', - $repository->getMonogram())."\n"); + 'Updated repository "%s".', + $repository->getDisplayName())); return 0; } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php index 300f8ec50f..654a974e6c 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php @@ -12,6 +12,7 @@ abstract class PhabricatorRepositoryManagementWorkflow $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) + ->needURIs(true) ->withIdentifiers($identifiers); $query->execute(); @@ -32,6 +33,32 @@ abstract class PhabricatorRepositoryManagementWorkflow return array_values($repositories); } + protected function loadLocalRepositories( + PhutilArgumentParser $args, + $param) { + + $repositories = $this->loadRepositories($args, $param); + if (!$repositories) { + return $repositories; + } + + $device = AlmanacKeys::getLiveDevice(); + $viewer = $this->getViewer(); + + $filter = id(new DiffusionLocalRepositoryFilter()) + ->setViewer($viewer) + ->setDevice($device) + ->setRepositories($repositories); + + $repositories = $filter->execute(); + + foreach ($filter->getRejectionReasons() as $reason) { + throw new PhutilArgumentUsageException($reason); + } + + return $repositories; + } + protected function loadCommits(PhutilArgumentParser $args, $param) { $names = $args->getArg($param); if (!$names) { diff --git a/src/applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php deleted file mode 100644 index 42737c7281..0000000000 --- a/src/applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php +++ /dev/null @@ -1,41 +0,0 @@ -withPHIDs($phids); - } - - public function loadHandles( - PhabricatorHandleQuery $query, - array $handles, - array $objects) { - - foreach ($handles as $phid => $handle) { - $mirror = $objects[$phid]; - - $handle->setName( - pht('Mirror %d %s', $mirror->getID(), $mirror->getRemoteURI())); - $handle->setURI('/diffusion/mirror/'.$mirror->getID().'/'); - } - } - -} diff --git a/src/applications/repository/query/PhabricatorRepositoryMirrorQuery.php b/src/applications/repository/query/PhabricatorRepositoryMirrorQuery.php deleted file mode 100644 index 84c5ef74ce..0000000000 --- a/src/applications/repository/query/PhabricatorRepositoryMirrorQuery.php +++ /dev/null @@ -1,99 +0,0 @@ -ids = $ids; - return $this; - } - - public function withPHIDs(array $phids) { - $this->phids = $phids; - return $this; - } - - public function withRepositoryPHIDs(array $repository_phids) { - $this->repositoryPHIDs = $repository_phids; - return $this; - } - - protected function loadPage() { - $table = new PhabricatorRepositoryMirror(); - $conn_r = $table->establishConnection('r'); - - $data = 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($data); - } - - protected function willFilterPage(array $mirrors) { - assert_instances_of($mirrors, 'PhabricatorRepositoryMirror'); - - $repository_phids = mpull($mirrors, 'getRepositoryPHID'); - if ($repository_phids) { - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($repository_phids) - ->execute(); - $repositories = mpull($repositories, null, 'getPHID'); - } else { - $repositories = array(); - } - - foreach ($mirrors as $key => $mirror) { - $phid = $mirror->getRepositoryPHID(); - if (empty($repositories[$phid])) { - unset($mirrors[$key]); - continue; - } - $mirror->attachRepository($repositories[$phid]); - } - - return $mirrors; - } - - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); - - if ($this->ids) { - $where[] = qsprintf( - $conn_r, - 'id IN (%Ld)', - $this->ids); - } - - if ($this->phids) { - $where[] = qsprintf( - $conn_r, - 'phid IN (%Ls)', - $this->phids); - } - - if ($this->repositoryPHIDs) { - $where[] = qsprintf( - $conn_r, - 'repositoryPHID IN (%Ls)', - $this->repositoryPHIDs); - } - - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); - } - - public function getQueryApplicationClass() { - return 'PhabricatorDiffusionApplication'; - } - -} diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 78693d54d6..b67b3c5610 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -650,7 +650,7 @@ final class PhabricatorRepositoryQuery } if ($this->uris !== null) { - $try_uris = $this->getNormalizedPaths(); + $try_uris = $this->getNormalizedURIs(); $try_uris = array_fuse($try_uris); $where[] = qsprintf( @@ -666,7 +666,7 @@ final class PhabricatorRepositoryQuery return 'PhabricatorDiffusionApplication'; } - private function getNormalizedPaths() { + private function getNormalizedURIs() { $normalized_uris = array(); // Since we don't know which type of repository this URI is in the general @@ -675,19 +675,15 @@ final class PhabricatorRepositoryQuery // or an `svn+ssh` URI, we could deduce how to normalize it. However, this // would be more complicated and it's not clear if it matters in practice. + $types = PhabricatorRepositoryURINormalizer::getAllURITypes(); foreach ($this->uris as $uri) { - $normalized_uris[] = new PhabricatorRepositoryURINormalizer( - PhabricatorRepositoryURINormalizer::TYPE_GIT, - $uri); - $normalized_uris[] = new PhabricatorRepositoryURINormalizer( - PhabricatorRepositoryURINormalizer::TYPE_SVN, - $uri); - $normalized_uris[] = new PhabricatorRepositoryURINormalizer( - PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, - $uri); + foreach ($types as $type) { + $normalized_uri = new PhabricatorRepositoryURINormalizer($type, $uri); + $normalized_uris[] = $normalized_uri->getNormalizedURI(); + } } - return array_unique(mpull($normalized_uris, 'getNormalizedPath')); + return array_unique($normalized_uris); } } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 059ae9a76d..78f29fad1a 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -38,6 +38,11 @@ final class PhabricatorRepositorySearchEngine ->setLabel(pht('Types')) ->setKey('types') ->setOptions(PhabricatorRepositoryType::getAllRepositoryTypes()), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('URIs')) + ->setKey('uris') + ->setDescription( + pht('Search for repositories by clone/checkout URI.')), ); } @@ -70,6 +75,10 @@ final class PhabricatorRepositorySearchEngine $query->withNameContains($map['name']); } + if ($map['uris']) { + $query->withURIs($map['uris']); + } + return $query; } @@ -242,16 +251,10 @@ final class PhabricatorRepositorySearchEngine protected function getNewUserBody() { - $import_button = id(new PHUIButtonView()) + $new_button = id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Import Repository')) - ->setHref('/diffusion/import/') - ->setColor(PHUIButtonView::GREEN); - - $create_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Create Repository')) - ->setHref('/diffusion/create/') + ->setText(pht('New Repository')) + ->setHref('/diffusion/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); @@ -261,8 +264,7 @@ final class PhabricatorRepositorySearchEngine ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Import, create, or just browse repositories in Diffusion.')) - ->addAction($import_button) - ->addAction($create_button); + ->addAction($new_button); return $view; } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index be77852be8..bc786e46a1 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -26,6 +26,11 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO */ const MINIMUM_QUALIFIED_HASH = 5; + /** + * Minimum number of commits to an empty repository to trigger "import" mode. + */ + const IMPORT_THRESHOLD = 7; + const TABLE_PATH = 'repository_path'; const TABLE_PATHCHANGE = 'repository_pathchange'; const TABLE_FILESYSTEM = 'repository_filesystem'; @@ -35,10 +40,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO const TABLE_PARENTS = 'repository_parents'; const TABLE_COVERAGE = 'repository_coverage'; - const SERVE_OFF = 'off'; - const SERVE_READONLY = 'readonly'; - const SERVE_READWRITE = 'readwrite'; - const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing'; const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled'; const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch'; @@ -62,6 +63,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO protected $credentialPHID; protected $almanacServicePHID; protected $spacePHID; + protected $localPath; private $commitCount = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE; @@ -107,6 +109,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'pushPolicy' => 'policy', 'credentialPHID' => 'phid?', 'almanacServicePHID' => 'phid?', + 'localPath' => 'text128?', ), self::CONFIG_KEY_SCHEMA => array( 'callsign' => array( @@ -123,6 +126,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'columns' => array('repositorySlug'), 'unique' => true, ), + 'key_local' => array( + 'columns' => array('localPath'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -216,6 +223,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $monograms; } + public function setLocalPath($path) { + // Convert any extra slashes ("//") in the path to a single slash ("/"). + $path = preg_replace('(//+)', '/', $path); + + return parent::setLocalPath($path); + } + public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } @@ -279,10 +293,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO )); } - public function getLocalPath() { - return $this->getDetail('local-path'); - } - public function getSubversionBaseURI($commit = null) { $subpath = $this->getDetail('svn-subpath'); if (!strlen($subpath)) { @@ -349,7 +359,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO if (!strlen($name)) { $name = $this->getName(); $name = phutil_utf8_strtolower($name); - $name = preg_replace('@[/ -:]+@', '-', $name); + $name = preg_replace('@[/ -:<>]+@', '-', $name); $name = trim($name, '-'); if (!strlen($name)) { $name = $this->getCallsign(); @@ -798,41 +808,24 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } public function updateURIIndex() { - $uris = array( - (string)$this->getCloneURIObject(), - ); + $indexes = array(); - foreach ($uris as $key => $uri) { - $uris[$key] = $this->getNormalizedURI($uri) - ->getNormalizedPath(); + $uris = $this->getURIs(); + foreach ($uris as $uri) { + if ($uri->getIsDisabled()) { + continue; + } + + $indexes[] = $uri->getNormalizedURI(); } PhabricatorRepositoryURIIndex::updateRepositoryURIs( $this->getPHID(), - $uris); + $indexes); return $this; } - private function getNormalizedURI($uri) { - switch ($this->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - return new PhabricatorRepositoryURINormalizer( - PhabricatorRepositoryURINormalizer::TYPE_GIT, - $uri); - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - return new PhabricatorRepositoryURINormalizer( - PhabricatorRepositoryURINormalizer::TYPE_SVN, - $uri); - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - return new PhabricatorRepositoryURINormalizer( - PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, - $uri); - default: - throw new Exception(pht('Unrecognized version control system.')); - } - } - public function isTracked() { $status = $this->getDetail('tracking-enabled'); $map = self::getStatusMap(); @@ -959,6 +952,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return (bool)$this->getDetail('importing', false); } + public function isNewlyInitialized() { + return (bool)$this->getDetail('newly-initialized', false); + } + public function loadImportProgress() { $progress = queryfx_all( $this->establishConnection('r'), @@ -1192,26 +1189,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO * @task uri */ public function getPublicCloneURI() { - $uri = $this->getCloneURIObject(); - - // Make sure we don't leak anything if this repo is using HTTP Basic Auth - // with the credentials in the URI or something zany like that. - - // If repository is not accessed over SSH we remove both username and - // password. - if (!$this->isHosted()) { - if (!$this->shouldUseSSH()) { - $uri->setUser(null); - - // This might be a Git URI or a normal URI. If it's Git, there's no - // password support. - if ($uri instanceof PhutilURI) { - $uri->setPass(null); - } - } - } - - return (string)$uri; + return (string)$this->getCloneURIObject(); } @@ -1294,91 +1272,19 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } } - // Choose the best URI: pick a read/write URI over a URI which is not - // read/write, and SSH over HTTP. + // TODO: This should be cleaned up to deal with all the new URI handling. + $another_copy = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($this->getPHID())) + ->needURIs(true) + ->executeOne(); - $serve_ssh = $this->getServeOverSSH(); - $serve_http = $this->getServeOverHTTP(); - - if ($serve_ssh === self::SERVE_READWRITE) { - return $this->getSSHCloneURIObject(); - } else if ($serve_http === self::SERVE_READWRITE) { - return $this->getHTTPCloneURIObject(); - } else if ($serve_ssh !== self::SERVE_OFF) { - return $this->getSSHCloneURIObject(); - } else if ($serve_http !== self::SERVE_OFF) { - return $this->getHTTPCloneURIObject(); - } else { - return null; - } - } - - - /** - * Get the repository's SSH clone/checkout URI, if one exists. - */ - public function getSSHCloneURIObject() { - if (!$this->isHosted()) { - if ($this->shouldUseSSH()) { - return $this->getRemoteURIObject(); - } else { - return null; - } - } - - $serve_ssh = $this->getServeOverSSH(); - if ($serve_ssh === self::SERVE_OFF) { + $clone_uris = $another_copy->getCloneURIs(); + if (!$clone_uris) { return null; } - $uri = new PhutilURI(PhabricatorEnv::getProductionURI($this->getURI())); - - if ($this->isSVN()) { - $uri->setProtocol('svn+ssh'); - } else { - $uri->setProtocol('ssh'); - } - - if ($this->isGit()) { - $uri->setPath($uri->getPath().$this->getCloneName().'.git'); - } else if ($this->isHg()) { - $uri->setPath($uri->getPath().$this->getCloneName().'/'); - } - - $ssh_user = AlmanacKeys::getClusterSSHUser(); - if (strlen($ssh_user)) { - $uri->setUser($ssh_user); - } - - $ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host'); - if (strlen($ssh_host)) { - $uri->setDomain($ssh_host); - } - - $uri->setPort(PhabricatorEnv::getEnvConfig('diffusion.ssh-port')); - - return $uri; - } - - - /** - * Get the repository's HTTP clone/checkout URI, if one exists. - */ - public function getHTTPCloneURIObject() { - if (!$this->isHosted()) { - if ($this->shouldUseHTTP()) { - return $this->getRemoteURIObject(); - } else { - return null; - } - } - - $serve_http = $this->getServeOverHTTP(); - if ($serve_http === self::SERVE_OFF) { - return null; - } - - return $this->getRawHTTPCloneURIObject(); + return head($clone_uris)->getEffectiveURI(); } private function getRawHTTPCloneURIObject() { @@ -1484,10 +1390,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $commit->delete(); } - $mirrors = id(new PhabricatorRepositoryMirror()) + $uris = id(new PhabricatorRepositoryURI()) ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); - foreach ($mirrors as $mirror) { - $mirror->delete(); + foreach ($uris as $uri) { + $uri->delete(); } $ref_cursors = id(new PhabricatorRepositoryRefCursor()) @@ -1545,56 +1451,40 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $this->setDetail('hosting-enabled', $enabled); } - public function getServeOverHTTP() { - if ($this->isSVN()) { - return self::SERVE_OFF; + public function canServeProtocol($protocol, $write) { + if (!$this->isTracked()) { + return false; } - $serve = $this->getDetail('serve-over-http', self::SERVE_OFF); - return $this->normalizeServeConfigSetting($serve); - } - public function setServeOverHTTP($mode) { - return $this->setDetail('serve-over-http', $mode); - } + $clone_uris = $this->getCloneURIs(); + foreach ($clone_uris as $uri) { + if ($uri->getBuiltinProtocol() !== $protocol) { + continue; + } - public function getServeOverSSH() { - $serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF); - return $this->normalizeServeConfigSetting($serve); - } + $io_type = $uri->getEffectiveIoType(); + if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) { + return true; + } - public function setServeOverSSH($mode) { - return $this->setDetail('serve-over-ssh', $mode); - } - - public static function getProtocolAvailabilityName($constant) { - switch ($constant) { - case self::SERVE_OFF: - return pht('Off'); - case self::SERVE_READONLY: - return pht('Read Only'); - case self::SERVE_READWRITE: - return pht('Read/Write'); - default: - return pht('Unknown'); - } - } - - private function normalizeServeConfigSetting($value) { - switch ($value) { - case self::SERVE_OFF: - case self::SERVE_READONLY: - return $value; - case self::SERVE_READWRITE: - if ($this->isHosted()) { - return self::SERVE_READWRITE; - } else { - return self::SERVE_READONLY; + if (!$write) { + if ($io_type == PhabricatorRepositoryURI::IO_READ) { + return true; } - default: - return self::SERVE_OFF; + } } + + return false; } + public function hasLocalWorkingCopy() { + try { + self::assertLocalExists(); + return true; + } catch (Exception $ex) { + return false; + } + } /** * Raise more useful errors when there are basic filesystem problems. @@ -2124,10 +2014,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO continue; } - // If we don't have a persisted version of the URI, add the builtin - // version. - if (empty($custom_map[$builtin_key])) { - $uris[] = $builtin_uri; + // If the URI exists, make sure it's marked as not being disabled. + if (isset($custom_map[$builtin_key])) { + $uris[$custom_map[$builtin_key]]->setIsDisabled(false); } } @@ -2147,10 +2036,42 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $this->assertAttached($this->uris); } - protected function newBuiltinURIs() { + public function getCloneURIs() { + $uris = $this->getURIs(); + + $clone = array(); + foreach ($uris as $uri) { + if (!$uri->isBuiltin()) { + continue; + } + + if ($uri->getIsDisabled()) { + continue; + } + + $io_type = $uri->getEffectiveIoType(); + $is_clone = + ($io_type == PhabricatorRepositoryURI::IO_READ) || + ($io_type == PhabricatorRepositoryURI::IO_READWRITE); + + if (!$is_clone) { + continue; + } + + $clone[] = $uri; + } + + return msort($clone, 'getURIScore'); + } + + + public function newBuiltinURIs() { $has_callsign = ($this->getCallsign() !== null); $has_shortname = ($this->getRepositorySlug() !== null); + // TODO: For now, never enable these because they don't work yet. + $has_shortname = false; + $identifier_map = array( PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_CALLSIGN => $has_callsign, PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_SHORTNAME => $has_shortname, @@ -2167,8 +2088,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $has_http = !PhabricatorEnv::getEnvConfig('security.require-https'); $has_http = ($has_http && $allow_http); - // TODO: Maybe allow users to disable this by default somehow? - $has_ssh = true; + // HTTP is not supported for Subversion. + if ($this->isSVN()) { + $has_http = false; + $has_https = false; + } + + $has_ssh = (bool)strlen(PhabricatorEnv::getEnvConfig('phd.user')); $protocol_map = array( PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH => $has_ssh, @@ -2179,12 +2105,16 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $uris = array(); foreach ($protocol_map as $protocol => $proto_supported) { foreach ($identifier_map as $identifier => $id_supported) { + // This is just a dummy value because it can't be empty; we'll force + // it to a proper value when using it in the UI. + $builtin_uri = "{$protocol}://{$identifier}"; $uris[] = PhabricatorRepositoryURI::initializeNewURI() ->setRepositoryPHID($this->getPHID()) ->attachRepository($this) ->setBuiltinProtocol($protocol) ->setBuiltinIdentifier($identifier) - ->setIsDisabled(!$proto_supported || !$id_supported); + ->setURI($builtin_uri) + ->setIsDisabled((int)(!$proto_supported || !$id_supported)); } } @@ -2239,6 +2169,18 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $service; } + public function markImporting() { + $this->openTransaction(); + $this->beginReadLocking(); + $repository = $this->reload(); + $repository->setDetail('importing', true); + $repository->save(); + $this->endReadLocking(); + $this->saveTransaction(); + + return $repository; + } + /* -( Symbols )-------------------------------------------------------------*/ diff --git a/src/applications/repository/storage/PhabricatorRepositoryMirror.php b/src/applications/repository/storage/PhabricatorRepositoryMirror.php index a5058ab345..d124297bcc 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryMirror.php +++ b/src/applications/repository/storage/PhabricatorRepositoryMirror.php @@ -1,19 +1,17 @@ setRemoteURI(''); - } - protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -29,41 +27,4 @@ final class PhabricatorRepositoryMirror extends PhabricatorRepositoryDAO ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhabricatorRepositoryMirrorPHIDType::TYPECONST); - } - - public function attachRepository(PhabricatorRepository $repository) { - $this->repository = $repository; - return $this; - } - - public function getRepository() { - return $this->assertAttached($this->repository); - } - - -/* -( PhabricatorPolicyInterface )----------------------------------------- */ - - - public function getCapabilities() { - return array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - ); - } - - public function getPolicy($capability) { - return $this->getRepository()->getPolicy($capability); - } - - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getRepository()->hasAutomaticCapability($capability, $viewer); - } - - public function describeAutomaticCapability($capability) { - return null; - } - } diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index 76e75a70b0..e02b670f75 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -351,15 +351,15 @@ final class PhabricatorRepositoryTransaction '%s changed the availability of this repository over HTTP from '. '"%s" to "%s".', $this->renderHandleLink($author_phid), - PhabricatorRepository::getProtocolAvailabilityName($old), - PhabricatorRepository::getProtocolAvailabilityName($new)); + $old, + $new); case self::TYPE_PROTOCOL_SSH: return pht( '%s changed the availability of this repository over SSH from '. '"%s" to "%s".', $this->renderHandleLink($author_phid), - PhabricatorRepository::getProtocolAvailabilityName($old), - PhabricatorRepository::getProtocolAvailabilityName($new)); + $old, + $new); case self::TYPE_PUSH_POLICY: return pht( '%s changed the push policy of this repository from "%s" to "%s".', diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 7e7741e372..53b9040453 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -193,11 +193,121 @@ final class PhabricatorRepositoryURI public function getDisplayURI() { - $uri = new PhutilURI($this->getURI()); + return $this->getURIObject(false); + } - $protocol = $this->getForcedProtocol(); - if ($protocol) { - $uri->setProtocol($protocol); + public function getNormalizedURI() { + $vcs = $this->getRepository()->getVersionControlSystem(); + + $map = array( + PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => + PhabricatorRepositoryURINormalizer::TYPE_GIT, + PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => + PhabricatorRepositoryURINormalizer::TYPE_SVN, + PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => + PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, + ); + + $type = $map[$vcs]; + $display = (string)$this->getDisplayURI(); + + $normal_uri = new PhabricatorRepositoryURINormalizer($type, $display); + + return $normal_uri->getNormalizedURI(); + } + + public function getEffectiveURI() { + return $this->getURIObject(true); + } + + public function getURIEnvelope() { + $uri = $this->getEffectiveURI(); + + $command_engine = $this->newCommandEngine(); + + $is_http = $command_engine->isAnyHTTPProtocol(); + // For SVN, we use `--username` and `--password` flags separately in the + // CommandEngine, so we don't need to add any credentials here. + $is_svn = $this->getRepository()->isSVN(); + $credential_phid = $this->getCredentialPHID(); + + if ($is_http && !$is_svn && $credential_phid) { + $key = PassphrasePasswordKey::loadFromPHID( + $credential_phid, + PhabricatorUser::getOmnipotentUser()); + + $uri->setUser($key->getUsernameEnvelope()->openEnvelope()); + $uri->setPass($key->getPasswordEnvelope()->openEnvelope()); + } + + return new PhutilOpaqueEnvelope((string)$uri); + } + + private function getURIObject($normalize) { + // Users can provide Git/SCP-style URIs in the form "user@host:path". + // These are equivalent to "ssh://user@host/path". We use the more standard + // form internally, and convert to it if we need to specify a port number, + // but try to preserve what the user typed when displaying the URI. + + if ($this->isBuiltin()) { + $builtin_protocol = $this->getForcedProtocol(); + $builtin_domain = $this->getForcedHost(); + $raw_uri = "{$builtin_protocol}://{$builtin_domain}"; + } else { + $raw_uri = $this->getURI(); + } + + $port = $this->getForcedPort(); + + $default_ports = array( + 'ssh' => 22, + 'http' => 80, + 'https' => 443, + ); + + $uri = new PhutilURI($raw_uri); + + // Make sure to remove any password from the URI before we do anything + // with it; this should always be provided by the associated credential. + $uri->setPass(null); + + if (!$uri->getProtocol()) { + $git_uri = new PhutilGitURI($raw_uri); + + // We must normalize this Git-style URI into a normal URI + $must_normalize = ($port && ($port != $default_ports['ssh'])); + if ($must_normalize || $normalize) { + $domain = $git_uri->getDomain(); + + + $uri = id(new PhutilURI("ssh://{$domain}")) + ->setUser($git_uri->getUser()) + ->setPath($git_uri->getPath()); + } else { + $uri = $git_uri; + } + } + + $is_normal = ($uri instanceof PhutilURI); + + if ($is_normal) { + $protocol = $this->getForcedProtocol(); + if ($protocol) { + $uri->setProtocol($protocol); + } + + if ($port) { + $uri->setPort($port); + } + + // Remove any explicitly set default ports. + $uri_port = $uri->getPort(); + $uri_protocol = $uri->getProtocol(); + + $uri_default = idx($default_ports, $uri_protocol); + if ($uri_default && ($uri_default == $uri_port)) { + $uri->setPort(null); + } } $user = $this->getForcedUser(); @@ -210,11 +320,6 @@ final class PhabricatorRepositoryURI $uri->setDomain($host); } - $port = $this->getForcedPort(); - if ($port) { - $uri->setPort($port); - } - $path = $this->getForcedPath(); if ($path) { $uri->setPath($path); @@ -223,10 +328,17 @@ final class PhabricatorRepositoryURI return $uri; } + private function getForcedProtocol() { + $repository = $this->getRepository(); + switch ($this->getBuiltinProtocol()) { case self::BUILTIN_PROTOCOL_SSH: - return 'ssh'; + if ($repository->isSVN()) { + return 'svn+ssh'; + } else { + return 'ssh'; + } case self::BUILTIN_PROTOCOL_HTTP: return 'http'; case self::BUILTIN_PROTOCOL_HTTPS: @@ -296,6 +408,7 @@ final class PhabricatorRepositoryURI $suffix = '/'; } else { $suffix = ''; + $clone_name = ''; } switch ($this->getBuiltinIdentifier()) { @@ -328,7 +441,7 @@ final class PhabricatorRepositoryURI if ($this->isBuiltin()) { $options[] = self::IO_READ; - $options[] = self::IO_WRITE; + $options[] = self::IO_READWRITE; } else { $options[] = self::IO_OBSERVE; $options[] = self::IO_MIRROR; @@ -446,6 +559,35 @@ final class PhabricatorRepositoryURI ); } + public function newCommandEngine() { + $repository = $this->getRepository(); + $protocol = $this->getEffectiveURI()->getProtocol(); + + return DiffusionCommandEngine::newCommandEngine($repository) + ->setCredentialPHID($this->getCredentialPHID()) + ->setProtocol($protocol); + } + + public function getURIScore() { + $score = 0; + + $io_points = array( + self::IO_READWRITE => 20, + self::IO_READ => 10, + ); + $score += idx($io_points, $this->getEffectiveIoType(), 0); + + $protocol_points = array( + self::BUILTIN_PROTOCOL_SSH => 3, + self::BUILTIN_PROTOCOL_HTTPS => 2, + self::BUILTIN_PROTOCOL_HTTP => 1, + ); + $score += idx($protocol_points, $this->getBuiltinProtocol(), 0); + + return $score; + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -569,7 +711,9 @@ final class PhabricatorRepositoryURI 'repositoryPHID' => $this->getRepositoryPHID(), 'uri' => array( 'raw' => $this->getURI(), - 'effective' => (string)$this->getDisplayURI(), + 'display' => (string)$this->getDisplayURI(), + 'effective' => (string)$this->getEffectiveURI(), + 'normalized' => (string)$this->getNormalizedURI(), ), 'io' => array( 'raw' => $this->getIOType(), diff --git a/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php b/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php index 034aa48d7c..241a95dad9 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php @@ -18,6 +18,26 @@ final class PhabricatorRepositoryURITransaction return PhabricatorRepositoryURIPHIDType::TYPECONST; } + public function getRequiredHandlePHIDs() { + $phids = parent::getRequiredHandlePHIDs(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_CREDENTIAL: + if ($old) { + $phids[] = $old; + } + if ($new) { + $phids[] = $new; + } + break; + } + + return $phids; + } + public function getTitle() { $author_phid = $this->getAuthorPHID(); @@ -61,6 +81,24 @@ final class PhabricatorRepositoryURITransaction '%s enabled this URI.', $this->renderHandleLink($author_phid)); } + case self::TYPE_CREDENTIAL: + if ($old && $new) { + return pht( + '%s changed the credential for this URI from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } else if ($old) { + return pht( + '%s removed %s as the credential for this URI.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old)); + } else if ($new) { + return pht( + '%s set the credential for this URI to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($new)); + } } diff --git a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php index c6d2278262..63bae03d80 100644 --- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php +++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php @@ -70,12 +70,12 @@ final class PhabricatorRepositoryTestCase $repo->setDetail('hosting-enabled', true); - $repo->setDetail('local-path', '/var/repo/SVN'); + $repo->setLocalPath('/var/repo/SVN'); $this->assertEqual( 'file:///var/repo/SVN', $repo->getSubversionPathURI()); - $repo->setDetail('local-path', '/var/repo/SVN/'); + $repo->setLocalPath('/var/repo/SVN/'); $this->assertEqual( 'file:///var/repo/SVN', $repo->getSubversionPathURI()); diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php index 5756c4e880..61a615950d 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php @@ -51,6 +51,11 @@ final class PhabricatorRepositoryCommitOwnersWorker continue; } + if ($package->isArchived()) { + // Don't trigger audits if the package is archived. + continue; + } + if ($package->getAuditingEnabled()) { $reasons = $this->checkAuditReasons($commit, $package); if ($reasons) { diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index 271fd1afb4..d8c4982ccc 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -113,7 +113,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { public static function filterMonospacedCSSRule($monospaced) { // Prevent the user from doing dangerous things. - return preg_replace('/[^a-z0-9 ,".]+/i', '', $monospaced); + return preg_replace('([^a-z0-9 ,"./]+)i', '', $monospaced); } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 7c9c0fbed1..1717b2cc7a 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -24,6 +24,8 @@ abstract class PhabricatorEditEngine private $editEngineConfiguration; private $contextParameters = array(); private $targetObject; + private $page; + private $pages; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -146,6 +148,8 @@ abstract class PhabricatorEditEngine $fields = $this->willConfigureFields($object, $fields); $fields = $config->applyConfigurationToFields($this, $object, $fields); + $fields = $this->applyPageToFields($object, $fields); + return $fields; } @@ -492,6 +496,34 @@ abstract class PhabricatorEditEngine return implode('', $parts); } + public function getEffectiveObjectViewURI($object) { + if ($this->getIsCreate()) { + return $this->getObjectViewURI($object); + } + + $page = $this->getSelectedPage(); + if ($page) { + $view_uri = $page->getViewURI(); + if ($view_uri !== null) { + return $view_uri; + } + } + + return $this->getObjectViewURI($object); + } + + public function getEffectiveObjectEditCancelURI($object) { + $page = $this->getSelectedPage(); + if ($page) { + $view_uri = $page->getViewURI(); + if ($view_uri !== null) { + return $view_uri; + } + } + + return $this->getObjectEditCancelURI($object); + } + /* -( Creating and Loading Objects )--------------------------------------- */ @@ -810,6 +842,21 @@ abstract class PhabricatorEditEngine return $this->buildDisabledFormResponse($object, $config); } + $page_key = $request->getURIData('pageKey'); + if (!strlen($page_key)) { + $pages = $this->getPages($object); + if ($pages) { + $page_key = head_key($pages); + } + } + + if (strlen($page_key)) { + $page = $this->selectPage($object, $page_key); + if (!$page) { + return new Aphront404Response(); + } + } + switch ($action) { case 'parameters': return $this->buildParametersResponse($object); @@ -841,7 +888,7 @@ abstract class PhabricatorEditEngine } else { $crumbs->addTextCrumb( $this->getObjectEditShortText($object), - $this->getObjectViewURI($object)); + $this->getEffectiveObjectViewURI($object)); $edit_text = pht('Edit'); if ($final) { @@ -1029,7 +1076,7 @@ abstract class PhabricatorEditEngine $cancel_uri = $this->getObjectCreateCancelURI($object); $submit_button = $this->getObjectCreateButtonText($object); } else { - $cancel_uri = $this->getObjectEditCancelURI($object); + $cancel_uri = $this->getEffectiveObjectEditCancelURI($object); $submit_button = $this->getObjectEditButtonText($object); } @@ -1079,7 +1126,7 @@ abstract class PhabricatorEditEngine $object, array $xactions) { return id(new AphrontRedirectResponse()) - ->setURI($this->getObjectViewURI($object)); + ->setURI($this->getEffectiveObjectViewURI($object)); } private function buildEditForm($object, array $fields) { @@ -1103,7 +1150,7 @@ abstract class PhabricatorEditEngine $cancel_uri = $this->getObjectCreateCancelURI($object); $submit_button = $this->getObjectCreateButtonText($object); } else { - $cancel_uri = $this->getObjectEditCancelURI($object); + $cancel_uri = $this->getEffectiveObjectEditCancelURI($object); $submit_button = $this->getObjectEditButtonText($object); } @@ -1547,7 +1594,7 @@ abstract class PhabricatorEditEngine $fields = $this->buildEditFields($object); $is_preview = $request->isPreviewRequest(); - $view_uri = $this->getObjectViewURI($object); + $view_uri = $this->getEffectiveObjectViewURI($object); $template = $object->getApplicationTransactionTemplate(); $comment_template = $template->getApplicationTransactionCommentObject(); @@ -1955,6 +2002,87 @@ abstract class PhabricatorEditEngine PhabricatorPolicyCapability::CAN_EDIT); } +/* -( Form Pages )--------------------------------------------------------- */ + + + public function getSelectedPage() { + return $this->page; + } + + + private function selectPage($object, $page_key) { + $pages = $this->getPages($object); + + if (empty($pages[$page_key])) { + return null; + } + + $this->page = $pages[$page_key]; + return $this->page; + } + + + protected function newPages($object) { + return array(); + } + + + protected function getPages($object) { + if ($this->pages === null) { + $pages = $this->newPages($object); + + assert_instances_of($pages, 'PhabricatorEditPage'); + $pages = mpull($pages, null, 'getKey'); + + $this->pages = $pages; + } + + return $this->pages; + } + + private function applyPageToFields($object, array $fields) { + $pages = $this->getPages($object); + if (!$pages) { + return $fields; + } + + if (!$this->getSelectedPage()) { + return $fields; + } + + $page_picks = array(); + $default_key = head($pages)->getKey(); + foreach ($pages as $page_key => $page) { + foreach ($page->getFieldKeys() as $field_key) { + $page_picks[$field_key] = $page_key; + } + if ($page->getIsDefault()) { + $default_key = $page_key; + } + } + + $page_map = array_fill_keys(array_keys($pages), array()); + foreach ($fields as $field_key => $field) { + if (isset($page_picks[$field_key])) { + $page_map[$page_picks[$field_key]][$field_key] = $field; + continue; + } + + // TODO: Maybe let the field pick a page to associate itself with so + // extensions can force themselves onto a particular page? + + $page_map[$default_key][$field_key] = $field; + } + + $page = $this->getSelectedPage(); + if (!$page) { + $page = head($pages); + } + + $selected_key = $page->getKey(); + return $page_map[$selected_key]; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/transactions/editengine/PhabricatorEditPage.php b/src/applications/transactions/editengine/PhabricatorEditPage.php new file mode 100644 index 0000000000..3669ef4494 --- /dev/null +++ b/src/applications/transactions/editengine/PhabricatorEditPage.php @@ -0,0 +1,58 @@ +key = $key; + return $this; + } + + public function getKey() { + return $this->key; + } + + public function setLabel($label) { + $this->label = $label; + return $this; + } + + public function getLabel() { + return $this->label; + } + + public function setFieldKeys(array $field_keys) { + $this->fieldKeys = $field_keys; + return $this; + } + + public function getFieldKeys() { + return $this->fieldKeys; + } + + public function setIsDefault($is_default) { + $this->isDefault = $is_default; + return $this; + } + + public function getIsDefault() { + return $this->isDefault; + } + + public function setViewURI($view_uri) { + $this->viewURI = $view_uri; + return $this; + } + + public function getViewURI() { + return $this->viewURI; + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 3b4c4e264b..6ff84da870 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -16,6 +16,7 @@ abstract class PhabricatorEditField extends Phobject { private $isRequired; private $previewPanel; private $controlID; + private $controlInstructions; private $description; private $conduitDescription; @@ -272,6 +273,15 @@ abstract class PhabricatorEditField extends Phobject { return $this->previewPanel; } + public function setControlInstructions($control_instructions) { + $this->controlInstructions = $control_instructions; + return $this; + } + + public function getControlInstructions() { + return $this->controlInstructions; + } + protected function newControl() { throw new PhutilMethodNotImplementedException(); } @@ -358,6 +368,11 @@ abstract class PhabricatorEditField extends Phobject { } } + $instructions = $this->getControlInstructions(); + if (strlen($instructions)) { + $form->appendRemarkupInstructions($instructions); + } + $form->appendControl($control); } return $this; diff --git a/src/applications/transactions/editfield/PhabricatorTextAreaEditField.php b/src/applications/transactions/editfield/PhabricatorTextAreaEditField.php index 4c07b31e54..4d4e3a75b8 100644 --- a/src/applications/transactions/editfield/PhabricatorTextAreaEditField.php +++ b/src/applications/transactions/editfield/PhabricatorTextAreaEditField.php @@ -51,7 +51,11 @@ final class PhabricatorTextAreaEditField protected function getValueForControl() { $value = $this->getValue(); - return implode("\n", $value); + if ($this->getIsStringList()) { + return implode("\n", $value); + } else { + return $value; + } } protected function newConduitParameterType() { diff --git a/src/docs/contributor/describing_problems.diviner b/src/docs/contributor/describing_problems.diviner index d6ba414502..57bb849b02 100644 --- a/src/docs/contributor/describing_problems.diviner +++ b/src/docs/contributor/describing_problems.diviner @@ -156,4 +156,4 @@ Next Steps Continue by: - - returning to @{article:Contribuing Feature Requests}. + - returning to @{article:Contributing Feature Requests}. diff --git a/src/docs/user/cluster/cluster.diviner b/src/docs/user/cluster/cluster.diviner index 0180c7ff42..d78c445c4a 100644 --- a/src/docs/user/cluster/cluster.diviner +++ b/src/docs/user/cluster/cluster.diviner @@ -4,11 +4,13 @@ Guide to configuring Phabricator across multiple hosts for availability and performance. + Overview ======== -WARNING: This feature is a very early prototype; the features this document -describes are mostly speculative fantasy. +WARNING: This feature is a prototype. Installs should expect a challening +adventure when deploying clusters. In the best of times, configuring a +cluster is complex and requires significant operations experience. Phabricator can be configured to run on multiple hosts with redundant services to improve its availability and scalability, and make disaster recovery much @@ -19,9 +21,9 @@ single host, but greatly reduces the cost of recovering from hardware and network failures. Each Phabricator service has an array of clustering options that can be -configured independently. Configuring a cluster is inherently complex, and this -is an advanced feature aimed at installs with large userbases and experienced -operations personnel who need this high degree of flexibility. +configured somewhat independently. Configuring a cluster is inherently complex, +and this is an advanced feature aimed at installs with large userbases and +experienced operations personnel who need this high degree of flexibility. The remainder of this document summarizes how to add redundancy to each service and where your efforts are likely to have the greatest impact. @@ -30,6 +32,25 @@ For additional guidance on setting up a cluster, see "Overlaying Services" and "Cluster Recipes" at the bottom of this document. +Clusterable Services +==================== + +This table provides an overview of clusterable services, their setup +complexity, and the rough impact that converting them to run on multiple hosts +will have on availability, resistance to data loss, and scalability. + +| Service | Setup | Availability | Loss Resistance | Scalability +|---------|-------|--------------|-----------|------------ +| **Databases** | Moderate | **High** | **High** | Low +| **Repositories** | Complex | Moderate | **High** | Moderate +| **Daemons** | Minimal | Low | No Risk | Low +| **SSH Servers** | Minimal | Low | No Risk | Low +| **Web Servers** | Minimal | **High** | No Risk | Moderate +| **Notifications** | Minimal | Low | No Risk | Low + +See below for a walkthrough of these services in greater detail. + + Preparing for Clustering ======================== @@ -146,7 +167,7 @@ Cluster: Daemons Configuring multiple daemon hosts is straightforward, but you must configure repositories first. -With daemons running on multiple hosts, you can transparently survive the loss +With daemons running on multiple hosts you can transparently survive the loss of any subset of hosts without an interruption to daemon services, as long as at least one host remains alive. Daemons are stateless, so spreading daemons across multiple hosts provides no resistance to data loss. @@ -161,14 +182,30 @@ in capacity. For details, see @{article:Cluster: Daemons}. +Cluster: SSH Servers +==================== + +Configuring multiple SSH hosts is straightforward, but you must configure +repositories first. + +With multiple SSH hosts you can transparently survive the loss of any subset +of hosts without interruption to repository services, as long as at last one +host remains alive. SSH services are stateless, so putting multiple hosts in +service provides no resistance to data loss because no data is at risk. + +SSH hosts are very rarely a scalability bottleneck. + +For details, see @{article:Cluster: SSH Servers}. + + Cluster: Web Servers ==================== Configuring multiple web hosts is straightforward, but you must configure repositories first. -With multiple web hosts, you can transparently survive the loss of any subset -of hosts as long as at least one host remains alive. Web hosts are stateless, +With multiple web hosts you can transparently survive the loss of any subset +of hosts as long as at least one host remains alive. Web services are stateless, so putting multiple hosts in service provides no resistance to data loss because no data is at risk. diff --git a/src/docs/user/cluster/cluster_daemons.diviner b/src/docs/user/cluster/cluster_daemons.diviner index 61cedd464d..8cde3e7b7d 100644 --- a/src/docs/user/cluster/cluster_daemons.diviner +++ b/src/docs/user/cluster/cluster_daemons.diviner @@ -6,9 +6,6 @@ Configuring Phabricator to use multiple daemon hosts. Overview ======== -WARNING: This feature is a very early prototype; the features this document -describes are mostly speculative fantasy. - You can run daemons on multiple hosts. The advantages of doing this are: - you can completely survive the loss of multiple daemon hosts; and @@ -18,7 +15,8 @@ This configuration is simple, but you must configure repositories first. For details, see @{article:Cluster: Repositories}. Since repository hosts must run daemons anyway, you usually do not need to do -any additional work and can skip this entirely. +any additional work and can skip this entirely if you have already configured +multiple repository hosts. Adding Daemon Hosts diff --git a/src/docs/user/cluster/cluster_devices.diviner b/src/docs/user/cluster/cluster_devices.diviner index 197620e461..57729fdb86 100644 --- a/src/docs/user/cluster/cluster_devices.diviner +++ b/src/docs/user/cluster/cluster_devices.diviner @@ -101,7 +101,7 @@ up with records that look like these: - Device: `repo001.mycompany.net` - Interface: `123.0.0.1:2222` - Interface: `123.0.0.1:80` - - Device: `repo002.mycopmany.net` + - Device: `repo002.mycompany.net` - Interface: `123.0.0.2:2222` - Interface: `123.0.0.2:80` diff --git a/src/docs/user/cluster/cluster_notifications.diviner b/src/docs/user/cluster/cluster_notifications.diviner index 3dd4e9d903..3cdeec3c39 100644 --- a/src/docs/user/cluster/cluster_notifications.diviner +++ b/src/docs/user/cluster/cluster_notifications.diviner @@ -6,9 +6,6 @@ Configuring Phabricator to use multiple notification servers. Overview ======== -WARNING: This feature is a very early prototype; the features this document -describes are mostly speculative fantasy. - You can run multiple notification servers. The advantages of doing this are: diff --git a/src/docs/user/cluster/cluster_repositories.diviner b/src/docs/user/cluster/cluster_repositories.diviner index 35508b0813..07203f03eb 100644 --- a/src/docs/user/cluster/cluster_repositories.diviner +++ b/src/docs/user/cluster/cluster_repositories.diviner @@ -6,9 +6,6 @@ Configuring Phabricator to use multiple repository hosts. Overview ======== -WARNING: This feature is a very early prototype; the features this document -describes are mostly speculative fantasy. - If you use Git, you can deploy Phabricator with multiple repository hosts, configured so that each host is readable and writable. The advantages of doing this are: @@ -296,8 +293,6 @@ the user whose change is holding the lock. currently held, this shows when the lock was acquired. - - Cluster Failure Modes ===================== diff --git a/src/docs/user/cluster/cluster_ssh.diviner b/src/docs/user/cluster/cluster_ssh.diviner new file mode 100644 index 0000000000..940ef98b05 --- /dev/null +++ b/src/docs/user/cluster/cluster_ssh.diviner @@ -0,0 +1,47 @@ +@title Cluster: SSH Servers +@group cluster + +Configuring Phabricator to use multiple SSH servers. + +Overview +======== + +You can run Phabricator on multiple SSH servers. The advantages of doing this +are: + + - you can completely survive the loss of multiple SSH hosts. + +This configuration is simple, but you must configure repositories first. For +details, see @{article:Cluster: Repositories}. + +SSH servers accept SSH requests from commands like `git clone` and relay them +to hosts that can serve the requests. + + +Adding SSH Hosts +================ + +After configuring repositories in cluster mode, you can add more web hosts +at any time. + +First, deploy the Phabricator software and configuration to a host, then +register the host as a cluster device if it is not already registered (for +help, see @{article:Cluster: Devices}. + +Once the host is registered, start the SSH server, and then add the host to the +SSH load balancer pool. + +Phabricator SSH servers are stateless, so you can pull them in and out of +production freely. + +You may also want to run web services on these hosts, since the service is very +similar to SSH, also stateless, and it may be simpler to load balance the +services together. For details, see @{cluster: Web Servers}. + + +Next Steps +========== + +Continue by: + + - returning to @{article:Clustering Introduction}. diff --git a/src/docs/user/cluster/cluster_webservers.diviner b/src/docs/user/cluster/cluster_webservers.diviner index 95699a8291..744696af66 100644 --- a/src/docs/user/cluster/cluster_webservers.diviner +++ b/src/docs/user/cluster/cluster_webservers.diviner @@ -6,9 +6,6 @@ Configuring Phabricator to use multiple web servers. Overview ======== -WARNING: This feature is a very early prototype; the features this document -describes are mostly speculative fantasy. - You can run Phabricator on multiple web servers. The advantages of doing this are: @@ -23,15 +20,21 @@ Adding Web Hosts ================ After configuring repositories in cluster mode, you can add more web hosts -at any time: simply deploy the Phabricator software and configuration to a -host, start the web server, and then add the host to the load balancer pool. +at any time. + +First, deploy the Phabricator software and configuration to a host, then +register the host as a cluster device if it is not already registered (for +help, see @{article:Cluster: Devices}. + +Once the host is registered, start the web server, and then add the host to the +load balancer pool. Phabricator web servers are stateless, so you can pull them in and out of production freely. You may also want to run SSH services on these hosts, since the service is very similar to HTTP, also stateless, and it may be simpler to load balance the -services together. +services together. For details, see @{cluster:SSH Servers}. Next Steps diff --git a/src/docs/user/userguide/diffusion.diviner b/src/docs/user/userguide/diffusion.diviner index f3fa53da3b..7c48d7a636 100644 --- a/src/docs/user/userguide/diffusion.diviner +++ b/src/docs/user/userguide/diffusion.diviner @@ -1,18 +1,20 @@ @title Diffusion User Guide @group userguide -Guide to Diffusion, the Phabricator repository browser. +Guide to Diffusion, the Phabricator application for hosting and browsing +repositories. -= Overview = +Overview +======== -Diffusion is a repository browser which allows you to explore source code in a -Subversion, Git, or Mercurial repository. It is somewhat similar to software -like Trac and GitWeb. +Diffusion allows you to create repositories so that you can browse them from +the web and interact with them from other applications. -Diffusion can either import a read-only copy of repositories hosted somewhere -else (for example, from GitHub, Bitbucket or existing hosting) or host -repositories within Phabricator. Hosted repositories support a variety of -triggers and access controls. +Diffusion can host repositories locally, or observe existing remote +repositories which are hosted elsewhere (for example, on GitHub, Bitbucket, or +other existing hosting). Both types of repositories can be browsed and +interacted with, but hosted repositories support some additional triggers +and access controls which are not available for observed repositories. Diffusion is integrated with the other tools in the Phabricator suite. For instance: @@ -24,10 +26,15 @@ instance: - for hosted repositories, Herald can enforce granular access control rules; - in all the tools, commit names are automatically linked. -= Adding Repositories = +The remainder of this document walks through creating, configuring, and +managing repositories. + + +Adding Repositories +=================== Repository administration is accomplished through Diffusion. You can use the -web interface in Diffusion to import an external repository, or create a new +web interface in Diffusion to observe an external repository or create a new hosted repository. - For hosted repositories, make sure you go through the setup instructions @@ -35,64 +42,35 @@ hosted repository. - For all repositories, you'll need to be running the daemons. If you have not set them up yet, see @{article:Managing Daemons with phd}. -By default, you must be an administrator to create a new repository. +By default, you must be an administrator to create a new repository. You can +change this in the application settings. -= Repository Callsigns and Commit Names = -Each repository is identified by a "callsign", which is a short uppercase string -like "P" (for Phabricator) or "ARC" (for Arcanist). +Managing Repositories +===================== -Each repository must have a unique callsign. Callsigns must be unique within -an install but do not need to be globally unique, so you are free to use the -single-letter callsigns for brevity. For example, Facebook uses "E" for the -Engineering repository, "O" for the Ops repository, "Y" for a Yum package -repository, and so on, while Phabricator uses "P", "ARC", "PHU" for libphutil, -and "J" for Javelin. Keeping callsigns brief will make them easier to use, and -the use of one-character callsigns is recommended if they are reasonably -evocative and you have no more than 26 tracked repositories. +Diffusion repositories have an array of configurable options and behaviors. For +details on the available options and guidance on managing and administrating +repositories, see @{article:Diffusion User Guide: Managing Repositories}. -The primary goal of callsigns is to namespace commits to SVN repositories: if -you use multiple SVN repositories, each repository has a revision 1, revision 2, -etc., so referring to them by number alone is ambiguous. However, even for Git -they impart additional information to human readers and allow parsers to detect -that something is a commit name with high probability (and allow distinguishing -between multiple copies of a repository). +Repositories can also be managed via the API. For an overview on using the +API to create and edit repositories, see +@{article:Diffusion User Guide: Repositories API}. -Diffusion uses this callsign and information about the commit itself to generate -a commit name, like "rE12345" or "rP28146171ce1278f2375e3646a1e1ea3fd56fc5a3". -The "r" stands for "revision". It is followed by the repository callsign, and -then a VCS-specific commit identifier (for SVN, the commit number; for Git and -Mercurial, the commit hash). When writing the name of a Git commit you may -abbreviate the hash, but note that hash collisions are probable for short prefix -lengths. See this post on the LKML for a historical explanation of Git's -occasional internal use of 7-character hashes: -https://lkml.org/lkml/2010/10/28/287 +Repository Clustering +===================== -Because 7-character hashes are likely to collide for even moderately large -repositories, Diffusion generally uses either a 16-character prefix (which makes -collisions very unlikely) or the full 40-character hash (which makes collisions -astronomically unlikely). +Phabricator repository hosts can be set up in a cluster configuration so you +can lose hosts with minimal downtime and data loss. This is an advanced feature +which most installs do not need to pursue. -= Running Diffusion Daemons = +To get started with clustering, see @{article:Clustering Introduction}. For +details on repository clustering, see @{article:Cluster: Repositories}. -In most cases, it is sufficient to run: - phabricator/bin/ $ ./phd start - -...to start the daemons. For a more in-depth explanation of `phd` and daemons, -see @{article:Managing Daemons with phd}. - -NOTE: If you have an unusually large install with multiple web frontends, see -notes in @{article:Managing Daemons with phd}. - -You can use the repository detail screen and the Daemon Console to monitor the -daemons and their progress importing the repository. Small repositories should -import quickly, while larger repositories may take some time. Commits should -begin appearing in Diffusion within a few minutes for all but the largest -repositories. - -= Next Steps = +Next Steps +========== Continue by: diff --git a/src/docs/user/userguide/diffusion_api.diviner b/src/docs/user/userguide/diffusion_api.diviner new file mode 100644 index 0000000000..8ddd6d4bf6 --- /dev/null +++ b/src/docs/user/userguide/diffusion_api.diviner @@ -0,0 +1,182 @@ +@title Diffusion User Guide: Repositories API +@group userguide + +Managing repositories with the API. + +Overview +======== + +You can create and update Diffusion repositories using the Conduit API. This +may be useful if you have a large number of existing repositories you want +to import or apply bulk actions to. + +For an introduction to Conduit, see @{article:Conduit API Overview}. + +In general, you'll use these API methods: + + - `diffusion.repository.edit`: Create and edit repositorie. + - `diffusion.uri.edit`: Create and edit repository URIs to configure + observation, mirroring, and cloning. + +To create a repository, you'll generally do this: + + - Call `diffusion.repository.edit` to create a new object and configure + basic information. + - Optionally, call `diffusion.uri.edit` to add URIs to observe or mirror. + - Call `diffusion.repository.edit` to activate the repository. + +This workflow mirrors the workflow from the web UI. The remainder of this +document walks through this workflow in greater detail. + + +Create a Repository +=================== + +To create a repository, call `diffusion.repository.edit`, providing any +properties you want to set. For simplicity these examples will use the +builtin `arc call-conduit` client, but you can use whatever Conduit client +you prefer. + +When creating a repository, you must provide a `vcs` transaction to choose +a repository type, one of: `git`, `hg` or `svn`. + +You must also provide a `name`. + +Other properties are optional. Review the Conduit method documentation from the +web UI for an exhaustive list. + +``` +$ echo '{ + "transactions": [ + { + "type": "vcs", + "value": "git" + }, + { + "type": "name", + "value": "Poetry" + } + ] +}' | arc call-conduit diffusion.repository.edit +``` + +If things work, you should get a result that looks something like this: + +```lang=json +{ + ... + "response": { + "object": { + "id": 1, + "phid": "PHID-REPO-7vm42oayez2rxcmpwhuv" + }, + ... + } + ... +} +``` + +If so, your new repository has been created. It hasn't been activated yet so +it will not show up in the default repository list, but you can find it in the +web UI by browsing to {nav Diffusion > All Repositories}. + +Continue to the next step to configure URIs. + + +Configure URIs +============== + +Now that the repository exists, you can add URIs to it. This is optional, +and if you're creating a //hosted// repository you may be able to skip this +step. + +However, if you want Phabricator to observe an existing remote, you'll +configure it here by adding a URI in "Observe" mode. Use the PHID from the +previous step to identify the repository you want to add a URI to, and call +`diffusion.uri.edit` to create a new URI in Observe mode for the repository. + +You need to provide a `repository` to add the URI to, and the `uri` itself. + +To add the URI in Observe mode, provide an `io` transaction selecting +`observe` mode. + +You may also want to provide a `credential`. + +``` +$ echo '{ + "transactions": [ + { + "type": "repository", + "value": "PHID-REPO-7vm42oayez2rxcmpwhuv" + }, + { + "type": "uri", + "value": "https://github.com/epriestley/poems.git" + }, + { + "type": "io", + "value": "observe" + } + ] +}' | arc call-conduit diffusion.uri.edit +``` + +You should get a response that looks something like this: + +```lang=json +{ + ... + "response": { + "object": { + "id": 1, + "phid": "PHID-RURI-zwtho5o7h3m6rjzgsgrh" + }, + ... + } + ... +} +``` + +If so, your URI has been created. You can review it in the web UI, under +{nav Manage Repository > URIs}. + +When satisfied, continue to the next step to activate the repository. + + +Activate the Repository +======================= + +Now that any URIs have been configured, activate the repository with another +call to `diffusion.repository.edit`. This time, modify the existing repostitory +instead of creating a new one: + +``` +$ echo '{ + "objectIdentifier": "PHID-REPO-7vm42oayez2rxcmpwhuv", + "transactions": [ + { + "type": "status", + "value": "active" + } + ] +}' | arc call-conduit diffusion.repository.edit +``` + +If that goes through cleanly, you should be all set. You can review the +repository from the web UI. + + +Editing Repositories +==================== + +To edit an existing repository, apply changes normally with +`diffusion.repository.edit`. For more details on using edit endpoints, see +@{article:Conduit API: Using Edit Endpoints}. + + +Next Steps +========== + +Continue by: + + - returning to the @{article:Diffusion User Guide}. diff --git a/src/docs/user/userguide/diffusion_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner new file mode 100644 index 0000000000..f6ab0997c4 --- /dev/null +++ b/src/docs/user/userguide/diffusion_managing.diviner @@ -0,0 +1,385 @@ +@title Diffusion User Guide: Managing Repositories +@group userguide + +Guide to configuring and managing repositories in Diffusion. + +Overview +======== + +After you create a new repository in Diffusion or select **Manage Repository** +from the main screen if an existing repository, you'll be taken to the +repository management interface for that repository. + +On this interface, you'll find many options which allow you to configure the +behavior of a repository. This document walks through the options. + +Basics +====== + +The **Basics** section of the management interface allows you to configure +the repository name, description, and identifiers. You can also activate or +deactivate the repository here, and configure a few other miscellaneous +settings. + +Basics: Name +============ + +The repository name is a human-readable primary name for the repository. It +does not need to be unique + +Because the name is not unique and does not have any meaningful restrictions, +it's fairly ambiguous and isn't very useful as an identifier. The other basic +information (primarily callsigns and short names) gives you control over +repository identifiers. + + +Basics: Callsigns +================= + +Each repository can optionally be identified by a "callsign", which is a short +uppercase string like "P" (for Phabricator) or "ARC" (for Arcanist). + +The primary goal of callsigns is to namespace commits to SVN repositories: if +you use multiple SVN repositories, each repository has a revision 1, revision 2, +etc., so referring to them by number alone is ambiguous. + +However, even for Git and Mercurial they impart additional information to human +readers and allow parsers to detect that something is a commit name with high +probability (and allow distinguishing between multiple copies of a repository). + +Configuring a callsign can make interacting with a commonly-used repository +easier, but you may not want to bother assigning one to every repository if you +have some similar, templated, or rarely-used repositories. + +If you choose to assign a callsign to a repository, it must be unique within an +install but do not need to be globally unique, so you are free to use the +single-letter callsigns for brevity. For example, Facebook uses "E" for the +Engineering repository, "O" for the Ops repository, "Y" for a Yum package +repository, and so on, while Phabricator uses "P", "ARC", "PHU" for libphutil, +and "J" for Javelin. Keeping callsigns brief will make them easier to use, and +the use of one-character callsigns is encouraged if they are reasonably +evocative. + +If you configure a callsign like `XYZ`, Phabricator will activate callsign URIs +and activate the callsign identifier (like `rXYZ`) for the repository. These +more human-readable identifiers can make things a little easier to interact +with. + + +Basics: Short Name +================== + +Each repository can optionally have a unique short name. Short names must be +unique and have some minor restrictions to make sure they are unambiguous and +appropriate for use as directory names and in URIs. + + +Basics: Description +=================== + +You may optionally provide a brief (or, at your discretion, excruciatingly +long) human-readable description of the repository. This description will be +shown on the main repository page. + +You can also create a `README` file at the repository root (or in any +subdirectory) to provide information about the repository. These formats are +supported: + +| File Name | Rendered As... +|-------------------|--------------- +| `README` | Plain Text +| `README.txt` | Plain Text +| `README.remarkup` | Remarkup +| `README.md` | Remarkup +| `README.rainbow` | Rainbow + + +Basics: Encoding +================ + +Before content from the repository can be shown in the web UI or embedded in +other contexts like email, it must be converted to UTF-8. + +Most source code is written in UTF-8 or a subset of UTF-8 (like plain ASCII) +already, so everything will work fine. The majority of repositories do not need +to adjust this setting. + +If your repository is primarily written in some other encoding, specify it here +so Phabricator can convert from it properly when reading content to embed in +a webpage or email. + + +Basics: Dangerous Changes +========================= + +By default, repositories are protected against dangerous changes. Dangerous +changes are operations which rewrite or destroy repository history (for +example, by deleting or rewriting branches). Normally, these take the form +of `git push --force` or similar. + +It is normally a good idea to leave this protection enabled because most +scalable workflows rarely rewrite repository history and it's easy to make +mistakes which are expensive to correct if this protection is disabled. + +If you do occasionally need to rewite published history, you can treat this +option like a safety: disable it, perform required rewrites, then enable it +again. + +If you fully disable this at the repository level, you can still use Herald to +selectively protect certain branches or grant this power to a limited set of +users. + +This option is only available in Git and Mercurial, because it is impossible +to make dangerous changes in Subversion. + +This option has no effect if a repository is not hosted because Phabricator +can not prevent dangerous changes in a remote repository it is merely +observing. + + +Basics: Deactivate Repository +============================= + +Repositories can be deactivated. Deactivating a repository has these effects: + + - the repository will no longer be updated; + - users will no longer be able to clone/fetch/checkout the repository; + - users will no longer be able to push to the repository; and + - the repository will be hidden from view in default queries. + +When repositories are created for the first time, they are deactivated. This +gives you an opportuinty to customize settings, like adjusting policies or +configuring a URI to observe. You must activate a repository before it will +start working normally. + + +Basics: Delete Repository +========================= + +Repositories can not be deleted from the web UI, so this option is always +disabled. Clicking it gives you information about how to delete a repository. + +Repositories can only be deleted from the command line, with `bin/remove`: + +``` +$ ./bin/remove destroy +``` + +WARNING: This command will issue you a dire warning about the severity of the +action you are taking. Heed this warning. You are **strongly discouraged** from +destroying repositories. Instead, deactivate them. + + +Policies +======== + +The **Policies** section of the management interface allows you to review and +manage repository access policies. + +You can configure granular access policies for each repository to control who +can view, clone, administate, and push to the repository. + + +Policies: View +============== + +The view policy for a repository controls who can view the repository from +the web UI and clone, fetch, or check it out from Phabricator. + +Users who can view a repository can also access the "Manage" interface to +review information about the repository and examine the edit history, but can +not make any changes. + + +Policies: Edit +============== + +The edit policy for a repository controls who can change repository settings +using the "Manage" interface. In essence, this is permission to administrate +the repository. + +You must be able to view a repository to edit it. + +You do not need this permission to push changes to a repository. + + +Policies: Push +============== + +The push policy for a repository controls who can push changes to the +repository. + +This policy has no effect if Phabricator is not hosting the repository, because +it can not control who is allowed to make changes to a remote repository it is +merely observing. + +You must also be able to view a repository to push to it. + +You do not need to be able to edit a repository to push to it. + +Further restrictions on who can push (and what they can push) can be configured +for hosted repositories with Herald, which allows you to write more +sophisticated rules that evaluate when Phabricator receives a push. To get +started with Herald, see @{article:Herald User Guide}. + +Additionally, Git and Mercurial repositories have a setting which allows +you to **Prevent Dangerous Changes**. This setting is enabled by default and +will prevent any users from pushing changes which rewrite or destroy history. + + +URIs +==== + +The **URIs** panel allows you to add and manage URIs which Phabricator will +fetch from, serve from, and push to. + +These options are covered in detail in @{article:Diffusion User Guide: URIs}. + + +Staging Area +============ + +The **Staging Area** panel configures staging areas, used to make proposed +changes available to build and continuous integration systems. + +For more details, see @{article:Harbormaster User Guide}. + + +Automation +========== + +The **Automation** panel configures support for allowing Phabricator to make +writes directly to the repository, so that it can perform operations like +automatically landing revisions from the web UI. + +For details on repository automation, see +@{article:Drydock User Guide: Repository Automation}. + + +Symbols +====== + +The **Symbols** panel allows you to customize how symbols (like class and +function names) are linked when viewing code in the repository, and when +viewing revisions which propose code changes to the repository. + +To take advantage of this feature, you need to do additional work to build +symbol indexes. For details on configuring and populating symbol indexes, see +@{article:User Guide: Symbol Indexes}. + + +Branches +======== + +The **Branches** panel allows you to configure how Phabricator interacts with +branches. + +This panel is not available for Subversion repositories, because Subversion +does not have formal branches. + +You can configure **Default Branch**. This controls which branch is shown by +default in the UI. If no branch is provided, Phabricator will use `master` in +Git and `default` in Mercurial. + +If you want Diffusion to ignore some branches in the repository, you can +configure **Track Only**. Other branches will be ignored. If you do not specify +any branches, all branches are tracked. + +When specifying branches, you should enter one branch name per line. You can +use regular expressions to match branches by wrapping an expression in +`regexp(...)`. For example: + +| Example | Effect | +|---------|--------| +| `master` | Track only `master`. +| `regexp(/^release-/)` | Track all branches which start with `release-`. +| `regexp(/^(?!temp-)/)` | Do not track branches which start with `temp-`. + + +Actions +====== + +The **Actions** panel can configure notifications and publishing behavior. + +Normally, Phabricator publishes notifications when it discovers new commits. +You can disable publishing for a repository by turning off **Publish/Noitfy**. +This will disable notifications, feed, and Herald (including audits and build +plans) for this repository. + +When Phabricator discovers a new commit, it can automatically close associated +revisions and tasks. If you don't want Phabricator to close objects when it +discovers new commits, disable **Autoclose** for the repository. + + +Repository Identifiers and Names +================================ + +Repositories have several short identifiers which you can use to refer to the +repository. For example, if you use command-line administrative tools to +interact with a repository, you'll provide one of these identifiers: + +``` +$ ./bin/repository update +``` + +The identifiers available for a repository depend on which options are +configured. Each repository may have several identifiers: + + - An **ID** identifier, like `R123`. This is available for all repositories. + - A **callsign** identifier, like `rXY`. This is available for repositories + with a callsign. + - A **short name** identifier, like `xylophone`. This is available for + repositories with a short name. + +All three identifiers can be used to refer to the repository in cases where +the intent is unambiguous, but only the first two forms work in ambiguous +contexts. + +For example, if you type `R123` or `rXY` into a comment, Phabricator will +recognize them as references to the repository. If you type `xylophone`, it +assumes you mean the word "xylophone". + +Only the `R123` identifier is immutable: the others can be changed later by +adjusting the callsign or short name for the repository. + + +Commit Identifiers +================== + +Diffusion uses repository identifiers and information about the commit itself +to generate globally unique identifers for each commit, like `rE12345`. + +Each commit may have several identifiers: + + - A repository **ID** identifier, like `R123:abcdef123...`. + - A repository **callsign** identifier, like `rXYZabcdef123...`. This only + works if a repository has a callsign. + - Any unique prefix of the commit hash. + +Git and Mercurial use commit hashes to identify commits, and Phabricator will +recognize a commit if the hash prefix is unique and sufficiently long. Commit +hashes qualified with a repository identifier must be at least 5 characters +long; unqualified commit hashes must be at least 7 characters long. + +In Subversion, commit identifiers are sequential integers and prefixes can not +be used to identify them. + +When rendering the name of a Git or Mercurial commit hash, Phabricator tends to +shorten it to 12 characters. This "short length" is relatively long compared to +Git itself (which often uses 7 characters). See this post on the LKML for a +historical explanation of Git's occasional internal use of 7-character hashes: + +https://lkml.org/lkml/2010/10/28/287 + +Because 7-character hashes are likely to collide for even moderately large +repositories, Diffusion generally uses either a 12-character prefix (which makes +collisions very unlikely) or the full 40-character hash (which makes collisions +astronomically unlikely). + + +Next Steps +========== + +Continue by: + + - returning to the @{article:Diffusion User Guide}. diff --git a/src/docs/user/userguide/diffusion_updates.diviner b/src/docs/user/userguide/diffusion_updates.diviner index eb01b8c9bc..7012e6228e 100644 --- a/src/docs/user/userguide/diffusion_updates.diviner +++ b/src/docs/user/userguide/diffusion_updates.diviner @@ -89,7 +89,7 @@ There are several ways to do this: - If your repository is hosted on Phabricator, this will also be done for you automatically. - You can schedule an update from the web interface, in Diffusion > - (Choose a Repository) > Edit Repository > Update Now. + (Choose a Repository) > Manage Repository > Status > Update Now. - You can make a call to the Conduit API method `diffusion.looksoon`. This hints to Phabricator that it should poll a repository as soon as it can. All of the other mechanisms do this under the hood. @@ -109,7 +109,7 @@ Troubleshooting Updates You can manually run a repository update from the command line to troubleshoot issues, using the `--trace` flag to get full details: - phabricator/ $ ./bin/repository update --trace + phabricator/ $ ./bin/repository update --trace To catch potential issues with permissions, run this command as the same user that the daemons run as. diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index b560f4ab3f..7377250f21 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -6,9 +6,6 @@ Guide to configuring repository URIs for fetching, cloning and mirroring. Overview ======== -WARNING: This document describes a feature which is still under development, -and is not necessarily accurate or complete. - Phabricator can create, host, observe, mirror, proxy, and import repositories. For example, you can: @@ -23,9 +20,9 @@ is hosted elsewhere (like GitHub or Bitbucket) and track updates to the remote repository. This will create a read-only copy of the repository in Phabricator. **Mirror Repositories**: Phabricator can publish any repository to mirrors, -updating the mirrors as changes are made to the repository. This works with -both local hosted repositories and remote repositories that Phabricator is -observing. +overwiting them with an exact copy of the repository that stays up to date as +the source changes. This works with both local repositories that Phabricator is +hosting and remote repositories that Phabricator is observing. **Proxy Repositories**: If you are observing a repository, you can allow users to read Phabricator's copy of the repository. Phabricator supports granular @@ -102,12 +99,19 @@ Mirror a Repository NOTE: Mirroring is not supported in Subversion. You can create a read-only mirror of an existing repository. Phabricator will -push all changes made to the repository to the mirror. +continuously publish the state of the source repository to the mirror, creating +an exact copy. For example, if you have a repository hosted in Phabricator that you want to mirror to GitHub, you can configure Phabricator to automatically maintain the mirror. This is how the upstream repositories are set up. +The mirror copy must be read-only for users because any writes made to the +mirror will be undone when Phabricator updates it. The mirroring process copies +the entire repository state exactly, so the remote state will be completely +replaced with an exact copy of the source repository. This may remove or +destroy information. Normally, you should only mirror to an empty repository. + You can mirror any repository, even if Phabricator is only observing it and not hosting it directly. @@ -160,6 +164,32 @@ Once you've put a working copy in the right place on disk, activate the repository. +Builtin Clone URIs +================== + +By default, Phabricator automatically exposes and activates HTTP, HTTPS and +SSH clone URIs by examining configuration. + +**HTTP**: The `http://` clone URI will be available if these conditions are +satisfied: + + - `diffusion.allow-http-auth` must be enabled. + - The repository must be a Git or Mercurial repository. + - `security.require-https` must be disabled. + +**HTTPS**: The `https://` clone URI will be available if these conditions are +satisfied: + + - `diffusion.allow-http-auth` must be enabled. + - The repository must be a Git or Mercurial repository. + - The `phabricator.base-uri` protocol must be `https://`. + +**SSH**: The `ssh://` or `svn+ssh://` clone URI will be available if these +conditions are satisfied: + + - `phd.user` must be configured. + + Customizing Displayed Clone URIs ================================ diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php index c1b6c84de7..c2f958f228 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php @@ -34,6 +34,7 @@ final class PhabricatorStandardCustomFieldCredential ->execute(); return id(new PassphraseCredentialControl()) + ->setViewer($this->getViewer()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setCaption($this->getCaption()) diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index 60bd9e4595..dd8c11e629 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -877,4 +877,21 @@ final class PhabricatorEnv extends Phobject { umask(022); } + + /** + * Get the path to an empty directory which is readable by all of the system + * user accounts that Phabricator acts as. + * + * In some cases, a binary needs some valid HOME or CWD to continue, but not + * all user accounts have valid home directories and even if they do they + * may not be readable after a `sudo` operation. + * + * @return string Path to an empty directory suitable for use as a CWD. + */ + public static function getEmptyCWD() { + $root = dirname(phutil_get_library_root('phabricator')); + return $root.'/support/empty/'; + } + + } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index f570ee7e51..92e027f86d 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -480,6 +480,10 @@ final class PhabricatorMarkupEngine extends Phobject { 'syntax-highlighter.engine', $options['syntax-highlighter.engine']); + $style_map = id(new PhabricatorDefaultSyntaxStyle()) + ->getRemarkupStyleMap(); + $engine->setConfig('phutil.codeblock.style-map', $style_map); + $engine->setConfig('uri.full', $options['uri.full']); $rules = array(); diff --git a/src/infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php b/src/infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php new file mode 100644 index 0000000000..72b6dfb4b0 --- /dev/null +++ b/src/infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php @@ -0,0 +1,76 @@ + 'color: #ffffcc', + 'c' => 'color: #74777d', + 'cm' => 'color: #74777d', + 'c1' => 'color: #74777d', + 'cs' => 'color: #74777d', + 'sd' => 'color: #000000', + 'sh' => 'color: #000000', + 's' => 'color: #766510', + 'sb' => 'color: #766510', + 'sc' => 'color: #766510', + 's2' => 'color: #766510', + 's1' => 'color: #766510', + 'sx' => 'color: #766510', + 'sr' => 'color: #bb6688', + 'nv' => 'color: #001294', + 'vi' => 'color: #001294', + 'vg' => 'color: #001294', + 'na' => 'color: #354bb3', + 'kc' => 'color: #000a65', + 'no' => 'color: #000a65', + 'k' => 'color: #aa4000', + 'kd' => 'color: #aa4000', + 'kn' => 'color: #aa4000', + 'kt' => 'color: #aa4000', + 'cp' => 'color: #304a96', + 'kp' => 'color: #304a96', + 'kr' => 'color: #304a96', + 'nb' => 'color: #304a96', + 'bp' => 'color: #304a96', + 'nc' => 'color: #00702a', + 'nt' => 'color: #00702a', + 'vc' => 'color: #00702a', + 'nf' => 'color: #004012', + 'nx' => 'color: #004012', + 'o' => 'color: #aa2211', + 'ss' => 'color: #aa2211', + 'm' => 'color: #601200', + 'mf' => 'color: #601200', + 'mh' => 'color: #601200', + 'mi' => 'color: #601200', + 'mo' => 'color: #601200', + 'il' => 'color: #601200', + 'gd' => 'color: #a00000', + 'gr' => 'color: #ff0000', + 'gh' => 'color: #000080', + 'gi' => 'color: #00a000', + 'go' => 'color: #808080', + 'gp' => 'color: #000080', + 'gu' => 'color: #800080', + 'gt' => 'color: #0040d0', + 'nd' => 'color: #aa22ff', + 'ni' => 'color: #92969d', + 'ne' => 'color: #d2413a', + 'nl' => 'color: #a0a000', + 'nn' => 'color: #0000ff', + 'ow' => 'color: #aa22ff', + 'w' => 'color: #bbbbbb', + 'se' => 'color: #bb6622', + 'si' => 'color: #bb66bb', + ); + } + +} diff --git a/src/infrastructure/syntax/PhabricatorSyntaxStyle.php b/src/infrastructure/syntax/PhabricatorSyntaxStyle.php new file mode 100644 index 0000000000..3f75f5c639 --- /dev/null +++ b/src/infrastructure/syntax/PhabricatorSyntaxStyle.php @@ -0,0 +1,44 @@ +addInt($this->isDefaultStyle() ? 0 : 1) + ->addString($this->getStyleName()); + } + + final public function getSyntaxStyleKey() { + return $this->getPhobjectClassConstant('STYLEKEY'); + } + + final public function isDefaultStyle() { + return ($this->getSyntaxStyleKey() == 'default'); + } + + public static function getAllStyles() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getSyntaxStyleKey') + ->setSortMethod('getStyleName') + ->execute(); + } + + final public function getRemarkupStyleMap() { + $map = array( + 'rbw_r' => 'color: red', + 'rbw_o' => 'color: orange', + 'rbw_y' => 'color: yellow', + 'rbw_g' => 'color: green', + 'rbw_b' => 'color: blue', + 'rbw_i' => 'color: indigo', + 'rbw_v' => 'color: violet', + ); + + return $map + $this->getStyleMap(); + } + +} diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index bbb2fdd684..832379aec7 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -7,8 +7,8 @@ } .device .phabricator-timer { - margin-left: 8px; - margin-right: 8px; + margin-left: 12px; + margin-right: 12px; } .phabricator-remarkup .phabricator-timer-view { @@ -16,9 +16,15 @@ margin: 0 0 12px 0; } -.phabricator-timer .countdown-description { +body .phabricator-timer .countdown-description.phabricator-remarkup { border-bottom: 1px solid {$thinblueborder}; - padding-bottom: 16px; + padding: 0 0 16px; + margin: 0; +} + +.phabricator-timer .countdown-description.phabricator-remarkup + .phabricator-remarkup { + padding: 0; } .device-phone .phabricator-remarkup .phabricator-timer { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index c735e3d842..0e00f6e46f 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -28,3 +28,22 @@ input.diffusion-clone-uri { .diffusion-search-boxen .phui-form-view { padding: 0; } + +.diffusion-clone-uri-table { + width: 100%; +} + +.diffusion-clone-uri-table th { + width: 24px; + padding: 0 0 0 4px; +} + +.diffusion-clone-uri-table th a.button { + width: 12px; + height: 19px; +} + +.diffusion-clone-uri-table th a.button .phui-icon-view { + left: 12px; + top: 7px; +} diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 8f48d7228b..b4fa18ac8d 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -353,12 +353,14 @@ box-shadow: 1px 1px 2px rgba({$alphablack}, 0.20); } -.phabricator-remarkup-embed-image-full { +.phabricator-remarkup-embed-image-full, +.phabricator-remarkup-embed-image-wide { display: inline-block; max-width: 100%; } -.phabricator-remarkup-embed-image-full img { +.phabricator-remarkup-embed-image-full img, +.phabricator-remarkup-embed-image-wide img { height: auto; max-width: 100%; } diff --git a/webroot/rsrc/css/core/syntax.css b/webroot/rsrc/css/core/syntax.css index 9cf3fc9a24..35e3d299d4 100644 --- a/webroot/rsrc/css/core/syntax.css +++ b/webroot/rsrc/css/core/syntax.css @@ -1,5 +1,6 @@ /** * @provides syntax-highlighting-css + * @requires syntax-default-css */ .remarkup-code .uu { /* Forbidden Unicode */ color: #aa0066; @@ -19,110 +20,6 @@ border-bottom: 1px solid transparent; } -.remarkup-code .hll { - background-color: #ffffcc; -} - -.remarkup-code .c, /* Comment */ -.remarkup-code .cm, /* Comment.Multiline */ -.remarkup-code .c1, /* Comment.Single */ -.remarkup-code .cs { /* Comment.Special */ - color: {$greytext}; -} - -.remarkup-code .sd, /* Literal.String.Doc */ -.remarkup-code .sh { /* Literal.String.Heredoc */ - color: #000000; -} - -.remarkup-code .s, /* Literal.String */ -.remarkup-code .sb, /* Literal.String.Backtick */ -.remarkup-code .sc, /* Literal.String.Char */ -.remarkup-code .s2, /* Literal.String.Double */ -.remarkup-code .s1, /* Literal.String.Single */ -.remarkup-code .sx { /* Literal.String.Other */ - color: #766510; -} - -.remarkup-code .sr { /* Literal.String.Regex */ - color: #BB6688; -} - -.remarkup-code .nv, /* Name.Variable */ -.remarkup-code .vi, /* Name.Variable.Instance */ -.remarkup-code .vg { /* Name.Variable.Global */ - color: #001294; -} - -.remarkup-code .na { /* Name.Attribute */ - color: #354BB3; -} - -.remarkup-code .kc, /* Keyword.Constant */ -.remarkup-code .no { /* Name.Constant */ - color: #000A65; -} - -.remarkup-code .k, /* Keyword */ -.remarkup-code .kd, /* Keyword.Declaration */ -.remarkup-code .kn, /* Keyword.Namespace */ -.remarkup-code .kt { /* Keyword.Type */ - color: #AA4000; -} - -.remarkup-code .cp, /* Comment.Preproc */ -.remarkup-code .kp, /* Keyword.Pseudo */ -.remarkup-code .kr, /* Keyword.Reserved */ -.remarkup-code .nb, /* Name.Builtin */ -.remarkup-code .bp { /* Name.Builtin.Pseudo */ - color: #304A96; -} - -.remarkup-code .nc, /* Name.Class */ -.remarkup-code .nt, /* Name.Tag */ -.remarkup-code .vc { /* Name.Variable.Class */ - color: #00702A; -} - -.remarkup-code .nf, /* Name.Function */ -.remarkup-code .nx { /* Name.Other */ - color: #004012; -} - -.remarkup-code .o, /* Operator */ -.remarkup-code .ss { /* Literal.String.Symbol */ - color: #AA2211; -} - -.remarkup-code .m, /* Literal.Number */ -.remarkup-code .mf, /* Literal.Number.Float */ -.remarkup-code .mh, /* Literal.Number.Hex */ -.remarkup-code .mi, /* Literal.Number.Integer */ -.remarkup-code .mo, /* Literal.Number.Oct */ -.remarkup-code .il { /* Literal.Number.Integer.Long */ - color: #601200; -} - -.remarkup-code .gd { color: #A00000 } /* Generic.Deleted */ -.remarkup-code .ge { } /* Generic.Emph */ -.remarkup-code .gr { color: #FF0000 } /* Generic.Error */ -.remarkup-code .gh { color: #000080; } /* Generic.Heading */ -.remarkup-code .gi { color: #00A000 } /* Generic.Inserted */ -.remarkup-code .go { color: #808080 } /* Generic.Output */ -.remarkup-code .gp { color: #000080 } /* Generic.Prompt */ -.remarkup-code .gs { } /* Generic.Strong */ -.remarkup-code .gu { color: #800080 } /* Generic.Subheading */ -.remarkup-code .gt { color: #0040D0 } /* Generic.Traceback */ -.remarkup-code .nd { color: #AA22FF } /* Name.Decorator */ -.remarkup-code .ni { color: {$lightgreytext} } /* Name.Entity */ -.remarkup-code .ne { color: #D2413A } /* Name.Exception */ -.remarkup-code .nl { color: #A0A000 } /* Name.Label */ -.remarkup-code .nn { color: #0000FF } /* Name.Namespace */ -.remarkup-code .ow { color: #AA22FF } /* Operator.Word */ -.remarkup-code .w { color: #bbbbbb } /* Text.Whitespace */ -.remarkup-code .se { color: #BB6622 } /* Literal.String.Escape */ -.remarkup-code .si { color: #BB6688 } /* Literal.String.Interpol */ - .remarkup-code .rbw_r { color: red; } .remarkup-code .rbw_o { color: orange; } .remarkup-code .rbw_y { color: yellow; } diff --git a/webroot/rsrc/css/layout/phabricator-side-menu-view.css b/webroot/rsrc/css/layout/phabricator-side-menu-view.css index 77c0b7cf39..b64fe5fbb5 100644 --- a/webroot/rsrc/css/layout/phabricator-side-menu-view.css +++ b/webroot/rsrc/css/layout/phabricator-side-menu-view.css @@ -18,6 +18,12 @@ border-bottom-right-radius: 3px; } +.phabricator-basic-nav .phabricator-side-menu .phui-list-item-icon { + margin-left: -12px; + text-align: center; + width: 24px; +} + .phabricator-basic-nav .phabricator-side-menu .phui-list-item-selected { background-color: rgba({$alphablack},.05); border-left: 4px solid {$sky}; diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 7fed4a535d..8d2acd0069 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -86,10 +86,11 @@ padding: 6px 16px; } -.device .phui-box.phui-box-blue-property .phui-header-shell, -.device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed +body.device .phui-box.phui-box-blue-property .phui-header-shell, +body.device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed .phui-header-shell { padding: 6px 12px; + margin-top: 0; } .phui-box.phui-box-blue-property .phui-header-header { diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 85f8150015..0df18815d9 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -141,8 +141,27 @@ a.button.phui-document-toc { margin: 16px 0 0 0; } +.phui-document-view-pro .phabricator-remarkup-embed-image-wide { + margin-left: -200px; + margin-right: -200px; + width: auto; +} +.phui-document-view-pro .phabricator-remarkup-embed-image-wide img { + max-width: 1200px; +} +@media (max-width: 1200px) { + .phui-document-view-pro .phabricator-remarkup-embed-image-wide { + margin-left: 0; + margin-right: 0; + width: auto; + } + + .phui-document-view-pro .phabricator-remarkup-embed-image-wide img { + max-width: inherit; + } +} .phui-document-view-pro-box .phui-timeline-view { padding: 16px 0 0 0; diff --git a/webroot/rsrc/css/phui/phui-document.css b/webroot/rsrc/css/phui/phui-document.css index fd1f057b88..2b75227c42 100644 --- a/webroot/rsrc/css/phui/phui-document.css +++ b/webroot/rsrc/css/phui/phui-document.css @@ -103,7 +103,6 @@ .phui-document-content { background: #fff; - overflow: hidden; } .phui-document-content .phabricator-remarkup { diff --git a/webroot/rsrc/css/phui/phui-feed-story.css b/webroot/rsrc/css/phui/phui-feed-story.css index 0d4b98a461..d971202ebe 100644 --- a/webroot/rsrc/css/phui/phui-feed-story.css +++ b/webroot/rsrc/css/phui/phui-feed-story.css @@ -27,12 +27,11 @@ text-align: center; vertical-align: middle; font-size: 24px; - line-height: 35px; - color: {$lightgreytext}; - background: {$greybackground}; + line-height: 32px; + color: #fff; + background-color: #c4cde0; } - .phui-feed-story-head { padding: 12px 4px; overflow: hidden; diff --git a/webroot/rsrc/css/phui/phui-status.css b/webroot/rsrc/css/phui/phui-status.css index d8f46c002f..dd22e6f337 100644 --- a/webroot/rsrc/css/phui/phui-status.css +++ b/webroot/rsrc/css/phui/phui-status.css @@ -13,8 +13,7 @@ } .phui-status-item-target { - padding: 0 12px 0 0; - line-height: 20px; + padding: 1px 8px; white-space: nowrap; } @@ -22,14 +21,18 @@ width: 100%; color: {$greytext}; line-height: 14px; - padding: 3px 0; + padding: 3px 4px; } .phui-status-item-highlighted td { - background-color: {$lightyellow}; + background-color: {$sh-yellowbackground}; + border-radius: 3px; +} + +.phui-status-list-view td a { + color: {$darkbluetext}; } .phui-status-item-highlighted td.phui-status-item-note { background-color: transparent; - padding-left: 4px; } diff --git a/webroot/rsrc/css/syntax/syntax-default.css b/webroot/rsrc/css/syntax/syntax-default.css new file mode 100644 index 0000000000..687698b1e6 --- /dev/null +++ b/webroot/rsrc/css/syntax/syntax-default.css @@ -0,0 +1,150 @@ +/** + * @provides syntax-default-css + * @generated + */ + +.remarkup-code .bp, +.remarkup-code .cp, +.remarkup-code .kp, +.remarkup-code .kr, +.remarkup-code .nb { + color: #304a96 +} + +.remarkup-code .c, +.remarkup-code .c1, +.remarkup-code .cm, +.remarkup-code .cs { + color: #74777d +} + +.remarkup-code .gd { + color: #a00000 +} + +.remarkup-code .gh, +.remarkup-code .gp { + color: #000080 +} + +.remarkup-code .gi { + color: #00a000 +} + +.remarkup-code .go { + color: #808080 +} + +.remarkup-code .gr { + color: #ff0000 +} + +.remarkup-code .gt { + color: #0040d0 +} + +.remarkup-code .gu { + color: #800080 +} + +.remarkup-code .hll { + color: #ffffcc +} + +.remarkup-code .il, +.remarkup-code .m, +.remarkup-code .mf, +.remarkup-code .mh, +.remarkup-code .mi, +.remarkup-code .mo { + color: #601200 +} + +.remarkup-code .k, +.remarkup-code .kd, +.remarkup-code .kn, +.remarkup-code .kt { + color: #aa4000 +} + +.remarkup-code .kc, +.remarkup-code .no { + color: #000a65 +} + +.remarkup-code .na { + color: #354bb3 +} + +.remarkup-code .nc, +.remarkup-code .nt, +.remarkup-code .vc { + color: #00702a +} + +.remarkup-code .nd, +.remarkup-code .ow { + color: #aa22ff +} + +.remarkup-code .ne { + color: #d2413a +} + +.remarkup-code .nf, +.remarkup-code .nx { + color: #004012 +} + +.remarkup-code .ni { + color: #92969d +} + +.remarkup-code .nl { + color: #a0a000 +} + +.remarkup-code .nn { + color: #0000ff +} + +.remarkup-code .nv, +.remarkup-code .vg, +.remarkup-code .vi { + color: #001294 +} + +.remarkup-code .o, +.remarkup-code .ss { + color: #aa2211 +} + +.remarkup-code .s, +.remarkup-code .s1, +.remarkup-code .s2, +.remarkup-code .sb, +.remarkup-code .sc, +.remarkup-code .sx { + color: #766510 +} + +.remarkup-code .sd, +.remarkup-code .sh { + color: #000000 +} + +.remarkup-code .se { + color: #bb6622 +} + +.remarkup-code .si { + color: #bb66bb +} + +.remarkup-code .sr { + color: #bb6688 +} + +.remarkup-code .w { + color: #bbbbbb +} diff --git a/webroot/rsrc/js/core/behavior-select-content.js b/webroot/rsrc/js/core/behavior-select-content.js new file mode 100644 index 0000000000..b54b6308cf --- /dev/null +++ b/webroot/rsrc/js/core/behavior-select-content.js @@ -0,0 +1,23 @@ +/** + * @provides javelin-behavior-select-content + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * @javelin + */ + +JX.behavior('select-content', function() { + JX.Stratcom.listen( + 'click', + 'select-content', + function(e) { + e.kill(); + + var node = e.getNode('select-content'); + var data = JX.Stratcom.getData(node); + + var target = JX.$(data.selectID); + JX.DOM.focus(target); + target.select(); + }); +});