From c2227d3ed24c879a31fb1a57299bd031b819fb94 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 30 Apr 2016 09:04:28 -0700 Subject: [PATCH 01/62] Fix "acccess" spelling error Summary: This is misspelled. Test Plan: Consulted a dictionary. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15827 --- src/aphront/AphrontRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 562d427f097e1761c1a3e885f978158dc580df5f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 1 May 2016 12:48:56 -0700 Subject: [PATCH 02/62] New icons, colors for per reviewer status Summary: Fixes T10906, Fixes T10820. Adds new icons, grey-er colors for previous states. Also, I think fixed a few bugs? Test Plan: Fake each state, verify icon is as intended. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10820, T10906 Differential Revision: https://secure.phabricator.com/D15830 --- resources/celerity/map.php | 6 +++--- .../view/DifferentialReviewersView.php | 19 ++++++++++--------- webroot/rsrc/css/phui/phui-status.css | 13 ++++++++----- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cd0efd5db5..08ff8d1298 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'b729f9f5', + 'core.pkg.css' => 'dd240688', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -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', @@ -848,7 +848,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', 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/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; } From 45ca0472a9d5d7b4827c8b23256ae34f391642bf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 1 May 2016 13:02:48 -0700 Subject: [PATCH 03/62] Spiffy up feed icon defaults Summary: Uses standard background color of profile icons. Test Plan: Review herald, commit stories. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15831 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-feed-story.css | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 08ff8d1298..e50636d3bb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'dd240688', + 'core.pkg.css' => 'ea0dfb3d', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -131,7 +131,7 @@ return array( 'rsrc/css/phui/phui-document-pro.css' => '73e45fd2', '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-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', @@ -823,7 +823,7 @@ return array( 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '9c71d2bf', 'phui-document-view-pro-css' => '73e45fd2', - 'phui-feed-story-css' => 'd8440402', + 'phui-feed-story-css' => 'aa49845d', 'phui-font-icon-base-css' => '6449bce8', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 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; From 99718b61d8f3d74d2824f58d5af2bea1b8d967f3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 1 May 2016 06:59:22 -0700 Subject: [PATCH 04/62] Fill in new URI credential edit web UI interfaces Summary: Ref T10748. Ref T10366. Allows users to set credential for new URIs. - Ref T7221. Our handling of the "git://" protocol is currently incorrect. This protocol is not authenticated, but is considered an SSH protocol. In the new UI, it is considered an anonymous/unauthenticated protocol instead. - Ref T10241. This fixes the `PassphraseCredentialControl` so it doesn't silently edit the value if the current value is not visible to you and/or not valid. Test Plan: Performed a whole lot of credential edits, removals, and adjustments. I'll give this additional vetting before cutting over to it. {F1253207} Reviewers: chad Reviewed By: chad Maniphest Tasks: T7221, T10241, T10366, T10748 Differential Revision: https://secure.phabricator.com/D15829 --- src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 2 + .../DiffusionMirrorEditController.php | 2 +- .../DiffusionRepositoryCreateController.php | 1 + ...usionRepositoryURICredentialController.php | 159 ++++++++++++++++++ .../DiffusionRepositoryURIViewController.php | 120 ++++++++++++- .../editor/DiffusionURIEditEngine.php | 13 +- .../diffusion/editor/DiffusionURIEditor.php | 40 +++++ .../protocol/DiffusionCommandEngine.php | 66 +++++++- .../view/PassphraseCredentialControl.php | 42 ++++- .../storage/PhabricatorRepositoryURI.php | 89 ++++++++-- .../PhabricatorRepositoryURITransaction.php | 38 +++++ ...abricatorStandardCustomFieldCredential.php | 1 + 13 files changed, 551 insertions(+), 24 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b0ffd4eef5..537bc69221 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -789,6 +789,7 @@ phutil_register_library_map(array( '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', @@ -5020,6 +5021,7 @@ phutil_register_library_map(array( 'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryTag' => 'Phobject', 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryURICredentialController' => 'DiffusionController', 'DiffusionRepositoryURIDisableController' => 'DiffusionController', 'DiffusionRepositoryURIEditController' => 'DiffusionController', 'DiffusionRepositoryURIViewController' => 'DiffusionController', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index ec27124d05..0170817e6c 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -97,6 +97,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { => 'DiffusionRepositoryURIDisableController', $this->getEditRoutePattern('edit/') => 'DiffusionRepositoryURIEditController', + 'credential/(?P[0-9]\d*)/(?Pedit|remove)/' + => 'DiffusionRepositoryURICredentialController', ), 'edit/' => array( '' => 'DiffusionRepositoryEditMainController', diff --git a/src/applications/diffusion/controller/DiffusionMirrorEditController.php b/src/applications/diffusion/controller/DiffusionMirrorEditController.php index d438a9e6d2..1209f937de 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorEditController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorEditController.php @@ -108,7 +108,7 @@ final class DiffusionMirrorEditController ->setName('remoteURI') ->setValue($v_remote) ->setError($e_remote)) - ->appendChild( + ->appendControl( id(new PassphraseCredentialControl()) ->setLabel(pht('Credentials')) ->setName('credential') diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index 09fdd23565..580f021257 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -510,6 +510,7 @@ final class DiffusionRepositoryCreateController ->setAdjustFormPageCallback(array($this, 'adjustAuthPage')) ->addControl( id(new PassphraseCredentialControl()) + ->setViewer($this->getViewer()) ->setName('credential')); } 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/DiffusionRepositoryURIViewController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php index 8cd143c5a1..61a8cba0a1 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,7 @@ final class DiffusionRepositoryURIViewController $disable_icon = 'fa-ban'; } - $disable_uri = $uri->getRepository()->getPathURI("uri/disable/{$id}/"); + $disable_uri = $repository->getPathURI("uri/disable/{$id}/"); $curtain->addAction( id(new PhabricatorActionView()) @@ -130,7 +167,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/editor/DiffusionURIEditEngine.php b/src/applications/diffusion/editor/DiffusionURIEditEngine.php index 6d351219dc..61e24e0997 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionURIEditEngine.php @@ -83,6 +83,14 @@ final class DiffusionURIEditEngine protected function buildCustomEditFields($object) { $viewer = $this->getViewer(); + if ($object->isBuiltin()) { + $is_builtin = true; + $uri_value = (string)$object->getDisplayURI(); + } else { + $is_builtin = false; + $uri_value = $object->getURI(); + } + return array( id(new PhabricatorHandlesEditField()) ->setKey('repository') @@ -104,12 +112,13 @@ 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), 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..44ffd83e6e 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -70,7 +70,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()); @@ -184,6 +219,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)) 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/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/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 7e7741e372..92bf5976ae 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -193,11 +193,73 @@ 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 getEffectiveURI() { + return $this->getURIObject(true); + } + + 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); + 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 +272,6 @@ final class PhabricatorRepositoryURI $uri->setDomain($host); } - $port = $this->getForcedPort(); - if ($port) { - $uri->setPort($port); - } - $path = $this->getForcedPath(); if ($path) { $uri->setPath($path); @@ -223,6 +280,7 @@ final class PhabricatorRepositoryURI return $uri; } + private function getForcedProtocol() { switch ($this->getBuiltinProtocol()) { case self::BUILTIN_PROTOCOL_SSH: @@ -446,6 +504,16 @@ final class PhabricatorRepositoryURI ); } + public function newCommandEngine() { + $repository = $this->getRepository(); + $protocol = $this->getEffectiveURI()->getProtocol(); + + return DiffusionCommandEngine::newCommandEngine($repository) + ->setCredentialPHID($this->getCredentialPHID()) + ->setProtocol($protocol); + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -569,7 +637,8 @@ final class PhabricatorRepositoryURI 'repositoryPHID' => $this->getRepositoryPHID(), 'uri' => array( 'raw' => $this->getURI(), - 'effective' => (string)$this->getDisplayURI(), + 'display' => (string)$this->getDisplayURI(), + 'effective' => (string)$this->getEffectiveURI(), ), '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/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()) From c0d42a89430a655f48e30adc1dbc2085c4a904e6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 May 2016 05:35:05 -0700 Subject: [PATCH 05/62] Split Repository EditEngine form into smaller pages Summary: Ref T10748. This allows an EditEngine form to be broken up into pages. This is less powerful than `PHUIPagedFormView`, because the pages are not sequential / stateful. Each form saves immediately once it's submitted, and can not take you to a new form or back/forward in a series of forms. For example, you can't create a workflow where the user fills out 5 pages of information before we create an object, like the current repository workflow does. However, the only place we've ever wanted to do this is repositories and it's fairly bad there, so I feel reasonably confident we aren't going to miss this in the future. (We do "choose a type of service/repository/rule -> fill out one page of info" fairly often, but can do this without the full-power paging stuff.) Test Plan: - Created a repository usin the new Manage UI, filling out only a handful of fields. - Edited a repository using the new Manage UI. - All forms are now EditEngine forms offering paged views of the big huge underlying form: {F1254371} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15832 --- src/__phutil_library_map__.php | 2 + .../base/PhabricatorApplication.php | 6 +- ...usionRepositoryEditDangerousController.php | 26 +++- .../editor/DiffusionRepositoryEditEngine.php | 42 ++++++ ...fusionRepositoryActionsManagementPanel.php | 9 +- ...ionRepositoryAutomationManagementPanel.php | 8 +- ...ffusionRepositoryBasicsManagementPanel.php | 13 +- ...usionRepositoryBranchesManagementPanel.php | 10 +- .../DiffusionRepositoryManagementPanel.php | 38 ++++++ ...usionRepositoryPoliciesManagementPanel.php | 10 +- ...fusionRepositoryStagingManagementPanel.php | 8 +- ...fusionRepositorySymbolsManagementPanel.php | 9 +- .../editengine/PhabricatorEditEngine.php | 123 +++++++++++++++++- .../editengine/PhabricatorEditPage.php | 58 +++++++++ 14 files changed, 342 insertions(+), 20 deletions(-) create mode 100644 src/applications/transactions/editengine/PhabricatorEditPage.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 537bc69221..ea12d54c1d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2358,6 +2358,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', @@ -6872,6 +6873,7 @@ phutil_register_library_map(array( 'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditField' => 'Phobject', + 'PhabricatorEditPage' => 'Phobject', 'PhabricatorEditType' => 'Phobject', 'PhabricatorEditor' => 'Phobject', 'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', 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/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php index 26d8b57f33..f2a49f820a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php @@ -13,12 +13,30 @@ final class DiffusionRepositoryEditDangerousController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - if (!$repository->canAllowDangerousChanges()) { - return new Aphront400Response(); - } - $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($edit_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($edit_uri); + } + } + if ($request->isFormPost()) { $xaction = id(new PhabricatorRepositoryTransaction()) ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_DANGEROUS) diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index 1ebbabe2ed..f6ba777edc 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -85,6 +85,48 @@ 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 buildCustomEditFields($object) { $viewer = $this->getViewer(); diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php index 4b9fa5b528..14bcc250d9 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php @@ -13,6 +13,13 @@ final class DiffusionRepositoryActionsManagementPanel return 1100; } + protected function getEditEngineFieldKeys() { + return array( + 'publish', + 'autoclose', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +29,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..911a594d05 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php @@ -13,6 +13,12 @@ final class DiffusionRepositoryAutomationManagementPanel return 800; } + protected function getEditEngineFieldKeys() { + return array( + 'automationBlueprintPHIDs', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -24,7 +30,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..40ba95045a 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -13,6 +13,15 @@ final class DiffusionRepositoryBasicsManagementPanel return 100; } + protected function getEditEngineFieldKeys() { + return array( + 'name', + 'callsign', + 'shortName', + 'description', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,10 +31,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()) { diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index 54184cd8ba..4ba7eed73c 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -13,6 +13,14 @@ final class DiffusionRepositoryBranchesManagementPanel return 1000; } + protected function getEditEngineFieldKeys() { + return array( + 'defaultBranch', + 'trackOnly', + 'autocloseOnly', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +30,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/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index f278c3323a..a8f68d71b9 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -98,4 +98,42 @@ 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/editpro/{$id}/page/{$page}/"; + } + + } diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php index 9bfe572070..7cca25d95c 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php @@ -13,6 +13,14 @@ final class DiffusionRepositoryPoliciesManagementPanel return 300; } + protected function getEditEngineFieldKeys() { + return array( + 'policy.view', + 'policy.edit', + 'policy.push', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +30,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..09cc3c58c4 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php @@ -13,6 +13,12 @@ final class DiffusionRepositoryStagingManagementPanel return 700; } + protected function getEditEngineFieldKeys() { + return array( + 'stagingAreaURI', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +28,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/DiffusionRepositorySymbolsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php index 4e934c67af..3a9d94a54d 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php @@ -13,6 +13,13 @@ final class DiffusionRepositorySymbolsManagementPanel return 900; } + protected function getEditEngineFieldKeys() { + return array( + 'symbolLanguages', + 'symbolRepositoryPHIDs', + ); + } + protected function buildManagementPanelActions() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -22,7 +29,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/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 7c9c0fbed1..f67860f938 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,30 @@ abstract class PhabricatorEditEngine return implode('', $parts); } + public function getEffectiveObjectViewURI($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 +838,14 @@ abstract class PhabricatorEditEngine return $this->buildDisabledFormResponse($object, $config); } + $page_key = $request->getURIData('pageKey'); + 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 +877,7 @@ abstract class PhabricatorEditEngine } else { $crumbs->addTextCrumb( $this->getObjectEditShortText($object), - $this->getObjectViewURI($object)); + $this->getEffectiveObjectViewURI($object)); $edit_text = pht('Edit'); if ($final) { @@ -1029,7 +1065,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 +1115,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 +1139,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 +1583,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 +1991,83 @@ 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; + } + + $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; + } + +} From cac26c8824767e76fc7baf27c1837e18e586459c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 04:20:35 -0700 Subject: [PATCH 06/62] Fix errant rules for associating projects when dragging tasks within a milestone column Summary: Fixes T10912. When you drag tasks within a milestone, we currently apply an overbroad, API-focused rule and add the parent board's project. This logic was added fairly recently, as part of T6027, to improve the behavior of API-originated moves. Later on, this causes the task to toggle in and out of the parent project on every alternate drag. This logic is also partially duplicated in the `MoveController`. - Add test coverage for this interaction. - Fix the logic so it accounts for subproject / milestone columns correctly. - Put all of the logic into the TransactionEditor, so the API gets the exact same rules. Test Plan: - Added a failing test and made it pass. - Dragged tasks around within a milestone column: - Before patch: they got bogus project swaps on every other move. - After patch: projects didn't change (correct). - Dragged tasks around between normal and milestone columns. - Before patch: worked properly. - After patch: still works properly. Here's what the bad changes look like, the task is swapping projects with every other move: {F1255957} The "every other" is because the logic was trying to do this: - Add both the parent and milestone project. - Whichever one exists already gets dropped from the change list because it would have no effect. - The other one then applies. - In applying, it forces removal of the first one. - Then this process repeats in the other direction the next time through. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10912 Differential Revision: https://secure.phabricator.com/D15834 --- .../editor/ManiphestTransactionEditor.php | 103 +++++++++++++++--- .../PhabricatorProjectCoreTestCase.php | 33 ++++++ .../PhabricatorProjectMoveController.php | 26 ----- 3 files changed, 119 insertions(+), 43 deletions(-) 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/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) From 319a9cefdea2b0fe88f7c4e22334ab96faac8bd4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 05:57:09 -0700 Subject: [PATCH 07/62] When creating a repository with EditEngine, allocate it onto a random cluster service Summary: Ref T10748. This copies existing code in the `CreateController` which will eventually be removed. Test Plan: - Created a new repository with the EditPro workflow. - Saw it come up into the cluster properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15835 --- .../editor/DiffusionRepositoryEditEngine.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index f6ba777edc..f421a1af18 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -45,6 +45,44 @@ final class DiffusionRepositoryEditEngine $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; } From c3afddec9c00832a5a86db2d13749ef000ed55c7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 06:54:50 -0700 Subject: [PATCH 08/62] Add icons to the new repository edit nav Summary: Ref T10748. These: - Look nice. - Hint at panel contents / effects. - Hint which panels have been customized. - Allow panels with issues or errors to be highlighted with an alert/attention icon. Test Plan: {F1256156} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15836 --- resources/celerity/map.php | 6 ++-- .../DiffusionRepositoryManageController.php | 16 ++++++++-- ...fusionRepositoryActionsManagementPanel.php | 17 ++++++++++ ...ionRepositoryAutomationManagementPanel.php | 22 +++++++++++++ ...ffusionRepositoryBasicsManagementPanel.php | 10 ++++++ ...usionRepositoryBranchesManagementPanel.php | 15 +++++++++ ...fusionRepositoryHistoryManagementPanel.php | 4 +++ .../DiffusionRepositoryManagementPanel.php | 4 +++ ...usionRepositoryPoliciesManagementPanel.php | 32 +++++++++++++++++++ ...fusionRepositoryStagingManagementPanel.php | 12 +++++++ ...ffusionRepositoryStatusManagementPanel.php | 27 ++++++++++++++-- ...fusionRepositoryStorageManagementPanel.php | 12 +++++++ ...fusionRepositorySymbolsManagementPanel.php | 14 ++++++++ ...DiffusionRepositoryURIsManagementPanel.php | 6 +++- .../query/DrydockAuthorizationQuery.php | 29 +++++++++++++++++ .../drydock/storage/DrydockAuthorization.php | 5 +++ .../css/layout/phabricator-side-menu-view.css | 6 ++++ 17 files changed, 227 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e50636d3bb..0115b0ce9a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ea0dfb3d', + 'core.pkg.css' => '9968bf8d', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -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', @@ -780,7 +780,7 @@ return array( 'phabricator-remarkup-css' => '6aae5360', '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', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php index aa34f8f909..4f4624b70d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php @@ -106,9 +106,19 @@ final class DiffusionRepositoryManageController ->setBaseURI($base_uri); foreach ($panels as $panel) { - $nav->addFilter( - $panel->getManagementPanelKey(), - $panel->getManagementPanelLabel()); + $key = $panel->getManagementPanelKey(); + $label = $panel->getManagementPanelLabel(); + $icon = $panel->getManagementPanelIcon(); + $href = $repository->getPathURI("manage/{$key}/"); + + $item = id(new PHUIListItemView()) + ->setKey($key) + ->setName($label) + ->setType(PHUIListItemView::TYPE_LINK) + ->setHref($href) + ->setIcon($icon); + + $nav->addMenuItem($item); } $nav->selectFilter($selected); diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php index 14bcc250d9..3a82879b26 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php @@ -13,6 +13,23 @@ final class DiffusionRepositoryActionsManagementPanel return 1100; } + public function getManagementPanelIcon() { + $repository = $this->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', diff --git a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php index 911a594d05..764f58c5bd 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php @@ -19,6 +19,28 @@ final class DiffusionRepositoryAutomationManagementPanel ); } + 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(); diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 40ba95045a..96bda40246 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -13,6 +13,16 @@ 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', diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index 4ba7eed73c..66ac0877dd 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -13,6 +13,21 @@ final class DiffusionRepositoryBranchesManagementPanel return 1000; } + 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', diff --git a/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php index e574030bfc..c1a812dca0 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php @@ -13,6 +13,10 @@ final class DiffusionRepositoryHistoryManagementPanel return 2000; } + public function getManagementPanelIcon() { + return 'fa-list-ul'; + } + public function buildManagementPanelContent() { return $this->newTimeline(); } diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index a8f68d71b9..efb165d801 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -38,6 +38,10 @@ abstract class DiffusionRepositoryManagementPanel abstract public function getManagementPanelOrder(); abstract public function buildManagementPanelContent(); + public function getManagementPanelIcon() { + return 'fa-pencil'; + } + protected function buildManagementPanelActions() { return array(); } diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php index 7cca25d95c..da53970fee 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php @@ -13,6 +13,38 @@ 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', diff --git a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php index 09cc3c58c4..0d8f9379be 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php @@ -13,6 +13,18 @@ final class DiffusionRepositoryStagingManagementPanel return 700; } + 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', diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php index 1666749980..eba7722f2f 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); @@ -469,5 +482,13 @@ 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; + } + } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php index 54c446b9fe..734b4c6f8e 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(), diff --git a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php index 3a9d94a54d..996473f4f1 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php @@ -13,6 +13,20 @@ 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', diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php index 634535fef4..45047dd4fe 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php @@ -9,8 +9,12 @@ final class DiffusionRepositoryURIsManagementPanel return pht('Clone / Fetch / Mirror'); } + public function getManagementPanelIcon() { + return 'fa-cogs'; + } + public function getManagementPanelOrder() { - return 300; + return 400; } public function buildManagementPanelContent() { 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/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}; From db065e67582ed86b518004168c00291f08f42439 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 9 Apr 2016 10:07:02 -0700 Subject: [PATCH 09/62] Increase specificity in blue property boxes Summary: Feels a little hairy, but should be fine overall. Ups the css specificity enough that it's always displayed the same. The main problem is headers and boxes get put everywhere, and sometimes override each other. Fixes T10757 Test Plan: review a countdown in a phame post and phriction document. Check diviner, phriction for regressions. Reviewers: epriestley, #phacility Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10757 Differential Revision: https://secure.phabricator.com/D15664 --- resources/celerity/map.php | 10 +++++----- webroot/rsrc/css/application/countdown/timer.css | 14 ++++++++++---- webroot/rsrc/css/phui/phui-box.css | 5 +++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0115b0ce9a..e1e501b9d1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '9968bf8d', + 'core.pkg.css' => '2bd78e9d', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -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', @@ -123,7 +123,7 @@ 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', @@ -758,7 +758,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', @@ -811,7 +811,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', 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/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 { From d85386488bf5419371dc4d608c65527d08b1ca1d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 3 May 2016 13:53:12 -0700 Subject: [PATCH 10/62] Add "wide" remarkup image support for Documents Summary: Seems to work ok, if you give `size=wide` to an image file, we blow it out a bit in DocumentPro mode. Test Plan: Test in Phame and Maniphest. {F1256717} {F1256718} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15840 --- resources/celerity/map.php | 14 +++++++------- .../PhabricatorEmbedFileRemarkupRule.php | 8 ++++++++ webroot/rsrc/css/core/remarkup.css | 6 ++++-- webroot/rsrc/css/phui/phui-document-pro.css | 19 +++++++++++++++++++ webroot/rsrc/css/phui/phui-document.css | 1 - 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e1e501b9d1..e31cea82c9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '2bd78e9d', + 'core.pkg.css' => '5242a859', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -104,7 +104,7 @@ 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/remarkup.css' => '787105d6', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '5b6fcf3f', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', @@ -128,9 +128,9 @@ return array( '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-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', @@ -777,7 +777,7 @@ 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' => 'dd849797', @@ -821,8 +821,8 @@ 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-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', 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/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/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 { From dd2b10b8f859e88a08499b334287a985781a6314 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 12:22:40 -0700 Subject: [PATCH 11/62] Guarantee repositories have unique local paths Summary: Ref T4039. Long ago these were more freely editable and there were some security concerns around creating a repository, then setting its local path to point somewhere it shouldn't. Local paths are no longer editable so there's no real reason we need to provide a uniqueness guarantee anymore, but you could still make a mistake with `bin/repository move-paths` by accident, and it's a little cleaner to pull them out into their own column with a key. (We still don't -- and, largely can't -- guarantee that two paths aren't //equivalent// since one might be symlinked to the other, or symlinked only on some hosts, or whatever, but the primary value here is as a sanity check that you aren't goofing things up and pointing a bunch of repositories at the same working copy by mistake.) Test Plan: - Ran migrations. - Grepped for `local-path`. - Listed and moved paths with `bin/repository`. - Created a new repository, verified its local path populated correctly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4039 Differential Revision: https://secure.phabricator.com/D15837 --- .../autopatches/20160503.repo.01.lpath.sql | 2 + .../autopatches/20160503.repo.02.lpathkey.sql | 2 + .../20160503.repo.03.lpathmigrate.php | 57 +++++++++++++++++++ .../DiffusionRepositoryEditMainController.php | 4 +- ...ffusionRepositoryEditStorageController.php | 8 +-- ...fusionRepositoryStorageManagementPanel.php | 2 +- .../RepositoryCreateConduitAPIMethod.php | 3 +- .../editor/PhabricatorRepositoryEditor.php | 8 +-- .../PhabricatorWorkingCopyTestCase.php | 2 +- ...icatorRepositoryManagementEditWorkflow.php | 11 ---- ...rRepositoryManagementMovePathsWorkflow.php | 6 +- .../storage/PhabricatorRepository.php | 17 ++++-- .../PhabricatorRepositoryTestCase.php | 4 +- 13 files changed, 89 insertions(+), 37 deletions(-) create mode 100644 resources/sql/autopatches/20160503.repo.01.lpath.sql create mode 100644 resources/sql/autopatches/20160503.repo.02.lpathkey.sql create mode 100644 resources/sql/autopatches/20160503.repo.03.lpathmigrate.php 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/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index d8a4cf40ba..08fc9778bc 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -631,9 +631,7 @@ final class DiffusionRepositoryEditMainController pht('Storage Service'), $v_service); - $view->addProperty( - pht('Storage Path'), - $repository->getHumanReadableDetail('local-path')); + $view->addProperty(pht('Storage Path'), $repository->getLocalPath()); return $view; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index f3a492e3b3..6aebd13500 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -15,7 +15,7 @@ final class DiffusionRepositoryEditStorageController $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - $v_local = $repository->getHumanReadableDetail('local-path'); + $v_local = $repository->getLocalPath(); $errors = array(); $crumbs = $this->buildApplicationCrumbs(); @@ -51,11 +51,7 @@ final class DiffusionRepositoryEditStorageController ->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()))) + 'web interface.')) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri, pht('Done'))); diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php index 734b4c6f8e..0c723bd9e6 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php @@ -40,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/repository/conduit/RepositoryCreateConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php index ad4b62104e..bec9b3b9a3 100644 --- a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php +++ b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php @@ -110,7 +110,6 @@ final class RepositoryCreateConduitAPIMethod '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), @@ -128,6 +127,8 @@ final class RepositoryCreateConduitAPIMethod $repository->setDetail($key, $value); } + $repository->setLocalPath($local_path); + try { $repository->save(); } catch (AphrontDuplicateKeyQueryException $ex) { diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 3dc842d94f..c528c91c99 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -86,7 +86,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: return $object->getDetail('remote-uri'); case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: - return $object->getDetail('local-path'); + return $object->getLocalPath(); case PhabricatorRepositoryTransaction::TYPE_HOSTING: return $object->isHosted(); case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: @@ -209,7 +209,7 @@ final class PhabricatorRepositoryEditor $object->setDetail('remote-uri', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: - $object->setDetail('local-path', $xaction->getNewValue()); + $object->setLocalPath($xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_HOSTING: return $object->setHosted($xaction->getNewValue()); @@ -706,7 +706,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,7 +716,7 @@ final class PhabricatorRepositoryEditor $id = $object->getID(); $local_path = "{$local_root}/{$id}/"; - $object->setDetail('local-path', $local_path); + $object->setLocalPath($local_path); $object->save(); } diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php index 5a9be99212..5b37a9e655 100644 --- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php +++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php @@ -65,7 +65,7 @@ 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); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php index cc33f1021c..8bf31c63d9 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php @@ -21,11 +21,6 @@ final class PhabricatorRepositoryManagementEditWorkflow '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', @@ -83,7 +78,6 @@ final class PhabricatorRepositoryManagementEditWorkflow $xactions = array(); - $type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH; $type_protocol_http = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; $type_protocol_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; @@ -93,11 +87,6 @@ final class PhabricatorRepositoryManagementEditWorkflow 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()) 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/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index be77852be8..9785a32611 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -62,6 +62,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO protected $credentialPHID; protected $almanacServicePHID; protected $spacePHID; + protected $localPath; private $commitCount = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE; @@ -107,6 +108,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'pushPolicy' => 'policy', 'credentialPHID' => 'phid?', 'almanacServicePHID' => 'phid?', + 'localPath' => 'text128?', ), self::CONFIG_KEY_SCHEMA => array( 'callsign' => array( @@ -123,6 +125,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'columns' => array('repositorySlug'), 'unique' => true, ), + 'key_local' => array( + 'columns' => array('localPath'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -216,6 +222,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 +292,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)) { 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()); From b8b700c179dbd6b76e0faa9b08f66e03936bd315 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 13:05:26 -0700 Subject: [PATCH 12/62] Remove "repository.create" Conduit API method Summary: Ref T10748. This has had many problems for a long time, can't create hosted repositories, can't create cluster repositories, etc. It is obsoleted by `diffusion.repository.edit`. Remove it. (Right now `diffusion.repository.edit` isn't a strict replacement, but it will be as soon as the URI stuff cuts over.) Test Plan: Grepped for `repository.create`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15838 --- src/__phutil_library_map__.php | 2 - .../RepositoryCreateConduitAPIMethod.php | 141 ------------------ 2 files changed, 143 deletions(-) delete mode 100644 src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ea12d54c1d..72e734da87 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4105,7 +4105,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', @@ -8988,7 +8987,6 @@ phutil_register_library_map(array( 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'ReleephConduitAPIMethod', 'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod', 'RepositoryConduitAPIMethod' => 'ConduitAPIMethod', - 'RepositoryCreateConduitAPIMethod' => 'RepositoryConduitAPIMethod', 'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod', 'ShellLogView' => 'AphrontView', 'SlowvoteConduitAPIMethod' => 'ConduitAPIMethod', diff --git a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php deleted file mode 100644 index bec9b3b9a3..0000000000 --- a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php +++ /dev/null @@ -1,141 +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, - '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); - } - - $repository->setLocalPath($local_path); - - try { - $repository->save(); - } catch (AphrontDuplicateKeyQueryException $ex) { - throw new ConduitException('ERR-DUPLICATE'); - } - - return $repository->toDictionary(); - } - -} From 34e870819ce59ddac78eeaa44c97b8da6f21fb4f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 13:10:38 -0700 Subject: [PATCH 13/62] Remove "bin/repository edit" workflow Summary: Ref T10748. In D14250#158181, I accepted this conditional on removing it once Conduit could handle it. Conduit can now handle it, or at least will be able to as soon as T10748 cuts over. Test Plan: Grepped for `repository edit`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15839 --- src/__phutil_library_map__.php | 2 - ...icatorRepositoryManagementEditWorkflow.php | 124 ------------------ 2 files changed, 126 deletions(-) delete mode 100644 src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 72e734da87..f6a91ecd35 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3190,7 +3190,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', @@ -7867,7 +7866,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow', - 'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php deleted file mode 100644 index 8bf31c63d9..0000000000 --- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php +++ /dev/null @@ -1,124 +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' => '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_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, - ); - - $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; - } - -} From 42eaa88f80310658c45bcda8984164b8dad5210c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 13:40:13 -0700 Subject: [PATCH 14/62] Cut mirroring over to new URIs Summary: Ref T10748. This migrates and swaps mirroring to `PhabricatorRepositoryURI`, obsoleting `PhabricatorRepositoryMirror`. This prevents you from editing, adding or disabling mirrors unless you know a secret URI (until the UI cuts over fully), but existing mirroring is not affected. Test Plan: - Added a mirroring URI to an old repository. - Verified it worked with `bin/repository mirror`. - Migrated forward. - Verified it still worked with `bin/repository mirror`. - Wow, mirroring: https://github.com/epriestley/locktopia-mirror Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15841 --- .../20160503.repo.04.mirrormigrate.php | 38 +++++ src/__phutil_library_map__.php | 13 +- .../PhabricatorDiffusionApplication.php | 4 - .../DiffusionMirrorDeleteController.php | 45 ------ .../DiffusionMirrorEditController.php | 130 ------------------ .../DiffusionRepositoryEditMainController.php | 115 ---------------- .../PhabricatorRepositoryMirrorEngine.php | 76 +++++----- ...atorRepositoryManagementMirrorWorkflow.php | 17 +-- .../PhabricatorRepositoryMirrorPHIDType.php | 41 ------ .../PhabricatorRepositoryMirrorQuery.php | 99 ------------- .../storage/PhabricatorRepository.php | 6 +- .../storage/PhabricatorRepositoryMirror.php | 53 +------ .../storage/PhabricatorRepositoryURI.php | 23 ++++ 13 files changed, 126 insertions(+), 534 deletions(-) create mode 100644 resources/sql/autopatches/20160503.repo.04.mirrormigrate.php delete mode 100644 src/applications/diffusion/controller/DiffusionMirrorDeleteController.php delete mode 100644 src/applications/diffusion/controller/DiffusionMirrorEditController.php delete mode 100644 src/applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php delete mode 100644 src/applications/repository/query/PhabricatorRepositoryMirrorQuery.php 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/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f6a91ecd35..c41fc1f0ba 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -682,8 +682,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', @@ -3208,8 +3206,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', @@ -4914,8 +4910,6 @@ phutil_register_library_map(array( 'DiffusionMercurialWireProtocolTests' => 'PhabricatorTestCase', 'DiffusionMercurialWireSSHTestCase' => 'PhabricatorTestCase', 'DiffusionMergedCommitsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionMirrorDeleteController' => 'DiffusionController', - 'DiffusionMirrorEditController' => 'DiffusionController', 'DiffusionPathChange' => 'Phobject', 'DiffusionPathChangeQuery' => 'Phobject', 'DiffusionPathCompleteController' => 'DiffusionController', @@ -7882,13 +7876,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( diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 0170817e6c..a7b7a51874 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -122,10 +122,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '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 1209f937de..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)) - ->appendControl( - 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/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 08fc9778bc..21c392cef5 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -129,37 +129,6 @@ final class DiffusionRepositoryEditMainController ->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')) @@ -1193,90 +1162,6 @@ final class DiffusionRepositoryEditMainController ); } - - 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(); 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/management/PhabricatorRepositoryManagementMirrorWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php index 99a2b08c8b..5db4899b41 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php @@ -12,12 +12,12 @@ 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, ), )); } @@ -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/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/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 9785a32611..5762386119 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1493,10 +1493,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()) 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/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 92bf5976ae..f432971d29 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -200,6 +200,29 @@ final class PhabricatorRepositoryURI 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 From 29d1115037b84454c4fd186c47f33f162cc337b9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2016 14:26:09 -0700 Subject: [PATCH 15/62] Swap Repository Edit UI to new code Summary: Ref T10748. This needs more extensive testing and is sure to have some rough edges, but seems to basically work so far. Throwing this up so I can work through it more deliberately and make notes. Test Plan: - Ran migration. - Used `bin/repository list` to list existing repositories. - Used `bin/repository update ` to update various repositories. - Updated a migrated, hosted Git repository. - Updated a migrated, observed Git repository. - Converted an observed repository into a hosted repository by toggling the I/O mode of the URI. - Conveted a hosted repository into an observed repository by toggling it back. - Created and activated a new empty hosted Git repository. - Created and activated an observed Git repository. - Updated a mirrored repository. - Cloned and pushed over HTTP. - Tried to HTTP push a read-only repository. - Cloned and pushed over SSH. - Tried to SSH push a read-only repository. - Updated several Mercurial repositories. - Updated several Subversion repositories. - Created and edited repositories via the API. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15842 --- .../20160503.repo.05.urimigrate.php | 82 ++ src/__phutil_library_map__.php | 22 - .../PhabricatorDiffusionApplication.php | 16 +- .../DiffusionRepositoryController.php | 142 +- .../DiffusionRepositoryCreateController.php | 849 ------------ ...ffusionRepositoryEditActionsController.php | 121 -- ...sionRepositoryEditAutomationController.php | 93 -- ...DiffusionRepositoryEditBasicController.php | 193 --- ...fusionRepositoryEditBranchesController.php | 248 ---- ...fusionRepositoryEditEncodingController.php | 107 -- ...ffusionRepositoryEditHostingController.php | 288 ---- .../DiffusionRepositoryEditMainController.php | 1221 ----------------- ...ffusionRepositoryEditStagingController.php | 91 -- ...ffusionRepositoryEditStorageController.php | 76 - ...sionRepositoryEditSubversionController.php | 118 -- .../DiffusionRepositoryListController.php | 13 +- .../controller/DiffusionServeController.php | 32 +- .../diffusion/editor/DiffusionURIEditor.php | 40 + .../DiffusionRepositoryManagementPanel.php | 2 +- ...ffusionRepositoryStatusManagementPanel.php | 14 +- .../diffusion/ssh/DiffusionSSHWorkflow.php | 67 +- .../editor/PhabricatorRepositoryEditor.php | 123 +- ...habricatorRepositoryManagementWorkflow.php | 1 + .../storage/PhabricatorRepository.php | 228 +-- .../PhabricatorRepositoryTransaction.php | 8 +- .../storage/PhabricatorRepositoryURI.php | 26 +- .../editengine/PhabricatorEditEngine.php | 4 + 27 files changed, 328 insertions(+), 3897 deletions(-) create mode 100644 resources/sql/autopatches/20160503.repo.05.urimigrate.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryCreateController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php 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/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c41fc1f0ba..b4f3aff738 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -749,25 +749,14 @@ 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', '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', @@ -4976,25 +4965,14 @@ 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', 'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DiffusionRepositoryEditController' => 'DiffusionController', 'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine', - 'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController', - 'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditproController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index a7b7a51874..c644365556 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -57,7 +57,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '/diffusion/' => array( $this->getQueryRoutePattern() => 'DiffusionRepositoryListController', - $this->getEditRoutePattern('editpro/') => + $this->getEditRoutePattern('edit/') => 'DiffusionRepositoryEditproController', 'new/' => 'DiffusionRepositoryNewController', '(?Pcreate)/' => 'DiffusionRepositoryCreateController', @@ -101,24 +101,10 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { => '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', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index b9e8fa40c6..9aad7f1c5b 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,24 @@ 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; - } + $display = (string)csprintf('%R', (string)$uri->getDisplayURI()); $input = javelin_tag( 'input', array( 'type' => 'text', - 'value' => (string)$command, + 'value' => $display, 'class' => 'diffusion-clone-uri', - 'sigil' => 'select-on-click', 'readonly' => 'true', )); - $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 $input; } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php deleted file mode 100644 index 580f021257..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ /dev/null @@ -1,849 +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()) - ->setViewer($this->getViewer()) - ->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/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/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 21c392cef5..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ /dev/null @@ -1,1221 +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 ($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->getLocalPath()); - - 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 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 6aebd13500..0000000000 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ /dev/null @@ -1,76 +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->getLocalPath(); - $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.')) - ->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/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/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index f1703655fa..06988ff4e3 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,25 @@ 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: + if ($request->isHTTPS()) { + $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; + } else { + $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; + } + + if (!$repository->canServeProtocol($protocol, false)) { + return new PhabricatorVCSResponse( + 403, + pht('This repository is not available over HTTP.')); + } + + if ($is_push) { + $can_write = $repository->canServeProtocol($protocol, 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/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 44ffd83e6e..9059430d0b 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -410,4 +410,44 @@ final class DiffusionURIEditor 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/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index efb165d801..156e1c436f 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -136,7 +136,7 @@ abstract class DiffusionRepositoryManagementPanel $repository = $this->getRepository(); $id = $repository->getID(); - return "/diffusion/editpro/{$id}/page/{$page}/"; + return "/diffusion/edit/{$id}/page/{$page}/"; } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php index eba7722f2f..a601bd1d64 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php @@ -135,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'; @@ -151,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'; 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/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index c528c91c99..fb97d7d908 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->getLocalPath(); - 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: @@ -205,22 +171,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->setLocalPath($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 +210,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 +235,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 +308,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()); @@ -721,6 +609,17 @@ final class PhabricatorRepositoryEditor } 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/management/PhabricatorRepositoryManagementWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php index 300f8ec50f..56a75764c8 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(); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 5762386119..1ca585851f 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -35,10 +35,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'; @@ -1201,26 +1197,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(); } @@ -1303,91 +1280,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); } private function getRawHTTPCloneURIObject() { @@ -1554,56 +1459,31 @@ 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; + } /** * Raise more useful errors when there are basic filesystem problems. @@ -2133,10 +2013,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); } } @@ -2156,10 +2035,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, @@ -2176,6 +2087,11 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $has_http = !PhabricatorEnv::getEnvConfig('security.require-https'); $has_http = ($has_http && $allow_http); + // HTTP is not supported for Subversion. + if ($this->isSVN()) { + $has_http = false; + } + // TODO: Maybe allow users to disable this by default somehow? $has_ssh = true; @@ -2188,12 +2104,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)); } } 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 f432971d29..227b13012f 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -246,6 +246,11 @@ final class PhabricatorRepositoryURI ); $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); @@ -409,7 +414,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; @@ -536,6 +541,25 @@ final class PhabricatorRepositoryURI ->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 )------------------------- */ diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index f67860f938..2c283a27f3 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -2035,6 +2035,10 @@ abstract class PhabricatorEditEngine return $fields; } + if (!$this->getSelectedPage()) { + return $fields; + } + $page_picks = array(); $default_key = head($pages)->getKey(); foreach ($pages as $page_key => $page) { From 95c284b95e5b5ba36746849a3de9bda9105bddb5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 May 2016 16:28:46 -0700 Subject: [PATCH 16/62] Fix a Harbormaster build issue with new URI code Summary: Ref T10748. We're returning a `PhabricatorRepositoryURI` here but the code expects an actual `PhutilURI`. Test Plan: This should clear this up in production: ``` Daemon 180278 STDE [Wed, 04 May 2016 23:25:16 +0000] [2016-05-04 23:25:16] EXCEPTION: (PhutilProxyException) Error while executing Task ID 1677075. {>} (RuntimeException) Object of class PhabricatorRepositoryURI could not be converted to string at [/src/error/PhutilErrorHandler.php:205] Daemon 180278 STDE [Wed, 04 May 2016 23:25:16 +0000] arcanist(head=master, ref.master=c58f1b9a2507), libcore(), phabricator(head=master, ref.master=29d1115037b8), phutil(head=master, ref.master=0709cd5cfc26), services(head=master, ref.master=04ae8c8f8e3b) Daemon 180278 STDE [Wed, 04 May 2016 23:25:16 +0000] #0 <#2> PhutilErrorHandler::handleError(integer, string, string, integer, array) called at [/src/applications/repository/storage/PhabricatorRepository.php:1200] Daemon 180278 STDE [Wed, 04 May 2016 23:25:16 +0000] #1 <#2> PhabricatorRepository::getPublicCloneURI() called at [/src/applications/repository/storage/PhabricatorRepositoryCommit.php:395] ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T10748 Differential Revision: https://secure.phabricator.com/D15844 --- src/applications/repository/storage/PhabricatorRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 1ca585851f..f5e883d20e 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1292,7 +1292,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return null; } - return head($clone_uris); + return head($clone_uris)->getEffectiveURI(); } private function getRawHTTPCloneURIObject() { From e422190eb530d9954ce7d5b8eb5a3d582ada4bf1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 4 May 2016 21:06:56 -0700 Subject: [PATCH 17/62] Fix create links when no repositories exist Summary: Fixes T10925. Sends users to /new/ instead of /create/ Test Plan: Visit page, see links, clicky clicky. Reviewers: epriestley, thoughtpolice Reviewed By: thoughtpolice Subscribers: thoughtpolice, Korvin Maniphest Tasks: T10925 Differential Revision: https://secure.phabricator.com/D15849 --- src/__phutil_library_map__.php | 2 - .../PhabricatorDiffusionApplication.php | 3 - .../DiffusionRepositoryNewController.php | 79 ------------------- .../PhabricatorRepositorySearchEngine.php | 15 +--- 4 files changed, 4 insertions(+), 95 deletions(-) delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryNewController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b4f3aff738..0d159e0586 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -763,7 +763,6 @@ phutil_register_library_map(array( 'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php', 'DiffusionRepositoryManageController' => 'applications/diffusion/controller/DiffusionRepositoryManageController.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', @@ -4979,7 +4978,6 @@ phutil_register_library_map(array( 'DiffusionRepositoryListController' => 'DiffusionController', 'DiffusionRepositoryManageController' => 'DiffusionController', 'DiffusionRepositoryManagementPanel' => 'Phobject', - 'DiffusionRepositoryNewController' => 'DiffusionController', 'DiffusionRepositoryPath' => 'Phobject', 'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryRef' => 'Phobject', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index c644365556..176ee81872 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -59,9 +59,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { => 'DiffusionRepositoryListController', $this->getEditRoutePattern('edit/') => 'DiffusionRepositoryEditproController', - 'new/' => 'DiffusionRepositoryNewController', - '(?Pcreate)/' => 'DiffusionRepositoryCreateController', - '(?Pimport)/' => 'DiffusionRepositoryCreateController', 'pushlog/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', 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/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 059ae9a76d..18519f56be 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -242,16 +242,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 +255,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; } From 01289f3f486a6a8f4c228f5a51942bfe82d5a537 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 May 2016 18:24:59 -0700 Subject: [PATCH 18/62] Generate syntax highlighting CSS from a reusable map Summary: Ref T9790. This prepares the syntax color rules to be reused in mail. This goes about halfway toward T5701 by sort-of supporting different styles but not really. Test Plan: - Ran `bin/celerity syntax` to regenerate syntax map. - Viewed some highlighted code, didn't see any differences. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9790 Differential Revision: https://secure.phabricator.com/D15846 --- resources/celerity/map.php | 12 +- resources/celerity/packages.php | 1 + src/__phutil_library_map__.php | 6 + .../CelerityManagementSyntaxWorkflow.php | 67 ++++++++ .../syntax/PhabricatorDefaultSyntaxStyle.php | 76 +++++++++ .../syntax/PhabricatorSyntaxStyle.php | 30 ++++ webroot/rsrc/css/core/syntax.css | 105 +----------- webroot/rsrc/css/syntax/syntax-default.css | 150 ++++++++++++++++++ 8 files changed, 340 insertions(+), 107 deletions(-) create mode 100644 src/applications/celerity/management/CelerityManagementSyntaxWorkflow.php create mode 100644 src/infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php create mode 100644 src/infrastructure/syntax/PhabricatorSyntaxStyle.php create mode 100644 webroot/rsrc/css/syntax/syntax-default.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e31cea82c9..90d5720d2c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '5242a859', + 'core.pkg.css' => 'b7b8d101', 'core.pkg.js' => '6972d365', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -105,7 +105,7 @@ return array( 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'd0801452', 'rsrc/css/core/remarkup.css' => '787105d6', - 'rsrc/css/core/syntax.css' => '9fd11da8', + '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', @@ -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', @@ -877,7 +878,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 +1241,9 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '5101175d' => array( + 'syntax-default-css', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -2192,6 +2197,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/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0d159e0586..ffbda83d63 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', @@ -2273,6 +2274,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', @@ -3454,6 +3456,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', @@ -4397,6 +4400,7 @@ phutil_register_library_map(array( 'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor', 'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor', 'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow', + 'CelerityManagementSyntaxWorkflow' => 'CelerityManagementWorkflow', 'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow', 'CelerityPhabricatorResourceController' => 'CelerityResourceController', 'CelerityPhabricatorResources' => 'CelerityResourcesOnDisk', @@ -6764,6 +6768,7 @@ phutil_register_library_map(array( 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', + 'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle', 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDestructionEngineExtension' => 'Phobject', @@ -8150,6 +8155,7 @@ phutil_register_library_map(array( 'PhabricatorSupportApplication' => 'PhabricatorApplication', 'PhabricatorSyntaxHighlighter' => 'Phobject', 'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorSyntaxStyle' => 'Phobject', 'PhabricatorSystemAction' => 'Phobject', 'PhabricatorSystemActionEngine' => 'Phobject', 'PhabricatorSystemActionGarbageCollector' => 'PhabricatorGarbageCollector', 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/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..c61210d06b --- /dev/null +++ b/src/infrastructure/syntax/PhabricatorSyntaxStyle.php @@ -0,0 +1,30 @@ +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(); + } + +} 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/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 +} From 19aac8e8d3be3676cbe8e064f38f428c67c451c3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 May 2016 18:42:47 -0700 Subject: [PATCH 19/62] Pass the new default syntax highlighting map to the remarkup engine Summary: Ref T9790. This passes the map down so we can generate highlighted mail. Test Plan: Generated this relatively respectable-looking HTML mail: {F1258558} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9790 Differential Revision: https://secure.phabricator.com/D15848 --- .../markup/PhabricatorMarkupEngine.php | 4 ++++ .../syntax/PhabricatorSyntaxStyle.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+) 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/PhabricatorSyntaxStyle.php b/src/infrastructure/syntax/PhabricatorSyntaxStyle.php index c61210d06b..3f75f5c639 100644 --- a/src/infrastructure/syntax/PhabricatorSyntaxStyle.php +++ b/src/infrastructure/syntax/PhabricatorSyntaxStyle.php @@ -27,4 +27,18 @@ abstract class PhabricatorSyntaxStyle extends Phobject { ->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(); + } + } From 2025ecd3d826a5c8918f7e521b1785715b2ca426 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 May 2016 04:42:29 -0700 Subject: [PATCH 20/62] Rough cut of inline comment context Summary: Ref T10694. This is still missing some pieces, but seems to get most of the data into the mail in a plausible format: - When an inline remarks on code, show the patch inline in the mail body. - When an inline replies to another inline, show that other inline in the mail body. - Apply remarkup rendering to inline content. - Apply basic styling to mail body blocks. Not covered yet: - Syntax highlighting. - Diff highlighting. - Maybe clearer style/layout hints to connect comments to what they reply to? Current approach might get messy with inlines that have blockquotes and code blocks inside them, for example. - I probably want to cap the amount of diff context we ever show to ~7 lines, even if you drag over 200 lines of code. - CSS is a generally a bit rough still. - The `unified-comment-context` option is effectively always on now, and should be removed. - Text section is getting indented right now but probably shouldn't be. - Spacing, etc., might be a bit off. Test Plan: Rigged Home to render these things, got a plausible-looking render (top is text, bottom is HTML): {F1259052} Sent myself some inline comment mail, got a plausible result. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10694 Differential Revision: https://secure.phabricator.com/D15850 --- src/__phutil_library_map__.php | 2 + .../editor/DifferentialTransactionEditor.php | 131 +----- .../DifferentialInlineCommentMailView.php | 382 ++++++++++++++++++ 3 files changed, 387 insertions(+), 128 deletions(-) create mode 100644 src/applications/differential/mail/DifferentialInlineCommentMailView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ffbda83d63..cf9896fcb5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -459,6 +459,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', @@ -4660,6 +4661,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentInterface', ), 'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController', + 'DifferentialInlineCommentMailView' => 'Phobject', 'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index fabd2511ba..fed5c3728d 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1374,138 +1374,13 @@ 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( PhabricatorLiskDAO $object, array $inlines) { - - $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()) + return id(new DifferentialInlineCommentMailView()) ->setViewer($this->getActor()) - ->withIDs($changeset_ids) - ->needHunks(true) - ->execute(); - - $inline_groups = DifferentialTransactionComment::sortAndGroupInlines( - $inlines, - $changesets); - - 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 = new PhabricatorMetaMTAMailSection(); - foreach ($inline_groups as $changeset_id => $group) { - $changeset = idx($changesets, $changeset_id); - if (!$changeset) { - continue; - } - - 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; - } - - $inline_content = $comment->getContent(); - - 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); - - $section - ->addFragment('================') - ->addFragment(pht('Comment at: %s:%s', $file, $range)) - ->addPlaintextFragment($patch) - ->addHTMLFragment($this->renderPatchHTMLForMail($patch)) - ->addFragment('----------------') - ->addFragment($nested_comments) - ->addFragment(null); - } - } - } - - return $section; + ->setInlines($inlines) + ->buildMailSection(); } private function loadDiff($phid, $need_changesets = false) { diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php new file mode 100644 index 0000000000..8b78fbc752 --- /dev/null +++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php @@ -0,0 +1,382 @@ +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 = $this->getPatch($hunk_parser, $comment); + $context_text = $this->renderPatch($comment, $patch, false); + $context_html = $this->renderPatch($comment, $patch, true); + } + + $render_text = $this->renderInline($comment, false, false); + $render_html = $this->renderInline($comment, true, false); + + $section->addPlaintextFragment($context_text); + $section->addHTMLFragment($context_html); + + $section->addPlaintextFragment($spacer_text); + $section->addHTMLFragment($spacer_html); + + $section->addPlaintextFragment($render_text); + $section->addHTMLFragment($render_html); + + 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; + } + + $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; + } + + $engine = PhabricatorMarkupEngine::newMarkupEngine(array()) + ->setConfig('viewer', $viewer) + ->setConfig('uri.base', $production_uri) + ->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: 4px 8px;', + 'background: #F8F9FC;', + 'border-left: 3px solid #a7b5bf;', + ); + + $styles = implode(' ', $styles); + + return phutil_tag( + 'div', + array( + 'style' => $styles, + ), + $block); + } + + private function getPatch( + DifferentialHunkParser $parser, + DifferentialTransactionComment $comment) { + + $changeset = $this->getChangeset($comment->getChangesetID()); + $hunks = $changeset->getHunks(); + + $is_new = $comment->getIsNewFile(); + $start = $comment->getLineNumber(); + $length = $comment->getLineLength(); + + $diff = $parser->makeContextDiff($hunks, $is_new, $start, $length, 1); + + return $diff; + } + + private function renderPatch( + DifferentialTransactionComment $comment, + $patch, + $is_html) { + + $patch = phutil_split_lines($patch); + + // Remove the "@@ -x,y +u,v @@" line. + array_shift($patch); + + $patch = implode('', $patch); + + if ($is_html) { + $style = array( + 'font: 11px/15px "Menlo", "Consolas", "Monaco", monospace;', + 'padding: 0', + 'margin: 0;', + ); + + $style = implode(' ', $style); + $patch = phutil_tag( + 'pre', + array( + 'style' => $style, + ), + $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(); + + $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 = phutil_tag('strong', array(), $header); + } + + if ($with_author) { + $author = $this->getAuthor($comment->getAuthorPHID()); + } else { + $author = null; + } + + if ($author) { + $byline = '@'.$author->getName(); + + if ($is_html) { + $byline = phutil_tag('strong', array(), $byline); + } + + $header = pht('%s wrote in %s', $byline, $header); + } else { + $header = pht('In %s', $header); + } + + if ($is_html) { + $header = phutil_tag( + 'div', + array( + 'style' => 'font-style: italic', + ), + $header); + } + + return $header; + } + +} From 94c7bb605c3561161a4211e8dca3a802af0f4ce4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 May 2016 09:46:44 -0700 Subject: [PATCH 21/62] Highlight inline diff context in HTML mail Summary: Ref T10694. Ref T9790. When generating inline diff context, highlight it and then mangle the highlighted output into `style="..."` so it works in HTML. Also try to tighten up some spacing/formatting stuff. Test Plan: Got some output in this vein: {F1259937} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9790, T10694 Differential Revision: https://secure.phabricator.com/D15852 --- src/__phutil_library_map__.php | 2 + .../editor/DifferentialTransactionEditor.php | 20 ++-- .../DifferentialInlineCommentMailView.php | 82 ++++++++++++---- .../parser/DifferentialChangesetParser.php | 42 ++++++++ ...DifferentialChangesetOneUpMailRenderer.php | 98 +++++++++++++++++++ .../view/PhabricatorMetaMTAMailBody.php | 7 +- 6 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cf9896fcb5..380ed6b3ac 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -370,6 +370,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', @@ -4559,6 +4560,7 @@ phutil_register_library_map(array( 'DifferentialChangesetFileTreeSideNavBuilder' => 'Phobject', 'DifferentialChangesetHTMLRenderer' => 'DifferentialChangesetRenderer', 'DifferentialChangesetListView' => 'AphrontView', + 'DifferentialChangesetOneUpMailRenderer' => 'DifferentialChangesetRenderer', 'DifferentialChangesetOneUpRenderer' => 'DifferentialChangesetHTMLRenderer', 'DifferentialChangesetOneUpTestRenderer' => 'DifferentialChangesetTestRenderer', 'DifferentialChangesetParser' => 'Phobject', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index fed5c3728d..4a41eb671a 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(); @@ -1374,13 +1372,23 @@ final class DifferentialTransactionEditor return $result; } - private function renderInlineCommentsForMail( + private function appendInlineCommentsForMail( PhabricatorLiskDAO $object, - array $inlines) { - return id(new DifferentialInlineCommentMailView()) + array $inlines, + PhabricatorMetaMTAMailBody $body) { + + $section = id(new DifferentialInlineCommentMailView()) ->setViewer($this->getActor()) ->setInlines($inlines) ->buildMailSection(); + + $header = pht('INLINE COMMENTS'); + + $section_text = "\n".$section->getPlaintext(); + $section_html = $section->getHTML(); + + $body->addPlaintextSection($header, $section_text, false); + $body->addHTMLSection($header, $section_html); } private function loadDiff($phid, $need_changesets = false) { diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php index 8b78fbc752..8b28b29c99 100644 --- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php +++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php @@ -71,9 +71,11 @@ final class DifferentialInlineCommentMailView $context_html = $this->renderInline($parent, true, true); } } else { - $patch = $this->getPatch($hunk_parser, $comment); - $context_text = $this->renderPatch($comment, $patch, false); - $context_html = $this->renderPatch($comment, $patch, true); + $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); @@ -83,7 +85,6 @@ final class DifferentialInlineCommentMailView $section->addHTMLFragment($context_html); $section->addPlaintextFragment($spacer_text); - $section->addHTMLFragment($spacer_html); $section->addPlaintextFragment($render_text); $section->addHTMLFragment($render_html); @@ -180,6 +181,18 @@ final class DifferentialInlineCommentMailView $content = $this->renderRemarkupContent($content, $is_html); if ($is_quote) { + if ($is_html) { + $style = array( + 'padding: 4px 0;', + ); + + $content = phutil_tag( + 'div', + array( + 'style' => implode(' ', $style), + ), + $content); + } $header = $this->renderHeader($comment, $is_html, true); } else { $header = null; @@ -254,6 +267,7 @@ final class DifferentialInlineCommentMailView 'padding: 4px 8px;', 'background: #F8F9FC;', 'border-left: 3px solid #a7b5bf;', + 'margin: 4px 0 0;', ); $styles = implode(' ', $styles); @@ -268,18 +282,43 @@ final class DifferentialInlineCommentMailView private function getPatch( DifferentialHunkParser $parser, - DifferentialTransactionComment $comment) { + DifferentialTransactionComment $comment, + $is_html) { $changeset = $this->getChangeset($comment->getChangesetID()); - $hunks = $changeset->getHunks(); - $is_new = $comment->getIsNewFile(); $start = $comment->getLineNumber(); $length = $comment->getLineLength(); - $diff = $parser->makeContextDiff($hunks, $is_new, $start, $length, 1); + if (!$is_html) { + $hunks = $changeset->getHunks(); + $patch = $parser->makeContextDiff($hunks, $is_new, $start, $length, 1); + $patch = phutil_split_lines($patch); - return $diff; + // 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 - 1, $length + 3, array()); } private function renderPatch( @@ -287,17 +326,10 @@ final class DifferentialInlineCommentMailView $patch, $is_html) { - $patch = phutil_split_lines($patch); - - // Remove the "@@ -x,y +u,v @@" line. - array_shift($patch); - - $patch = implode('', $patch); - if ($is_html) { $style = array( 'font: 11px/15px "Menlo", "Consolas", "Monaco", monospace;', - 'padding: 0', + 'padding: 4px 0;', 'margin: 0;', ); @@ -346,7 +378,12 @@ final class DifferentialInlineCommentMailView $header = "{$path}:{$range}"; if ($is_html) { - $header = phutil_tag('strong', array(), $header); + $header = phutil_tag( + 'span', + array( + 'style' => 'color: #000000', + ), + $header); } if ($with_author) { @@ -359,7 +396,12 @@ final class DifferentialInlineCommentMailView $byline = '@'.$author->getName(); if ($is_html) { - $byline = phutil_tag('strong', array(), $byline); + $byline = phutil_tag( + 'span', + array( + 'style' => 'color: #000000', + ), + $byline); } $header = pht('%s wrote in %s', $byline, $header); @@ -371,7 +413,7 @@ final class DifferentialInlineCommentMailView $header = phutil_tag( 'div', array( - 'style' => 'font-style: italic', + 'style' => 'font-style: italic; color: #74777d', ), $header); } diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index f6c7309ec7..ef45896081 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -50,6 +50,7 @@ final class DifferentialChangesetParser extends Phobject { private $showEditAndReplyLinks = true; private $canMarkDone; private $objectOwnerPHID; + private $offsetMode; private $rangeStart; private $rangeEnd; @@ -138,6 +139,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; @@ -829,6 +839,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( @@ -1632,5 +1658,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/render/DifferentialChangesetOneUpMailRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php new file mode 100644 index 0000000000..ee1508d535 --- /dev/null +++ b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php @@ -0,0 +1,98 @@ +buildPrimitives($range_start, $range_len); + return $this->renderPrimitives($primitives, $rows); + } + + protected function renderPrimitives(array $primitives, $rows) { + $out = array(); + 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[] = phutil_tag( + 'div', + array( + 'style' => $style, + ), + $p['render']); + break; + default: + break; + } + } + + $style_map = id(new PhabricatorDefaultSyntaxStyle()) + ->getRemarkupStyleMap(); + + $styled_body = id(new PhutilPygmentizeParser()) + ->setMap($style_map) + ->parse((string)hsprintf('%s', $out)); + + return phutil_safe_html($styled_body); + } + +} 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; } From 1baef494c1b78cc084cec04a51e4d073e6dee309 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 May 2016 10:53:59 -0700 Subject: [PATCH 22/62] Pick context windows for inlines in a slightly smarter way Summary: Ref T10694. This mostly prevents us from having a degenerate case if someone leaves a 200-line inline. - For one-line inlines, show 1 line of context above and below (3 lines total). - For 3+ line inlines, show just the inline. - For 7+ line inlines, show only the first part. Test Plan: Made a bunch of weird long/short/different-sized comments, saw reasonble-appearing context in text and HTML mail output. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10694 Differential Revision: https://secure.phabricator.com/D15853 --- .../DifferentialInlineCommentMailView.php | 25 +++++++++++++++++-- .../parser/DifferentialChangesetParser.php | 2 +- ...DifferentialChangesetOneUpMailRenderer.php | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php index 8b28b29c99..4c6dd5ea8b 100644 --- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php +++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php @@ -290,9 +290,27 @@ final class DifferentialInlineCommentMailView $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, 1); + $patch = $parser->makeContextDiff( + $hunks, + $is_new, + $start, + $length, + $context); $patch = phutil_split_lines($patch); // Remove the "@@ -x,y +u,v @@" line. @@ -318,7 +336,10 @@ final class DifferentialInlineCommentMailView $parser->setRenderer(new DifferentialChangesetOneUpMailRenderer()); - return $parser->render($start - 1, $length + 3, array()); + return $parser->render( + $start - $context, + $length + 1 + (2 * $context), + array()); } private function renderPatch( diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index ef45896081..f7815538ac 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -852,7 +852,7 @@ final class DifferentialChangesetParser extends Phobject { $range_end = $this->getOffset($offset_map, $range_start + $range_len); $range_start = $this->getOffset($offset_map, $range_start); - $range_len = $range_end - $range_start; + $range_len = ($range_end - $range_start); } $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset); diff --git a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php index ee1508d535..cb1fda70c6 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php @@ -73,6 +73,8 @@ final class DifferentialChangesetOneUpMailRenderer } } + $style = "padding: 0 2px; {$style}"; + $out[] = phutil_tag( 'div', array( From fde02c4b4edfb70a40cc98f28d9e173202098ef1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 May 2016 16:16:09 -0700 Subject: [PATCH 23/62] Fix protocol serve detection for clustered repositories that terminate HTTPS Summary: Ref T10927. Pretty sure the issue is: - User makes an HTTPS request. - Load balancer terminates it, but with an `X-Forwarded-Proto` header. - `secure001` (or whatever; acting as web host) proxies it to `secure002` (or whatever; acting as a repository host). **This** connection is plain HTTP. - Since this proxied connection is plain HTTP, we check if the repository can serve over "http", but it can't: only "https". So we fail incorrectly, even though the original user request was HTTPS. In the long run we should probably forward the `X-Forwarded-Proto` header, but that has some weird implications and it's broadly fine to allow either protocol to serve as long as the other one is active: configuration like `security.require-https` is already stronger than these settings. Test Plan: This is likely only observable in production, but normal cloning still works locally. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10927 Differential Revision: https://secure.phabricator.com/D15856 --- .../controller/DiffusionServeController.php | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 06988ff4e3..69b8c64b17 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -267,20 +267,27 @@ 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 { - if ($request->isHTTPS()) { - $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; - } else { - $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; - } + // 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; - if (!$repository->canServeProtocol($protocol, false)) { + $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($protocol, true); + $can_write = + $repository->canServeProtocol($proto_https, true) || + $repository->canServeProtocol($proto_http, true); if (!$can_write) { return new PhabricatorVCSResponse( 403, From cc579be6f149f28ef0431b540e841e0799017ef6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 6 May 2016 07:56:08 -0700 Subject: [PATCH 24/62] Use more precise language to describe what mirroring is Summary: Fixes T10928. This behavior hasn't changed at all and this is what, e.g., `git push --mirror` means, and the other behavior isn't possible, but be more explicit about the inherent destructive potential of mirroring. Test Plan: Read documentation. Reviewers: chad, sascha-egerer Reviewed By: sascha-egerer Subscribers: sascha-egerer Maniphest Tasks: T10928 Differential Revision: https://secure.phabricator.com/D15861 --- src/docs/user/userguide/diffusion_uris.diviner | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index b560f4ab3f..fc045f9704 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -23,9 +23,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 +102,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. From 053d6111e4ab0f8ad4051ee193e7cca4f55e2815 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 May 2016 16:57:37 -0700 Subject: [PATCH 25/62] Refine inline style rendering in email Summary: Ref T10694. Move the inline style more toward a mix of standard`
` style and the web UI style for inlines.

Test Plan: See screenshots in comments.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10694

Differential Revision: https://secure.phabricator.com/D15857
---
 .../DifferentialInlineCommentMailView.php     | 130 ++++++++++++++----
 ...DifferentialChangesetOneUpMailRenderer.php |   2 +-
 2 files changed, 104 insertions(+), 28 deletions(-)

diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
index 4c6dd5ea8b..0a0a85306f 100644
--- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php
+++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
@@ -82,12 +82,25 @@ final class DifferentialInlineCommentMailView
         $render_html = $this->renderInline($comment, true, false);
 
         $section->addPlaintextFragment($context_text);
-        $section->addHTMLFragment($context_html);
-
         $section->addPlaintextFragment($spacer_text);
-
         $section->addPlaintextFragment($render_text);
-        $section->addHTMLFragment($render_html);
+
+        $style = array(
+          'border: 1px solid #C7CCD9;',
+          'border-radius: 3px;',
+        );
+
+        $html_fragment = phutil_tag(
+          'div',
+          array(
+            'style' => implode(' ', $style),
+          ),
+          array(
+            $context_html,
+            $render_html,
+          ));
+
+        $section->addHTMLFragment($html_fragment);
 
         if (!$is_last_group || !$is_last_inline) {
           $section->addPlaintextFragment($spacer_text);
@@ -181,23 +194,28 @@ final class DifferentialInlineCommentMailView
     $content = $this->renderRemarkupContent($content, $is_html);
 
     if ($is_quote) {
-      if ($is_html) {
-        $style = array(
-          'padding: 4px 0;',
-        );
-
-        $content = phutil_tag(
-          'div',
-          array(
-            'style' => implode(' ', $style),
-          ),
-          $content);
-      }
       $header = $this->renderHeader($comment, $is_html, true);
     } else {
       $header = null;
     }
 
+    if ($is_html) {
+      $style = array(
+        'padding: 8px 12px;',
+      );
+
+      if ($is_quote) {
+        $style[] = 'color: #74777D;';
+      }
+
+      $content = phutil_tag(
+        'div',
+        array(
+          'style' => implode(' ', $style),
+        ),
+        $content);
+    }
+
     $parts = array(
       $header,
       "\n",
@@ -230,9 +248,14 @@ final class DifferentialInlineCommentMailView
       $mode = PhutilRemarkupEngine::MODE_TEXT;
     }
 
+    $attributes = array(
+      'style' => 'padding: 0; margin: 0;',
+    );
+
     $engine = PhabricatorMarkupEngine::newMarkupEngine(array())
       ->setConfig('viewer', $viewer)
       ->setConfig('uri.base', $production_uri)
+      ->setConfig('default.p.attributes', $attributes)
       ->setMode($mode);
 
     try {
@@ -264,10 +287,12 @@ final class DifferentialInlineCommentMailView
 
   private function quoteHTML($block) {
     $styles = array(
-      'padding: 4px 8px;',
-      'background: #F8F9FC;',
-      'border-left: 3px solid #a7b5bf;',
-      'margin: 4px 0 0;',
+      'padding: 0;',
+      'background: #F7F7F7;',
+      'border-color: #e3e4e8;',
+      'border-style: solid;',
+      'border-width: 0 0 1px 0;',
+      'margin: 0;',
     );
 
     $styles = implode(' ', $styles);
@@ -389,6 +414,9 @@ final class DifferentialInlineCommentMailView
     $changeset = $this->getChangeset($comment->getChangesetID());
     $path = $changeset->getFilename();
 
+    // Only show the filename.
+    $path = basename($path);
+
     $start = $comment->getLineNumber();
     $length = $comment->getLineLength();
     if ($length) {
@@ -402,7 +430,7 @@ final class DifferentialInlineCommentMailView
       $header = phutil_tag(
         'span',
         array(
-          'style' => 'color: #000000',
+          'style' => 'color: #4b4d51; font-weight: bold;',
         ),
         $header);
     }
@@ -414,32 +442,80 @@ final class DifferentialInlineCommentMailView
     }
 
     if ($author) {
-      $byline = '@'.$author->getName();
+      $byline = $author->getName();
 
       if ($is_html) {
         $byline = phutil_tag(
           'span',
           array(
-            'style' => 'color: #000000',
+            'style' => 'color: #4b4d51; font-weight: bold;',
           ),
           $byline);
       }
 
       $header = pht('%s wrote in %s', $byline, $header);
-    } else {
-      $header = pht('In %s', $header);
     }
 
     if ($is_html) {
+      $link_href = $this->getInlineURI($comment);
+      if ($link_href) {
+        $link_style = array(
+          'float: right;',
+        );
+
+        $link = phutil_tag(
+          'a',
+          array(
+            'style' => implode(' ', $link_style),
+            'href' => $link_href,
+          ),
+          pht('View Inline'));
+      } else {
+        $link = null;
+      }
+
+      $style = array(
+        'color: #74777d;',
+        'background: #eff2f4;',
+        'padding: 4px 8px;',
+        'overflow: hidden;',
+      );
+
       $header = phutil_tag(
         'div',
         array(
-          'style' => 'font-style: italic; color: #74777d',
+          'style' => implode(' ', $style),
         ),
-        $header);
+        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/render/DifferentialChangesetOneUpMailRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
index cb1fda70c6..4c34ebace5 100644
--- a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
@@ -73,7 +73,7 @@ final class DifferentialChangesetOneUpMailRenderer
             }
           }
 
-          $style = "padding: 0 2px; {$style}";
+          $style = "padding: 0 8px; margin: 0 4px; {$style}";
 
           $out[] = phutil_tag(
             'div',

From 371051ff377977783f59611aaccfbf5bf89b85e3 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Fri, 6 May 2016 10:48:16 -0700
Subject: [PATCH 26/62] Minor tweaks to pre/inline style for inline comments in
 HTML mail

Summary:
Ref T10694.

  - Shift margins/padding around so inlines with multiple paragraphs get reasonable spacing.
  - Add `text-decoration: none` to the "View Inline" link to kill the underline.

Test Plan: {F1265342}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10694

Differential Revision: https://secure.phabricator.com/D15863
---
 .../editor/DifferentialTransactionEditor.php         | 12 +++++++++++-
 .../mail/DifferentialInlineCommentMailView.php       |  8 +++++---
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php
index 4a41eb671a..0db490769f 100644
--- a/src/applications/differential/editor/DifferentialTransactionEditor.php
+++ b/src/applications/differential/editor/DifferentialTransactionEditor.php
@@ -1385,7 +1385,17 @@ final class DifferentialTransactionEditor
     $header = pht('INLINE COMMENTS');
 
     $section_text = "\n".$section->getPlaintext();
-    $section_html = $section->getHTML();
+
+    $style = array(
+      'margin: 12px 0;',
+    );
+
+    $section_html = phutil_tag(
+      'div',
+      array(
+        'style' => implode(' ', $style),
+      ),
+      $section->getHTML());
 
     $body->addPlaintextSection($header, $section_text, false);
     $body->addHTMLSection($header, $section_html);
diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
index 0a0a85306f..5fc13c34c5 100644
--- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php
+++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
@@ -201,7 +201,8 @@ final class DifferentialInlineCommentMailView
 
     if ($is_html) {
       $style = array(
-        'padding: 8px 12px;',
+        'margin: 8px 0;',
+        'padding: 0 12px;',
       );
 
       if ($is_quote) {
@@ -249,7 +250,7 @@ final class DifferentialInlineCommentMailView
     }
 
     $attributes = array(
-      'style' => 'padding: 0; margin: 0;',
+      'style' => 'padding: 0; margin: 8px;',
     );
 
     $engine = PhabricatorMarkupEngine::newMarkupEngine(array())
@@ -461,6 +462,7 @@ final class DifferentialInlineCommentMailView
       if ($link_href) {
         $link_style = array(
           'float: right;',
+          'text-decoration: none;',
         );
 
         $link = phutil_tag(
@@ -477,7 +479,7 @@ final class DifferentialInlineCommentMailView
       $style = array(
         'color: #74777d;',
         'background: #eff2f4;',
-        'padding: 4px 8px;',
+        'padding: 6px 8px;',
         'overflow: hidden;',
       );
 

From 412fc3455730408d8b868bf30bafda20b803b8cc Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Fri, 6 May 2016 11:46:46 -0700
Subject: [PATCH 27/62] Improve inline mail snippet rendering, possibly fixing
 Airmail?

Summary:
Ref T10694. General improvements:

  - Remove leading empty lines from context snippets.
  - Remove trailing empty lines from context snippets.
  - If we removed everything, render a note.
  - Try using `style` instead of `
`? My thinking is that maybe Airmail has weird default rules for `
`, since that's the biggest / most obvious thing that's different about this element to me.

Test Plan: Viewed normal comments locally, faked a comment on an empty line in the middle of a lot of other empty lines.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10694

Differential Revision: https://secure.phabricator.com/D15864
---
 .../DifferentialInlineCommentMailView.php     |  4 +-
 ...DifferentialChangesetOneUpMailRenderer.php | 59 ++++++++++++++++---
 2 files changed, 53 insertions(+), 10 deletions(-)

diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
index 5fc13c34c5..554bb66a63 100644
--- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php
+++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
@@ -376,13 +376,15 @@ final class DifferentialInlineCommentMailView
     if ($is_html) {
       $style = array(
         'font: 11px/15px "Menlo", "Consolas", "Monaco", monospace;',
+        'white-space: pre-wrap;',
+        'clear: both;',
         'padding: 4px 0;',
         'margin: 0;',
       );
 
       $style = implode(' ', $style);
       $patch = phutil_tag(
-        'pre',
+        'div',
         array(
           'style' => $style,
         ),
diff --git a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
index 4c34ebace5..042047b4a8 100644
--- a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
@@ -50,6 +50,7 @@ final class DifferentialChangesetOneUpMailRenderer
 
   protected function renderPrimitives(array $primitives, $rows) {
     $out = array();
+
     foreach ($primitives as $k => $p) {
       $type = $p['type'];
       switch ($type) {
@@ -73,26 +74,66 @@ final class DifferentialChangesetOneUpMailRenderer
             }
           }
 
-          $style = "padding: 0 8px; margin: 0 4px; {$style}";
-
-          $out[] = phutil_tag(
-            'div',
-            array(
-              'style' => $style,
-            ),
-            $p['render']);
+          $out[] = array(
+            'style' => $style,
+            'render' => $p['render'],
+            'text' => (string)$p['render'],
+          );
           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', $out));
+      ->parse((string)hsprintf('%s', $render));
 
     return phutil_safe_html($styled_body);
   }

From 612a93229fa3004c0a1f1da3e1d4155aa544dd20 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Fri, 6 May 2016 12:36:48 -0700
Subject: [PATCH 28/62] Fix some pagination/redirect issues for repositories

Summary:
Ref T10923. Paging wasn't being applied correctly when creating //new// repositories after API changes.

Also, the first redirect after creation wasn't sending users to the right place.

Test Plan:
  - Created a new repostiory, got redirected properly.
  - Verified that new repostiory flow has the correct fields (name, callsign, etc) and Conduit API has the correct fields (everything).

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15865
---
 .../transactions/editengine/PhabricatorEditEngine.php | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
index 2c283a27f3..1717b2cc7a 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -497,6 +497,10 @@ abstract class PhabricatorEditEngine
   }
 
   public function getEffectiveObjectViewURI($object) {
+    if ($this->getIsCreate()) {
+      return $this->getObjectViewURI($object);
+    }
+
     $page = $this->getSelectedPage();
     if ($page) {
       $view_uri = $page->getViewURI();
@@ -839,6 +843,13 @@ abstract class PhabricatorEditEngine
     }
 
     $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) {

From 846eec7563d8e04a6110b96bde3a6a1d6e61d420 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 06:28:35 -0700
Subject: [PATCH 29/62] Put "Push Policy" last in Diffusion, make editing
 Spaces work

Summary:
Ref T10923.

  - The "Policy" edit form currently goes "Push, View, Edit". Reorder the defaults to "View, Edit, Push".
  - Editing Spaces doesn't currently work: the element appears in the UI, but isn't actually processed when handling transactions. Make that work.

Test Plan:
  - Edited a repository policies, saw "View, Edit, Push".
  - Moved a repository between Spaces.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15866
---
 .../editor/DiffusionRepositoryEditEngine.php  | 29 +++++++++++++++++++
 ...usionRepositoryPoliciesManagementPanel.php |  1 +
 2 files changed, 30 insertions(+)

diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
index f421a1af18..3d510f4648 100644
--- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
@@ -165,6 +165,35 @@ final class DiffusionRepositoryEditEngine
     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();
 
diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php
index da53970fee..081869fbaa 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php
@@ -49,6 +49,7 @@ final class DiffusionRepositoryPoliciesManagementPanel
     return array(
       'policy.view',
       'policy.edit',
+      'spacePHID',
       'policy.push',
     );
   }

From 8512f9358e60573011d935b4ae2310c4743db496 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 07:01:37 -0700
Subject: [PATCH 30/62] Update redirect/cancel URIs for repository dialogs

Summary:
Ref T10923. Some of the dialogs ("Deactivate Repository", "Test Automation", etc.) had cancel or redirect URIs which I missed originally.

Go through them and make sure they all point to the right places.

Also removed one unused controller which I missed the first time around.

Test Plan:
  - Opened all these dialogs in a new tab with Command-Click.
  - Clicked every "cancel" and "submit" button on all of these dialogs.
  - Got consistently sent to the place I came from.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15867
---
 src/__phutil_library_map__.php                |   2 -
 ...fusionRepositoryEditActivateController.php |  30 +++--
 ...usionRepositoryEditDangerousController.php |  48 +++----
 ...iffusionRepositoryEditDeleteController.php |   6 +-
 ...iffusionRepositoryEditUpdateController.php |   8 +-
 .../DiffusionRepositorySymbolsController.php  | 120 ------------------
 ...sionRepositoryTestAutomationController.php |   8 +-
 7 files changed, 55 insertions(+), 167 deletions(-)
 delete mode 100644 src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 380ed6b3ac..6b71b47c43 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -774,7 +774,6 @@ 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',
     'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php',
     'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
     'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
@@ -4994,7 +4993,6 @@ phutil_register_library_map(array(
     'DiffusionRepositoryStagingManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel',
-    'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryTag' => 'Phobject',
     'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
index 55caaa59b9..5105ff6ee8 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
@@ -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,24 @@ 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('Deactivate this repository?');
+      $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');
+      $body = pht('Activate this repository?');
+      $submit = pht('Activate Repository');
     }
+
+    return $this->newDialog()
+      ->setTitle($title)
+      ->appendChild($body)
+      ->addSubmitButton($submit)
+      ->addCancelButton($panel_uri);
   }
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
index f2a49f820a..f24a23bb8e 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
@@ -13,7 +13,9 @@ final class DiffusionRepositoryEditDangerousController
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
-    $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
+    $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel())
+      ->setRepository($repository)
+      ->getPanelURI();
 
     if (!$repository->canAllowDangerousChanges()) {
       if ($repository->isSVN()) {
@@ -24,7 +26,7 @@ final class DiffusionRepositoryEditDangerousController
               '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($edit_uri);
+          ->addCancelButton($panel_uri);
       } else {
         return $this->newDialog()
           ->setTitle(pht('Unprotectable Repository'))
@@ -33,7 +35,7 @@ final class DiffusionRepositoryEditDangerousController
               'This repository can not be protected from dangerous changes '.
               'because Phabricator does not control what users are allowed '.
               'to push to it.'))
-          ->addCancelButton($edit_uri);
+          ->addCancelButton($panel_uri);
       }
     }
 
@@ -48,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..48207fd7d3 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php
@@ -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/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
index 303959d098..2aa2f6d474 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
@@ -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/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(<<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'));
   }
 

From 34e85aaeb8b69cbd6c45dc5f3759a6706c3e11f8 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 08:10:21 -0700
Subject: [PATCH 31/62] Document most of the new Diffusion management panel

Summary: Ref T10923. This isn't complete yet, but reduces lies and increases truths.

Test Plan: Read documentation, clicked new "Documentation" nav item.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15868
---
 src/__phutil_library_map__.php                |   2 +
 .../DiffusionRepositoryManageController.php   |   2 +-
 ...RepositoryDocumentationManagementPanel.php |  29 ++
 .../DiffusionRepositoryManagementPanel.php    |   3 +
 ...DiffusionRepositoryURIsManagementPanel.php |   2 +-
 src/docs/user/userguide/diffusion.diviner     |  94 ++----
 .../user/userguide/diffusion_managing.diviner | 310 ++++++++++++++++++
 .../user/userguide/diffusion_updates.diviner  |   4 +-
 .../user/userguide/diffusion_uris.diviner     |   3 -
 9 files changed, 382 insertions(+), 67 deletions(-)
 create mode 100644 src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php
 create mode 100644 src/docs/user/userguide/diffusion_managing.diviner

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 6b71b47c43..0b939aa459 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -754,6 +754,7 @@ phutil_register_library_map(array(
     'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
     'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php',
     'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php',
+    'DiffusionRepositoryDocumentationManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php',
     'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php',
     'DiffusionRepositoryEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositoryEditConduitAPIMethod.php',
     'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
@@ -4973,6 +4974,7 @@ phutil_register_library_map(array(
     'DiffusionRepositoryController' => 'DiffusionController',
     'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource',
     'DiffusionRepositoryDefaultController' => 'DiffusionController',
+    'DiffusionRepositoryDocumentationManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'DiffusionRepositoryEditController' => 'DiffusionController',
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php
index 4f4624b70d..dcafbe8685 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php
@@ -109,7 +109,7 @@ final class DiffusionRepositoryManageController
       $key = $panel->getManagementPanelKey();
       $label = $panel->getManagementPanelLabel();
       $icon = $panel->getManagementPanelIcon();
-      $href = $repository->getPathURI("manage/{$key}/");
+      $href = $panel->getPanelNavigationURI();
 
       $item = id(new PHUIListItemView())
         ->setKey($key)
diff --git a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php
new file mode 100644
index 0000000000..add9cfe054
--- /dev/null
+++ b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php
@@ -0,0 +1,29 @@
+getPanelURI();
+  }
 
 }
diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
index 45047dd4fe..5422d15779 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
@@ -6,7 +6,7 @@ final class DiffusionRepositoryURIsManagementPanel
   const PANELKEY = 'uris';
 
   public function getManagementPanelLabel() {
-    return pht('Clone / Fetch / Mirror');
+    return pht('URIs');
   }
 
   public function getManagementPanelIcon() {
diff --git a/src/docs/user/userguide/diffusion.diviner b/src/docs/user/userguide/diffusion.diviner
index f3fa53da3b..19fd0a70c2 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,31 @@ 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 Guilde: 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).
 
-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:
+Repository Clustering
+=====================
 
-https://lkml.org/lkml/2010/10/28/287
+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.
 
-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).
+To get started with clustering, see @{article:Clustering Introduction}. For
+details on repository clustering, see @{article:Cluster: Repositories}.
 
-= Running Diffusion Daemons =
 
-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_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner
new file mode 100644
index 0000000000..c3492c507a
--- /dev/null
+++ b/src/docs/user/userguide/diffusion_managing.diviner
@@ -0,0 +1,310 @@
+@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}.
+
+
+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 fc045f9704..218b8182ef 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:
 

From f191f66f3480b8367ca12cf96ce84692ea479955 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 13:52:23 -0700
Subject: [PATCH 32/62] Document API management of repositories and fix some
 issues with creating URIs via API

Summary:
Ref T10923. Primarily documents the process for creating repositories via the API.

Also fixes a couple of issues with `repositoryPHID` not being set yet when creating URIs via the API.

Test Plan:
  - Followed all documented steps to create a new repository.
  - Created and edited some new URIs from the web workflow, too.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15870
---
 .../diffusion/editor/DiffusionURIEditor.php   |  12 +-
 src/docs/user/userguide/diffusion.diviner     |   4 +
 src/docs/user/userguide/diffusion_api.diviner | 182 ++++++++++++++++++
 3 files changed, 197 insertions(+), 1 deletion(-)
 create mode 100644 src/docs/user/userguide/diffusion_api.diviner

diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php
index 9059430d0b..9115b00083 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';
   }
@@ -115,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());
@@ -151,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);
@@ -208,6 +215,9 @@ final class DiffusionURIEditor
               $xaction);
             continue;
           }
+
+          $this->repository = $repository;
+          $this->repositoryPHID = $repository_phid;
         }
         break;
       case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
@@ -315,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();
diff --git a/src/docs/user/userguide/diffusion.diviner b/src/docs/user/userguide/diffusion.diviner
index 19fd0a70c2..86bee6314d 100644
--- a/src/docs/user/userguide/diffusion.diviner
+++ b/src/docs/user/userguide/diffusion.diviner
@@ -53,6 +53,10 @@ 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 Guilde: Managing Repositories}.
 
+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}.
+
 
 Repository Clustering
 =====================
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}.

From 3328e78a7b4c9c2fd543862219cacd4dcdc4ff68 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 14:24:21 -0700
Subject: [PATCH 33/62] Sort out EditController / ManageController /
 EditproController Diffusion hierarchy

Summary: Ref T10923. This cleans up the remaining "pro" mess left by the cutover.

Test Plan: Viewed, managed, edited a repository.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15871
---
 src/__phutil_library_map__.php                |  16 +-
 .../PhabricatorDiffusionApplication.php       |   4 +-
 ...fusionRepositoryEditActivateController.php |   2 +-
 .../DiffusionRepositoryEditController.php     |  73 +++++++--
 ...usionRepositoryEditDangerousController.php |   2 +-
 ...iffusionRepositoryEditDeleteController.php |   2 +-
 ...iffusionRepositoryEditUpdateController.php |   2 +-
 .../DiffusionRepositoryEditproController.php  |  69 ---------
 .../DiffusionRepositoryManageController.php   | 146 ++----------------
 ...fusionRepositoryManagePanelsController.php | 133 ++++++++++++++++
 ...sionRepositoryTestAutomationController.php |   2 +-
 11 files changed, 221 insertions(+), 230 deletions(-)
 delete mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditproController.php
 create mode 100644 src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 0b939aa459..937dec6c4a 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -762,10 +762,10 @@ phutil_register_library_map(array(
     'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php',
     'DiffusionRepositoryEditEngine' => 'applications/diffusion/editor/DiffusionRepositoryEditEngine.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',
     'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
     'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php',
@@ -4975,17 +4975,17 @@ phutil_register_library_map(array(
     'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource',
     'DiffusionRepositoryDefaultController' => 'DiffusionController',
     'DiffusionRepositoryDocumentationManagementPanel' => 'DiffusionRepositoryManagementPanel',
-    'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
+    'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryManageController',
     'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
-    'DiffusionRepositoryEditController' => 'DiffusionController',
-    'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryEditController',
-    'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController',
+    'DiffusionRepositoryEditController' => 'DiffusionRepositoryManageController',
+    'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryManageController',
+    'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryManageController',
     'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine',
-    'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController',
-    'DiffusionRepositoryEditproController' => 'DiffusionRepositoryEditController',
+    'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryManageController',
     'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryListController' => 'DiffusionController',
     'DiffusionRepositoryManageController' => 'DiffusionController',
+    'DiffusionRepositoryManagePanelsController' => 'DiffusionRepositoryManageController',
     'DiffusionRepositoryManagementPanel' => 'Phobject',
     'DiffusionRepositoryPath' => 'Phobject',
     'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel',
@@ -4997,7 +4997,7 @@ phutil_register_library_map(array(
     'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryTag' => 'Phobject',
-    'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
+    'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryManageController',
     'DiffusionRepositoryURICredentialController' => 'DiffusionController',
     'DiffusionRepositoryURIDisableController' => 'DiffusionController',
     'DiffusionRepositoryURIEditController' => 'DiffusionController',
diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php
index 176ee81872..7eff7d7bc8 100644
--- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php
+++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php
@@ -58,7 +58,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
         $this->getQueryRoutePattern()
           => 'DiffusionRepositoryListController',
         $this->getEditRoutePattern('edit/') =>
-          'DiffusionRepositoryEditproController',
+          'DiffusionRepositoryEditController',
         'pushlog/' => array(
           '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController',
           'view/(?P\d+)/' => 'DiffusionPushEventViewController',
@@ -87,7 +87,7 @@ 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*)/'
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
index 5105ff6ee8..3ebed70348 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
@@ -1,7 +1,7 @@
 loadDiffusionContextForEdit();
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 f24a23bb8e..d0dd2cc2e9 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
@@ -1,7 +1,7 @@
 loadDiffusionContextForEdit();
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php
index 48207fd7d3..0380b99f0f 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php
@@ -1,7 +1,7 @@
 loadDiffusionContextForEdit();
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
index 2aa2f6d474..4cb6ffda67 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
@@ -1,7 +1,7 @@
 loadDiffusionContextForEdit();
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/DiffusionRepositoryManageController.php b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php
index dcafbe8685..8b0740c540 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php
@@ -1,139 +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) {
-      $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/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
new file mode 100644
index 0000000000..8a9f258ebe
--- /dev/null
+++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
@@ -0,0 +1,133 @@
+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 $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($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) {
+      $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/DiffusionRepositoryTestAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php
index f7fdc37311..bc1c470caa 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php
@@ -1,7 +1,7 @@
 loadDiffusionContextForEdit();

From 98b202042e936e09a9154ac88e5e6f53151c69d2 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 15:36:13 -0700
Subject: [PATCH 34/62] Provide some more context hints for repository URIs

Summary: Ref T10923. This provides a little guidance about hosted vs observed, and points at the `diffusion.ssh-*` options.

Test Plan: Poked around in the web UI, saw useful guidance.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15872
---
 .../editor/DiffusionURIEditEngine.php         | 19 +++++++++++++++++-
 ...RepositoryDocumentationManagementPanel.php |  2 +-
 ...DiffusionRepositoryURIsManagementPanel.php | 20 +++++++++++++++++++
 .../editfield/PhabricatorEditField.php        | 15 ++++++++++++++
 4 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/src/applications/diffusion/editor/DiffusionURIEditEngine.php b/src/applications/diffusion/editor/DiffusionURIEditEngine.php
index 61e24e0997..639dd74400 100644
--- a/src/applications/diffusion/editor/DiffusionURIEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionURIEditEngine.php
@@ -83,9 +83,25 @@ 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();
@@ -118,7 +134,8 @@ final class DiffusionURIEditEngine
         ->setConduitTypeDescription(pht('New repository URI.'))
         ->setIsRequired(!$is_builtin)
         ->setIsLocked($is_builtin)
-        ->setValue($uri_value),
+        ->setValue($uri_value)
+        ->setControlInstructions($uri_instructions),
       id(new PhabricatorSelectEditField())
         ->setKey('io')
         ->setLabel(pht('I/O Type'))
diff --git a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php
index add9cfe054..ca2c577b38 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php
@@ -14,7 +14,7 @@ final class DiffusionRepositoryDocumentationManagementPanel
   }
 
   public function getManagementPanelIcon() {
-    return 'fa-book bluegrey';
+    return 'fa-book';
   }
 
   public function buildManagementPanelContent() {
diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
index 5422d15779..5a4e4de79f 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
@@ -113,8 +113,28 @@ final class DiffusionRepositoryURIsManagementPanel
           ->setTag('a')
           ->setText(pht('Documentation')));
 
+    $messages = array();
+    if ($repository->isHosted()) {
+      $messages[] = array(
+        id(new PHUIIconView())->setIcon('fa-folder'),
+        ' ',
+        pht('Phabricator is hosting this repository.'),
+      );
+    } else {
+      $messages[] = array(
+        id(new PHUIIconView())->setIcon('fa-download'),
+        ' ',
+        pht('This repository is hosted remotely. Phabricator is observing it.'),
+      );
+    }
+
+    $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/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;

From e2bbde9675be15c0ca4d010e0d8a97d9dd8d2048 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 16:12:37 -0700
Subject: [PATCH 35/62] Bring old repository instructions and guidance forward
 to new UI

Summary:
Ref T10923. Fixes T10406. This brings most of the guidance/instructions forward:

  - Some remained as instructions.
  - Some moved to documentation.

Test Plan: Went through all of the sections and hit the help.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10406, T10923

Differential Revision: https://secure.phabricator.com/D15873
---
 ...fusionRepositoryEditActivateController.php |  5 ++-
 .../editor/DiffusionRepositoryEditEngine.php  | 22 +++++++++-
 .../editor/DiffusionURIEditEngine.php         | 37 ++++++++++++++++
 ...ffusionRepositoryStatusManagementPanel.php |  9 ++++
 .../user/userguide/diffusion_managing.diviner | 43 +++++++++++++++++++
 5 files changed, 113 insertions(+), 3 deletions(-)

diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
index 3ebed70348..52ab0d01ca 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
@@ -40,7 +40,10 @@ final class DiffusionRepositoryEditActivateController
 
     if ($repository->isTracked()) {
       $title = pht('Deactivate Repository');
-      $body = pht('Deactivate this 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 {
       $title = pht('Activate Repository');
diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
index 3d510f4648..699bd83aa3 100644
--- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
@@ -208,6 +208,22 @@ 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.");
+
     return array(
       id(new PhabricatorSelectEditField())
         ->setKey('vcs')
@@ -329,7 +345,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'))
@@ -340,7 +357,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 639dd74400..fdc91cff5f 100644
--- a/src/applications/diffusion/editor/DiffusionURIEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionURIEditEngine.php
@@ -105,6 +105,43 @@ final class DiffusionURIEditEngine
     } 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(
diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php
index a601bd1d64..573162b927 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php
@@ -500,5 +500,14 @@ final class DiffusionRepositoryStatusManagementPanel
     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/docs/user/userguide/diffusion_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner
index c3492c507a..efdb783c04 100644
--- a/src/docs/user/userguide/diffusion_managing.diviner
+++ b/src/docs/user/userguide/diffusion_managing.diviner
@@ -236,6 +236,49 @@ fetch from, serve from, and push to.
 These options are covered in detail in @{article:Diffusion User Guide: URIs}.
 
 
+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
 ================================
 

From 0b5ab2330d998918b3f960e4e508a2648ee33539 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Mon, 9 May 2016 16:40:09 -0700
Subject: [PATCH 36/62] Hide irrelevant panels in Mercurial/Subversion, fix
 Subversion URIs

Summary:
Ref T10923.

  - Hide "Automation", "Staging" and "Branches" in repositories where they do nothing.
  - Fix SVN SSH URIs to read "svn+ssh://" and have proper paths.

Test Plan:
  - Verified irrelevant sections did not appear in Subversion in Manage UI.
  - Checked out a new hosted SVN repository.

Reviewers: chad, avivey

Reviewed By: avivey

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15874
---
 .../DiffusionRepositoryManagePanelsController.php        | 7 ++++++-
 .../DiffusionRepositoryAutomationManagementPanel.php     | 5 +++++
 .../DiffusionRepositoryBranchesManagementPanel.php       | 5 +++++
 .../management/DiffusionRepositoryManagementPanel.php    | 5 +++++
 .../DiffusionRepositoryStagingManagementPanel.php        | 6 ++++++
 .../repository/storage/PhabricatorRepositoryURI.php      | 9 ++++++++-
 6 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
index 8a9f258ebe..a5541acabc 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
@@ -27,11 +27,16 @@ final class DiffusionRepositoryManagePanelsController
 
     $panels = DiffusionRepositoryManagementPanel::getAllPanels();
 
-    foreach ($panels as $panel) {
+    foreach ($panels as $key => $panel) {
       $panel
         ->setViewer($viewer)
         ->setRepository($repository)
         ->setController($this);
+
+      if (!$panel->shouldEnableForRepository($repository)) {
+        unset($panels[$key]);
+        continue;
+      }
     }
 
     $selected = $request->getURIData('panel');
diff --git a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php
index 764f58c5bd..14182a0444 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php
@@ -13,6 +13,11 @@ final class DiffusionRepositoryAutomationManagementPanel
     return 800;
   }
 
+  public function shouldEnableForRepository(
+    PhabricatorRepository $repository) {
+    return $repository->isGit();
+  }
+
   protected function getEditEngineFieldKeys() {
     return array(
       'automationBlueprintPHIDs',
diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php
index 66ac0877dd..b3e7926cfc 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php
@@ -13,6 +13,11 @@ final class DiffusionRepositoryBranchesManagementPanel
     return 1000;
   }
 
+  public function shouldEnableForRepository(
+    PhabricatorRepository $repository) {
+    return ($repository->isGit() || $repository->isHg());
+  }
+
   public function getManagementPanelIcon() {
     $repository = $this->getRepository();
 
diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php
index 8d23e87406..34047cdb89 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php
@@ -46,6 +46,11 @@ abstract class DiffusionRepositoryManagementPanel
     return array();
   }
 
+  public function shouldEnableForRepository(
+    PhabricatorRepository $repository) {
+    return true;
+  }
+
   final protected function newActions() {
     $actions = $this->buildManagementPanelActions();
     if (!$actions) {
diff --git a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php
index 0d8f9379be..79b716e82a 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php
@@ -13,6 +13,12 @@ final class DiffusionRepositoryStagingManagementPanel
     return 700;
   }
 
+  public function shouldEnableForRepository(
+    PhabricatorRepository $repository) {
+    return $repository->isGit();
+  }
+
+
   public function getManagementPanelIcon() {
     $repository = $this->getRepository();
 
diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php
index 227b13012f..21fc791de5 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryURI.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php
@@ -310,9 +310,15 @@ final class PhabricatorRepositoryURI
 
 
   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:
@@ -382,6 +388,7 @@ final class PhabricatorRepositoryURI
       $suffix = '/';
     } else {
       $suffix = '';
+      $clone_name = '';
     }
 
     switch ($this->getBuiltinIdentifier()) {

From f05fce44aabbb30518b001c56e26aefc73f224bf Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 05:54:16 -0700
Subject: [PATCH 37/62] Provide more UI guidance when creating repositories

Summary: Ref T10923. Walk users through the "create, configure, activate" workflow a little better and set expectations more clearly.

Test Plan:
  - Created a new repository, saw new UI help.
  - Activated repository, saw onboarding help disappear.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15875
---
 ...fusionRepositoryEditActivateController.php | 19 ++++++++++-
 .../editor/DiffusionRepositoryEditEngine.php  |  2 ++
 ...ffusionRepositoryBasicsManagementPanel.php | 33 ++++++++++++++++++-
 ...DiffusionRepositoryURIsManagementPanel.php | 20 +++++++++--
 .../editor/PhabricatorRepositoryEditor.php    | 10 +++++-
 .../storage/PhabricatorRepository.php         |  4 +++
 6 files changed, 83 insertions(+), 5 deletions(-)

diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
index 52ab0d01ca..b383333fbc 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php
@@ -47,7 +47,24 @@ final class DiffusionRepositoryEditActivateController
       $submit = pht('Deactivate Repository');
     } else {
       $title = pht('Activate Repository');
-      $body = pht('Activate this 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');
     }
 
diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
index 699bd83aa3..7d3d5640ff 100644
--- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
@@ -40,6 +40,8 @@ final class DiffusionRepositoryEditEngine
     $viewer = $this->getViewer();
     $repository = PhabricatorRepository::initializeNewRepository($viewer);
 
+    $repository->setDetail('newly-initialized', true);
+
     $vcs = $this->getVersionControlSystem();
     if ($vcs) {
       $repository->setVersionControlSystem($vcs);
diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
index 96bda40246..b783a63fd8 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
@@ -103,7 +103,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/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
index 5a4e4de79f..333c2f03c8 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
@@ -113,18 +113,34 @@ 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'),
         ' ',
-        pht('Phabricator is hosting this repository.'),
+        $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'),
         ' ',
-        pht('This repository is hosted remotely. Phabricator is observing it.'),
+        $observe_message,
       );
     }
 
diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
index fb97d7d908..43c95f6b4a 100644
--- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -138,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());
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index f5e883d20e..36379eda6c 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -964,6 +964,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'),

From 576b73dc5375e80c3b687712686d10337386e06b Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 06:20:48 -0700
Subject: [PATCH 38/62] Index all repository URIs, not just the "primary"
 repository URI

Summary:
Ref T10923. When regenerating the URI index for a repository, index every URI.

  - Also, make the index slightly stricter (domain + path instead of just path). Excluding the domain made more sense when we were generating only first-party URIs.
  - Make the index smarter about `/diffusion/123/` URIs.
  - Show normalized URIs in `diffusion.repository.search` results.

Test Plan:
  - Ran migration.
  - Verified sensible-looking results in database.
  - Searched for a repository URI by first-party clone URI.
  - Searched for a repository URI by mirror URI.
  - Used `diffusion.repository.search` to get information about repository URIs.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15876
---
 .../20160112.repo.02.uri.index.php            |  7 ++--
 .../autopatches/20160510.repo.01.uriindex.php | 10 ++++++
 .../PhabricatorRepositoryURINormalizer.php    | 35 ++++++++++++++++++-
 .../query/PhabricatorRepositoryQuery.php      | 20 +++++------
 .../PhabricatorRepositorySearchEngine.php     |  9 +++++
 .../storage/PhabricatorRepository.php         | 35 +++++--------------
 .../storage/PhabricatorRepositoryURI.php      | 21 +++++++++++
 7 files changed, 93 insertions(+), 44 deletions(-)
 create mode 100644 resources/sql/autopatches/20160510.repo.01.uriindex.php

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/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/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/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 18519f56be..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;
   }
 
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 36379eda6c..229059efcb 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -803,41 +803,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();
diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php
index 21fc791de5..53b9040453 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryURI.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php
@@ -196,6 +196,26 @@ final class PhabricatorRepositoryURI
     return $this->getURIObject(false);
   }
 
+  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);
   }
@@ -693,6 +713,7 @@ final class PhabricatorRepositoryURI
         'raw' => $this->getURI(),
         'display' => (string)$this->getDisplayURI(),
         'effective' => (string)$this->getEffectiveURI(),
+        'normalized' => (string)$this->getNormalizedURI(),
       ),
       'io' => array(
         'raw' => $this->getIOType(),

From 71a97d8af56b0926e91b35e45bd1b5d11e1e0376 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 06:40:30 -0700
Subject: [PATCH 39/62] When observing a repository, switch to "importing" mode
 on a large discovery in an empty repository

Summary:
Ref T10923. Fixes T9554.

When hosting a repository, we currently have a heuristic that tries to detect when you're doing an initial import: if you push more than 7 commits to an empty repository, it counts as an import and we disable mail/feed/etc.

Do something similar for observed repositories: if the repository is empty and we discover more than 7 commits, switch to import mode until we catch up.

This should align behavior with user expectation more often when juggling hosted vs imported repositories.

Test Plan:
  - Created a new hosted repository.
  - Activated it and allowed it to fully import.
  - Added an "Observe URI".
  - Saw it automatically drop into "Importing" mode until the import completed.
  - Swapped it back to hosted mode.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9554, T10923

Differential Revision: https://secure.phabricator.com/D15877
---
 .../engine/DiffusionCommitHookEngine.php      | 11 ++----
 .../PhabricatorRepositoryDiscoveryEngine.php  | 36 +++++++++++++++++++
 .../PhabricatorWorkingCopyTestCase.php        |  1 -
 .../storage/PhabricatorRepository.php         | 19 +++++++++-
 4 files changed, 56 insertions(+), 11 deletions(-)

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/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php
index 3c3a0526be..82bd7706aa 100644
--- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php
@@ -52,6 +52,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 +589,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/__tests__/PhabricatorWorkingCopyTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
index 5b37a9e655..c489d69424 100644
--- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
+++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
@@ -71,7 +71,6 @@ abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase {
     $this->didConstructRepository($repo);
 
     $repo->save();
-    $repo->makeEphemeral();
 
     // Keep the disk resources around until we exit.
     $this->dirs[] = $dir;
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 229059efcb..5705bb719c 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';
@@ -354,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();
@@ -2155,6 +2160,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  )-------------------------------------------------------------*/
 

From 3fdb1a2bc437e65f266321cbb94c2a3f4920afcf Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 07:13:44 -0700
Subject: [PATCH 40/62] Improve behavior for not-yet-created non-cluster
 repositories

Summary: Fixes T10815. We already recovered reasonably from this for cluster repositories, but not for non-cluster repositories.

Test Plan:
  - Viewed cluster and non-cluster empty Git repository.
  - Viewed cluster and non-cluster empty Mercurial repository.
  - Viewed cluster and non-clsuter empty hosted SVN repository.
  - Viewed cluster and non-cluster empty observed SVN repository.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10815

Differential Revision: https://secure.phabricator.com/D15878
---
 .../query/lowlevel/DiffusionLowLevelResolveRefsQuery.php | 5 +++++
 .../repository/storage/PhabricatorRepository.php         | 9 +++++++++
 2 files changed, 14 insertions(+)

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/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 5705bb719c..f23661cfbe 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1477,6 +1477,15 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
     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.
    */

From 97c103fa00cc19e9cda1a08de691987790c785fa Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 17:13:11 -0700
Subject: [PATCH 41/62] Restore edit UI for "Import Only" in Subversion

Summary: Ref T10923. Although I'd ideally like to get rid of this eventually, keep it around for now.

Test Plan:
  - Edited value for an SVN repository.
  - Observed no panel present for a Git repository.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15883
---
 src/__phutil_library_map__.php                |  2 +
 .../editor/DiffusionRepositoryEditEngine.php  | 16 ++++
 ...ionRepositorySubversionManagementPanel.php | 77 +++++++++++++++++++
 3 files changed, 95 insertions(+)
 create mode 100644 src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 937dec6c4a..8b6406f3b8 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -775,6 +775,7 @@ phutil_register_library_map(array(
     'DiffusionRepositoryStagingManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php',
     'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php',
     'DiffusionRepositoryStorageManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.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',
@@ -4995,6 +4996,7 @@ phutil_register_library_map(array(
     'DiffusionRepositoryStagingManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel',
+    'DiffusionRepositorySubversionManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
     'DiffusionRepositoryTag' => 'Phobject',
     'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryManageController',
diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
index 7d3d5640ff..933cdbb597 100644
--- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
@@ -226,6 +226,11 @@ final class DiffusionRepositoryEditEngine
       "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')
@@ -338,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'))
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);
+  }
+
+}

From cd86bf01746b63359032d77ca8d558c25184a3a8 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 19:06:01 -0700
Subject: [PATCH 42/62] Remove `metamta.differential.unified-comment-context`
 and explain it in ExtraConfigSetupCheck

Summary: Ref T10694. This setting no longer has any effect: we always show a limited amount of context now.

Test Plan: `grep`

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10694

Differential Revision: https://secure.phabricator.com/D15886
---
 .../check/PhabricatorExtraConfigSetupCheck.php    |  4 ++++
 .../PhabricatorDifferentialConfigOptions.php      | 15 ---------------
 2 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
index 5e5b493591..602d756d05 100644
--- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
+++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
@@ -310,6 +310,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
       'notification.enabled' => $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.')),
     );
   }
 

From de4312bcdeab6cb623295cc1e83f379a63fd75c6 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Tue, 10 May 2016 05:25:43 -0700
Subject: [PATCH 43/62] Before executing svnserve, change the CWD to a readable
 directory

Summary: Fixes T10941. This avoids a confusing dead end when configuring Subversion hosting, where `svnserve` will fail to execute hooks if the CWD isn't readable by the vcs-user.

Test Plan:
  - Updated and committed in a hosted SVN repository.
  - Ran some git operations, too.
  - @dpotter confirmed this locally in T10941.

Reviewers: chad

Reviewed By: chad

Subscribers: dpotter

Maniphest Tasks: T10941

Differential Revision: https://secure.phabricator.com/D15879
---
 .../protocol/DiffusionGitCommandEngine.php      |  3 +--
 .../ssh/DiffusionSubversionServeSSHWorkflow.php | 10 ++++++++++
 src/infrastructure/env/PhabricatorEnv.php       | 17 +++++++++++++++++
 3 files changed, 28 insertions(+), 2 deletions(-)

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/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/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/';
+  }
+
+
 }

From 5587d97a7f0cf382ef58076b46c4ce3673c2716e Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Wed, 11 May 2016 06:55:51 -0700
Subject: [PATCH 44/62] Tailor Diffusion protocol rules slightly

Summary: Fixes T10948. Ref T10923. Make these rules a little more thorough and document their behavior.

Test Plan: Looked at Diffusion clone URIs.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923, T10948

Differential Revision: https://secure.phabricator.com/D15887
---
 .../storage/PhabricatorRepository.php         |  4 +--
 .../user/userguide/diffusion_uris.diviner     | 26 +++++++++++++++++++
 2 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index f23661cfbe..bc786e46a1 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -2091,10 +2091,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
     // HTTP is not supported for Subversion.
     if ($this->isSVN()) {
       $has_http = false;
+      $has_https = false;
     }
 
-    // TODO: Maybe allow users to disable this by default somehow?
-    $has_ssh = true;
+    $has_ssh = (bool)strlen(PhabricatorEnv::getEnvConfig('phd.user'));
 
     $protocol_map = array(
       PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH => $has_ssh,
diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner
index 218b8182ef..7377250f21 100644
--- a/src/docs/user/userguide/diffusion_uris.diviner
+++ b/src/docs/user/userguide/diffusion_uris.diviner
@@ -164,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
 ================================
 

From 6615d76c3489a3977549d2133374b60b5ce98e42 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Wed, 11 May 2016 07:22:07 -0700
Subject: [PATCH 45/62] In Subversion, show "svn checkout  " in
 Diffusion

Summary:
Ref T10923. The old behavior was to show a full command in SVN, Mercurial, and Git, like this:

  - `git clone `
  - `hg clone `
  - `svn checkout  `

In Git and Mercurial, the `` ends in something like `/nice-repository-name.git` so the default directory it creates is called `nice-repository-name/`.

In Subversion, we don't (and can't easily) do that for various reasons so we provide an explicit `` with the nice name.

In the update, I've changed things to just show the URI. I often found that I wanted the URI alone, not the whole clone command (for example, to `fetch`, `remote-add`, etc). This is also consistent with GitHub. Because we have nice URIs for Git and Mercurial, `git clone ` has good behavior.

In Subversion, `svn checkout ` has bad beahvior (you get a directory named `47/` or whatever). So continue showing the whole command there.

We can possibly tailor this after T4245 finishes up and we get access to `/source/nice-repository-name/` URIs.

Test Plan:
  - Viewed a Subversion repository, saw a full command.
  - Viewed a Git repository, saw only a clone URI.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15888
---
 .../controller/DiffusionRepositoryController.php      | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php
index 9aad7f1c5b..45fbf5c717 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -671,7 +671,16 @@ final class DiffusionRepositoryController extends DiffusionController {
 
     require_celerity_resource('diffusion-icons-css');
 
-    $display = (string)csprintf('%R', (string)$uri->getDisplayURI());
+    if ($repository->isSVN()) {
+      $display = csprintf(
+        'svn checkout %R %R',
+        (string)$uri->getDisplayURI(),
+        $repository->getCloneName());
+    } else {
+      $display = csprintf('%R', (string)$uri->getDisplayURI());
+    }
+
+    $display = (string)$display;
 
     $input = javelin_tag(
       'input',

From ee74fb4cc76402a98f0b086c848594586fc276b2 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Wed, 11 May 2016 08:17:42 -0700
Subject: [PATCH 46/62] Add a "View Repository" button to the repository manage
 UI

Summary:
Ref T10923. We sort of dead-end new users creating repositories right now, by dumping them into the manage UI without an obvious way forward.

You can click the crumb to get to the repository, but by default it will say something like `R1` which isn't very obvious.

Add a more obvious navigational link to get to the main view.

Test Plan: {F1308196}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15889
---
 .../DiffusionRepositoryManagePanelsController.php         | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
index a5541acabc..22c03145e0 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php
@@ -62,6 +62,7 @@ final class DiffusionRepositoryManagePanelsController
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($panel->getManagementPanelLabel());
+    $crumbs->setBorder(true);
 
     $header_text = pht(
       '%s: %s',
@@ -77,6 +78,13 @@ final class DiffusionRepositoryManagePanelsController
       $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)

From dc6d108b26cff221ee37631e4efa3585b6df84b9 Mon Sep 17 00:00:00 2001
From: Aviv Eyal 
Date: Wed, 11 May 2016 18:21:14 +0000
Subject: [PATCH 47/62] Paramater type inheritence fix

Summary: These parameters wrongly extend List.

Test Plan:
Used createdStart field for a search - didn't get error about "should be a list".
`git grep 'extends ConduitListParameterType'`.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: Korvin

Differential Revision: https://secure.phabricator.com/D15881
---
 src/__phutil_library_map__.php                                | 4 ++--
 .../conduit/parametertype/ConduitEpochParameterType.php       | 2 +-
 .../conduit/parametertype/ConduitWildParameterType.php        | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 8b6406f3b8..09fb23f221 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -4437,7 +4437,7 @@ phutil_register_library_map(array(
     'ConduitCallTestCase' => 'PhabricatorTestCase',
     'ConduitColumnsParameterType' => 'ConduitParameterType',
     'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod',
-    'ConduitEpochParameterType' => 'ConduitListParameterType',
+    'ConduitEpochParameterType' => 'ConduitParameterType',
     'ConduitException' => 'Exception',
     'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod',
     'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
@@ -4461,7 +4461,7 @@ phutil_register_library_map(array(
     'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
     'ConduitUserListParameterType' => 'ConduitListParameterType',
     'ConduitUserParameterType' => 'ConduitParameterType',
-    'ConduitWildParameterType' => 'ConduitListParameterType',
+    'ConduitWildParameterType' => 'ConduitParameterType',
     'ConpherenceColumnViewController' => 'ConpherenceController',
     'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod',
     'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions',
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 @@
 
Date: Wed, 11 May 2016 09:43:42 -0700
Subject: [PATCH 48/62] Flesh out a few more repository documentation sections

Summary: Ref T10923. This links up some more related documentation about repository management.

Test Plan: Read documentation.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15890
---
 .../user/userguide/diffusion_managing.diviner | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/src/docs/user/userguide/diffusion_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner
index efdb783c04..f6ab0997c4 100644
--- a/src/docs/user/userguide/diffusion_managing.diviner
+++ b/src/docs/user/userguide/diffusion_managing.diviner
@@ -236,6 +236,38 @@ 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
 ========
 

From b21b43131c143741942d8b8b4a1827401fee411e Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Wed, 11 May 2016 10:17:33 -0700
Subject: [PATCH 49/62] Clean up display of clone URIs a little bit

Summary:
Ref T10923. This makes the "Clone URI" UI a little nicer:

  - Show whether each URI is read-only, read-write, or external.
  - Clicking the button selects the URI.
  - Add a link to manage the appropriate credentials.

Test Plan: {F1308302, size=full}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15891
---
 resources/celerity/map.php                    |  13 +-
 src/__phutil_library_map__.php                |   2 +
 .../DiffusionRepositoryController.php         |  18 +--
 .../diffusion/view/DiffusionCloneURIView.php  | 131 ++++++++++++++++++
 .../application/diffusion/diffusion-icons.css |  19 +++
 .../rsrc/js/core/behavior-select-content.js   |  23 +++
 6 files changed, 191 insertions(+), 15 deletions(-)
 create mode 100644 src/applications/diffusion/view/DiffusionCloneURIView.php
 create mode 100644 webroot/rsrc/js/core/behavior-select-content.js

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 90d5720d2c..3e622a32b9 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -12,7 +12,7 @@ return array(
     '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',
@@ -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',
@@ -504,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',
@@ -557,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',
@@ -685,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',
@@ -1837,6 +1839,11 @@ return array(
       'javelin-util',
       'javelin-request',
     ),
+    'bf5374ef' => array(
+      'javelin-behavior',
+      'javelin-stratcom',
+      'javelin-dom',
+    ),
     'bff6884b' => array(
       'javelin-install',
       'javelin-dom',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 09fb23f221..74302c34e4 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -573,6 +573,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',
@@ -4792,6 +4793,7 @@ phutil_register_library_map(array(
     'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionChangeController' => 'DiffusionController',
     'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup',
+    'DiffusionCloneURIView' => 'AphrontView',
     'DiffusionCommandEngine' => 'Phobject',
     'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase',
     'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField',
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php
index 45fbf5c717..d38ad77787 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -669,8 +669,6 @@ final class DiffusionRepositoryController extends DiffusionController {
     PhabricatorRepository $repository,
     PhabricatorRepositoryURI $uri) {
 
-    require_celerity_resource('diffusion-icons-css');
-
     if ($repository->isSVN()) {
       $display = csprintf(
         'svn checkout %R %R',
@@ -681,17 +679,13 @@ final class DiffusionRepositoryController extends DiffusionController {
     }
 
     $display = (string)$display;
+    $viewer = $this->getViewer();
 
-    $input = javelin_tag(
-      'input',
-      array(
-        'type' => 'text',
-        'value' => $display,
-        'class' => 'diffusion-clone-uri',
-        'readonly' => 'true',
-      ));
-
-    return $input;
+    return id(new DiffusionCloneURIView())
+      ->setViewer($viewer)
+      ->setRepository($repository)
+      ->setRepositoryURI($uri)
+      ->setDisplayURI($display);
   }
 
 }
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/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/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();
+    });
+});

From 54409e7716499edcd653ea112b93e6515fe0b91b Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Wed, 11 May 2016 15:30:57 -0700
Subject: [PATCH 50/62] Fix an issue with TextAreaEditField affecting Paste

Summary: Fixes T10952. Fixes T10930. I didn't implement this method correctly when I expanded this field for repositories.

Test Plan: Edited a paste without warnings.

Reviewers: avivey, chad

Reviewed By: chad

Maniphest Tasks: T10930, T10952

Differential Revision: https://secure.phabricator.com/D15892
---
 .../transactions/editfield/PhabricatorTextAreaEditField.php | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

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() {

From 15f14d6c2f207a471d8095cb2a365eaa51ced9a5 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Wed, 11 May 2016 17:01:52 -0700
Subject: [PATCH 51/62] Fix improper viewer for Git SSH cluster workflows

Summary: Ref T10751. These workflows have separate `getUser()` and `getViewer()` for weird legacy reasons. `getUser()` is correct.

Test Plan:
  - Did a Git SSH push, verified that "Last Writer" reflected the proper user in the "Storage" UI in repository management.
  - Grepped for other callsites, double-checked that they used correct users.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10751

Differential Revision: https://secure.phabricator.com/D15893
---
 .../diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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.

From 5003f21919348e2add3ff11bf5fffcabaf64f65b Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 06:24:15 -0700
Subject: [PATCH 52/62] Put "Projects" edit field back on Basics management
 panel for repositories

Summary: Ref T10923. Fixes T10955. This was accidentally excluded when I broke the form into pages.

Test Plan: Saw edit field in panel; changed project tags for a repository.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10923, T10955

Differential Revision: https://secure.phabricator.com/D15896
---
 .../management/DiffusionRepositoryBasicsManagementPanel.php      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
index b783a63fd8..6a2f70d693 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
@@ -29,6 +29,7 @@ final class DiffusionRepositoryBasicsManagementPanel
       'callsign',
       'shortName',
       'description',
+      'projectPHIDs',
     );
   }
 

From 0faf3dd18d73fc2001bb2a27ff6885133536031f Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 08:19:19 -0700
Subject: [PATCH 53/62] Fix "Contributing" misspelling in documentation

Summary: Fixes T10958.

Test Plan: Dictionary'd.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10958

Differential Revision: https://secure.phabricator.com/D15898
---
 src/docs/contributor/describing_problems.diviner | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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}.

From 5162f8109525a78514a9e5f3f5270cbdaf510d1a Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 06:54:43 -0700
Subject: [PATCH 54/62] Provide SSH host documentation, tweak/supplement
 cluster documentation

Summary: Ref T10751. I think this mostly brings us up to date with the state of the world.

Test Plan: Read documentation.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10751

Differential Revision: https://secure.phabricator.com/D15897
---
 src/docs/user/cluster/cluster.diviner         | 53 ++++++++++++++++---
 src/docs/user/cluster/cluster_daemons.diviner |  6 +--
 src/docs/user/cluster/cluster_devices.diviner |  2 +-
 .../cluster/cluster_notifications.diviner     |  3 --
 .../user/cluster/cluster_repositories.diviner |  5 --
 src/docs/user/cluster/cluster_ssh.diviner     | 47 ++++++++++++++++
 .../user/cluster/cluster_webservers.diviner   | 15 +++---
 7 files changed, 104 insertions(+), 27 deletions(-)
 create mode 100644 src/docs/user/cluster/cluster_ssh.diviner

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

From 9d196648f54f5b758fd34673982735505b287155 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 08:27:04 -0700
Subject: [PATCH 55/62] Prevent users from disabling repository builtin URIs

Summary:
Ref T10923. Currently, users can disable or enable builtin URIs, but this doesn't actually do anything.

The behavior of "disable" has changed a bit over time and might need some further refinement, but it's currently meaningless for builtin URIs. Prevent adjustment of it. If users want to hide a URI, they should set "Display: Hidden" instead.

Test Plan:
  - Disabled/enabled a non-builtin URI.
  - Tried to disable a builtin URI, saw greyed out UI and got a helpful error message.

Reviewers: chad

Reviewed By: chad

Subscribers: eadler

Maniphest Tasks: T10923

Differential Revision: https://secure.phabricator.com/D15899
---
 ...iffusionRepositoryURIDisableController.php | 10 ++++++++++
 .../DiffusionRepositoryURIViewController.php  |  4 +++-
 .../diffusion/editor/DiffusionURIEditor.php   | 20 +++++++++++++++++++
 3 files changed, 33 insertions(+), 1 deletion(-)

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 61a8cba0a1..61335d20d0 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php
@@ -147,6 +147,8 @@ final class DiffusionRepositoryURIViewController
       $disable_icon = 'fa-ban';
     }
 
+    $can_disable = ($can_edit && !$uri->isBuiltin());
+
     $disable_uri = $repository->getPathURI("uri/disable/{$id}/");
 
     $curtain->addAction(
@@ -155,7 +157,7 @@ final class DiffusionRepositoryURIViewController
         ->setName($disable_name)
         ->setHref($disable_uri)
         ->setWorkflow(true)
-        ->setDisabled(!$can_edit));
+        ->setDisabled(!$can_disable));
 
     return $curtain;
   }
diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php
index 9115b00083..020275a367 100644
--- a/src/applications/diffusion/editor/DiffusionURIEditor.php
+++ b/src/applications/diffusion/editor/DiffusionURIEditor.php
@@ -415,6 +415,26 @@ 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;

From bd9bcaa8ffea6a84119bcdb57f753bc8fb756456 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 09:59:07 -0700
Subject: [PATCH 56/62] Improve HTML mail rendering of inline patches

Summary: Fixes T9790. This uses a simple renderer, like the inline context renderer, that emphasizes getting a quick glance at small changes and working reasonably on mobile devices.

Test Plan:
  - Set `inline` setting to `9999`.
  - Created a diff.
  - Saw it render reasonably in HTML mail.
  - Also tested text mail to make sure I didn't break that.

{F1310137, size=full}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9790

Differential Revision: https://secure.phabricator.com/D15901
---
 src/__phutil_library_map__.php                |  6 +-
 .../editor/DifferentialTransactionEditor.php  | 54 +++++++++----
 .../mail/DifferentialChangeDetailMailView.php | 77 +++++++++++++++++++
 .../DifferentialInlineCommentMailView.php     | 60 ++-------------
 .../mail/DifferentialMailView.php             | 62 +++++++++++++++
 .../parser/DifferentialChangesetParser.php    | 26 +++++--
 .../parser/DifferentialHunkParser.php         |  3 +-
 ...DifferentialChangesetOneUpMailRenderer.php | 19 +++++
 8 files changed, 228 insertions(+), 79 deletions(-)
 create mode 100644 src/applications/differential/mail/DifferentialChangeDetailMailView.php
 create mode 100644 src/applications/differential/mail/DifferentialMailView.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 74302c34e4..8f4756e9e4 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -362,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',
@@ -471,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',
@@ -4551,6 +4553,7 @@ phutil_register_library_map(array(
     'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField',
     'DifferentialBlockHeraldAction' => 'HeraldAction',
     'DifferentialBranchField' => 'DifferentialCustomField',
+    'DifferentialChangeDetailMailView' => 'DifferentialMailView',
     'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
     'DifferentialChangeType' => 'Phobject',
     'DifferentialChangesSinceLastUpdateField' => 'DifferentialCustomField',
@@ -4665,7 +4668,7 @@ phutil_register_library_map(array(
       'PhabricatorInlineCommentInterface',
     ),
     'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController',
-    'DifferentialInlineCommentMailView' => 'Phobject',
+    'DifferentialInlineCommentMailView' => 'DifferentialMailView',
     'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
     'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
     'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
@@ -4676,6 +4679,7 @@ phutil_register_library_map(array(
     'DifferentialLintField' => 'DifferentialHarbormasterField',
     'DifferentialLintStatus' => 'Phobject',
     'DifferentialLocalCommitsView' => 'AphrontView',
+    'DifferentialMailView' => 'Phobject',
     'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
     'DifferentialModernHunk' => 'DifferentialHunk',
     'DifferentialNextStepField' => 'DifferentialCustomField',
diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php
index 0db490769f..3855edeac9 100644
--- a/src/applications/differential/editor/DifferentialTransactionEditor.php
+++ b/src/applications/differential/editor/DifferentialTransactionEditor.php
@@ -1251,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));
         }
       }
     }
@@ -1387,7 +1384,38 @@ final class DifferentialTransactionEditor
     $section_text = "\n".$section->getPlaintext();
 
     $style = array(
-      'margin: 12px 0;',
+      '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 appendChangeDetailsForMail(
+    PhabricatorLiskDAO $object,
+    DifferentialDiff $diff,
+    $patch,
+    PhabricatorMetaMTAMailBody $body) {
+
+    $section = id(new DifferentialChangeDetailMailView())
+      ->setViewer($this->getActor())
+      ->setDiff($diff)
+      ->setPatch($patch)
+      ->buildMailSection();
+
+    $header = pht('CHANGE DETAILS');
+
+    $section_text = "\n".$section->getPlaintext();
+
+    $style = array(
+      'margin: 6px 0 12px 0;',
     );
 
     $section_html = phutil_tag(
@@ -1659,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
index 554bb66a63..57958202b1 100644
--- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php
+++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php
@@ -1,7 +1,7 @@
 addPlaintextFragment($spacer_text);
         $section->addPlaintextFragment($render_text);
 
-        $style = array(
-          'border: 1px solid #C7CCD9;',
-          'border-radius: 3px;',
-        );
-
-        $html_fragment = phutil_tag(
-          'div',
-          array(
-            'style' => implode(' ', $style),
-          ),
+        $html_fragment = $this->renderContentBox(
           array(
             $context_html,
             $render_html,
@@ -374,21 +365,7 @@ final class DifferentialInlineCommentMailView
     $is_html) {
 
     if ($is_html) {
-      $style = array(
-        'font: 11px/15px "Menlo", "Consolas", "Monaco", monospace;',
-        'white-space: pre-wrap;',
-        'clear: both;',
-        'padding: 4px 0;',
-        'margin: 0;',
-      );
-
-      $style = implode(' ', $style);
-      $patch = phutil_tag(
-        'div',
-        array(
-          'style' => $style,
-        ),
-        $patch);
+      $patch = $this->renderCodeBlock($patch);
     }
 
     $header = $this->renderHeader($comment, $is_html, false);
@@ -430,12 +407,7 @@ final class DifferentialInlineCommentMailView
 
     $header = "{$path}:{$range}";
     if ($is_html) {
-      $header = phutil_tag(
-        'span',
-        array(
-          'style' => 'color: #4b4d51; font-weight: bold;',
-        ),
-        $header);
+      $header = $this->renderHeaderBold($header);
     }
 
     if ($with_author) {
@@ -448,12 +420,7 @@ final class DifferentialInlineCommentMailView
       $byline = $author->getName();
 
       if ($is_html) {
-        $byline = phutil_tag(
-          'span',
-          array(
-            'style' => 'color: #4b4d51; font-weight: bold;',
-          ),
-          $byline);
+        $byline = $this->renderHeaderBold($byline);
       }
 
       $header = pht('%s wrote in %s', $byline, $header);
@@ -478,22 +445,7 @@ final class DifferentialInlineCommentMailView
         $link = null;
       }
 
-      $style = array(
-        'color: #74777d;',
-        'background: #eff2f4;',
-        'padding: 6px 8px;',
-        'overflow: hidden;',
-      );
-
-      $header = phutil_tag(
-        'div',
-        array(
-          'style' => implode(' ', $style),
-        ),
-        array(
-          $link,
-          $header,
-        ));
+      $header = $this->renderHeaderBlock(array($link, $header));
     }
 
     return $header;
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 f7815538ac..9fc0adf537 100644
--- a/src/applications/differential/parser/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/DifferentialChangesetParser.php
@@ -55,6 +55,7 @@ final class DifferentialChangesetParser extends Phobject {
   private $rangeStart;
   private $rangeEnd;
   private $mask;
+  private $linesOfContext = 8;
 
   private $highlightEngine;
 
@@ -195,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';
@@ -227,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
@@ -724,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());
@@ -959,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
@@ -1001,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;
@@ -1189,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;
@@ -1199,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;
             }
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
index 042047b4a8..e720422ecd 100644
--- a/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetOneUpMailRenderer.php
@@ -51,6 +51,16 @@ final class DifferentialChangesetOneUpMailRenderer
   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) {
@@ -80,6 +90,15 @@ final class DifferentialChangesetOneUpMailRenderer
             '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;
       }

From 984dff0ae335dcb6987f2d72ca28f79b4cdab818 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 14:22:11 -0700
Subject: [PATCH 57/62] Provide a more consistent, mostly relaxed severity for
 updating non-cluster repositories on cluster devices

Summary:
Fixes T10940. Two issues currently:

First, `PullLocal` deamon refuses to update non-cluster repositories on cluster devices. However, this is surprising/confusing/bad because as soon as you enroll a repository host in the cluster, most of the repositories on it stop working until you `clusterize` them. This is especially confusing because the documentation gives you a very nice, gradual walkthrough about going through things slowly and being able to check your work at every step, but we really drop you off a bit of a cliff here. The workflow implied by the documentation is a desirable one.

This operation is generally only unsafe/problematic if the daemon would be creating a //new// working copy. If a working copy already exists, we can reasonably guess that it's almost certainly because you've enrolled a previously un-clustered host into a new cluster. This allows the nice, gradual workflow the documentation describes to proceed as expected, without any weird surprises.

Instead of refusing to update these repositories, only refuse to update them if updating would create a new working copy. This should make transitioning much smoother without any meaningful reduction in safety.

Second, the lower-level `bin/repository update`, `refs`, `mirror`, etc., commands don't apply this same check. However, these commands are potentially just as dangerous. Use the same code to do a similar check there, making sure we only operate on repositories that are either expected to be on the current device, or which already exist here.

Test Plan:
  - Ran `bin/phd debug pull`, saw diagnostic information choose to update most repositories (including some non-cluster repositories) but properly skip non-cluster repositories that do not exist locally.
  - Ran `bin/repository update`, etc., saw the command apply consistent rules to the rules applied by `PullLocal` and refuse to update non-local repositories it would need to create.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10940

Differential Revision: https://secure.phabricator.com/D15902
---
 src/__phutil_library_map__.php                |   2 +
 .../data/DiffusionLocalRepositoryFilter.php   | 184 ++++++++++++++++++
 .../PhabricatorRepositoryPullLocalDaemon.php  | 116 +----------
 ...orRepositoryManagementDiscoverWorkflow.php |   2 +-
 ...atorRepositoryManagementMirrorWorkflow.php |   2 +-
 ...icatorRepositoryManagementPullWorkflow.php |   2 +-
 ...icatorRepositoryManagementRefsWorkflow.php |   2 +-
 ...atorRepositoryManagementUpdateWorkflow.php |   2 +-
 ...habricatorRepositoryManagementWorkflow.php |  26 +++
 9 files changed, 225 insertions(+), 113 deletions(-)
 create mode 100644 src/applications/diffusion/data/DiffusionLocalRepositoryFilter.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 8f4756e9e4..1bca354414 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -665,6 +665,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',
@@ -4890,6 +4891,7 @@ phutil_register_library_map(array(
     'DiffusionLintController' => 'DiffusionController',
     'DiffusionLintCountQuery' => 'PhabricatorQuery',
     'DiffusionLintSaveRunner' => 'Phobject',
+    'DiffusionLocalRepositoryFilter' => 'Phobject',
     'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelCommitQuery' => 'DiffusionLowLevelQuery',
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/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/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/PhabricatorRepositoryManagementMirrorWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php
index 5db4899b41..caa7574424 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php
@@ -23,7 +23,7 @@ final class PhabricatorRepositoryManagementMirrorWorkflow
   }
 
   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/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..11ad6a9418 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.'));
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php
index 56a75764c8..654a974e6c 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php
@@ -33,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) {

From 8cdafb003250018d55746a65697e6f6030bc45f2 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 16:40:21 -0700
Subject: [PATCH 58/62] Allow users to set a line-height in their monospaced
 font preference

Summary: Ref T10959. This does not fix the problem because the `.differential-diff td` rule is still stronger, but it does let you choose a more compact or breezy style for remarkup blocks and pastes.

Test Plan:
  - Set font to `24px / 48px impact`.
  - Viewed a paste, saw lovely readable text.
  - Viewed an inline code block which was very easy on the eyes.

{F1310420}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10959

Differential Revision: https://secure.phabricator.com/D15904
---
 .../settings/storage/PhabricatorUserPreferences.php             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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);
   }
 
 }

From 1c73ad6a1bb04715d27d795d7c44b068590cb864 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Thu, 12 May 2016 16:09:20 -0700
Subject: [PATCH 59/62] Make repository daemon locks more granular and
 forgiving

Summary:
Ref T4292. Currently, we hold one big lock around the whole `bin/repository update` workflow.

When running multiple daemons on different hosts, this lock can end up being contentious. In particular, we'll hold it during `git fetch` on every host globally, even though it's only useful to hold it locally per-device (that is, it's fine/good/expected if `repo001` and `repo002` happen to be fetching from a repository they are observing at the same time).

Instead, split it into two locks:

  - One lock is scoped to the current device, and held during pull (usually `git fetch`). This just keeps multiple daemons accidentally running on the same host from making a mess when trying to initialize or update a working copy.
  - One lock is scoped globally, and held during discovery. This makes sure daemons on different hosts don't step on each other when updating the database.

If we fail to acquire either lock, assume some other process is legitimately doing the work and bail more quietly instead of fataling. In approximately 100% of cases where users have hit this lock contention, that was the case: some other daemon was running somewhere doing the work and the error didn't actually represent an issue.

If there's an actual problem, we still raise a diagnostically useful message if you run `bin/repository update` manually, so there are still tools to figure out that something is hung or whatever.

Test Plan:
  - Ran `bin/repository update`, `pull`, `discover`.
  - Added `sleep(5)`, forced processes to contend, got lock exceptions and graceful exit with diagnostic message.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4292

Differential Revision: https://secure.phabricator.com/D15903
---
 src/__phutil_library_map__.php                |  2 +
 src/applications/almanac/util/AlmanacKeys.php |  8 ++
 .../DiffusionDaemonLockException.php          |  3 +
 .../PhabricatorRepositoryDiscoveryEngine.php  | 27 +++++++
 .../engine/PhabricatorRepositoryEngine.php    | 21 +++++
 .../PhabricatorRepositoryPullEngine.php       | 27 +++++++
 ...atorRepositoryManagementUpdateWorkflow.php | 81 +++++++------------
 7 files changed, 119 insertions(+), 50 deletions(-)
 create mode 100644 src/applications/diffusion/exception/DiffusionDaemonLockException.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 1bca354414..e11c20a33c 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -622,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',
@@ -4845,6 +4846,7 @@ phutil_register_library_map(array(
     'DiffusionController' => 'PhabricatorController',
     'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability',
+    'DiffusionDaemonLockException' => 'Exception',
     'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability',
     'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability',
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/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();
 
+    $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:
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/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/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php
index 11ad6a9418..f435861c60 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php
@@ -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;
   }

From e5f2ccc57fb841fbc2c78e91e7ea23e07b24fba5 Mon Sep 17 00:00:00 2001
From: epriestley 
Date: Fri, 13 May 2016 05:24:39 -0700
Subject: [PATCH 60/62] Don't trigger audits for archived packages

Summary:
Ref T10939. This is just a bug. I thought this was what was described in T10174 but that's actually talking about something completely different.

Also make a `