mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-24 15:52:41 +01:00
(stable) Promote 2016 Week 21
This commit is contained in:
commit
77d543ba44
121 changed files with 3658 additions and 951 deletions
|
@ -7,10 +7,10 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'b7b8d101',
|
||||
'core.pkg.css' => '204cabae',
|
||||
'core.pkg.js' => '6972d365',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '7ba78475',
|
||||
'differential.pkg.css' => '33da0633',
|
||||
'differential.pkg.js' => 'd0cd0df6',
|
||||
'diffusion.pkg.css' => '91c5d3a6',
|
||||
'diffusion.pkg.js' => '3a9a8bfa',
|
||||
|
@ -57,7 +57,7 @@ return array(
|
|||
'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
|
||||
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
|
||||
'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
|
||||
'rsrc/css/application/differential/changeset-view.css' => '3e3b0b76',
|
||||
'rsrc/css/application/differential/changeset-view.css' => '7bcbe615',
|
||||
'rsrc/css/application/differential/core.css' => '5b7b8ff4',
|
||||
'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
|
||||
'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
|
||||
|
@ -82,7 +82,7 @@ return array(
|
|||
'rsrc/css/application/paste/paste.css' => '1898e534',
|
||||
'rsrc/css/application/people/people-profile.css' => '2473d929',
|
||||
'rsrc/css/application/phame/phame.css' => '737792d6',
|
||||
'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee',
|
||||
'rsrc/css/application/pholio/pholio-edit.css' => 'b15fec4a',
|
||||
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
|
||||
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
|
||||
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02',
|
||||
|
@ -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' => '5101175d',
|
||||
'rsrc/css/core/syntax.css' => '9fc496d5',
|
||||
'rsrc/css/core/z-index.css' => '5b6fcf3f',
|
||||
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
||||
'rsrc/css/font/font-aleo.css' => '8bdb2835',
|
||||
|
@ -159,7 +159,7 @@ return array(
|
|||
'rsrc/css/phui/phui-two-column-view.css' => 'b9538af1',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373',
|
||||
'rsrc/css/sprite-login.css' => '60e8560e',
|
||||
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
||||
|
@ -550,7 +550,7 @@ return array(
|
|||
'conpherence-update-css' => 'faf6be09',
|
||||
'conpherence-widget-pane-css' => '775eaaba',
|
||||
'd3' => 'a11a5ff2',
|
||||
'differential-changeset-view-css' => '3e3b0b76',
|
||||
'differential-changeset-view-css' => '7bcbe615',
|
||||
'differential-core-view-css' => '5b7b8ff4',
|
||||
'differential-inline-comment-editor' => '64a5550f',
|
||||
'differential-revision-add-comment-css' => 'c47f8c40',
|
||||
|
@ -804,7 +804,7 @@ return array(
|
|||
'phabricator-zindex-css' => '5b6fcf3f',
|
||||
'phame-css' => '737792d6',
|
||||
'pholio-css' => 'ca89d380',
|
||||
'pholio-edit-css' => '3ad9d1ee',
|
||||
'pholio-edit-css' => 'b15fec4a',
|
||||
'pholio-inline-comments-css' => '8e545e49',
|
||||
'phortune-credit-card-form' => '2290aeef',
|
||||
'phortune-credit-card-form-css' => '8391eb02',
|
||||
|
@ -858,7 +858,7 @@ return array(
|
|||
'phui-two-column-view-css' => 'b9538af1',
|
||||
'phui-workboard-color-css' => 'ac6fe6a7',
|
||||
'phui-workboard-view-css' => 'e6d89647',
|
||||
'phui-workcard-view-css' => '3646fb96',
|
||||
'phui-workcard-view-css' => '0c62d7c5',
|
||||
'phui-workpanel-view-css' => '92197373',
|
||||
'phuix-action-list-view' => 'b5c256b8',
|
||||
'phuix-action-view' => '8cf6d262',
|
||||
|
@ -881,7 +881,7 @@ return array(
|
|||
'sprite-menu-css' => '9dd65b92',
|
||||
'sprite-tokens-css' => '4f399012',
|
||||
'syntax-default-css' => '9923583c',
|
||||
'syntax-highlighting-css' => '5101175d',
|
||||
'syntax-highlighting-css' => '9fc496d5',
|
||||
'tokens-css' => '3d0f239e',
|
||||
'typeahead-browse-css' => 'd8581d2c',
|
||||
'unhandled-exception-css' => '4c96257a',
|
||||
|
@ -1148,9 +1148,6 @@ return array(
|
|||
'javelin-util',
|
||||
'javelin-uri',
|
||||
),
|
||||
'3e3b0b76' => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'3f5d6dbf' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1243,9 +1240,6 @@ return array(
|
|||
'javelin-typeahead-source',
|
||||
'javelin-util',
|
||||
),
|
||||
'5101175d' => array(
|
||||
'syntax-default-css',
|
||||
),
|
||||
'519705ea' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1495,6 +1489,9 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
),
|
||||
'7bcbe615' => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'7cbe244b' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1660,6 +1657,9 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'9fc496d5' => array(
|
||||
'syntax-default-css',
|
||||
),
|
||||
'a0b57eb8' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_owners.owners_package
|
||||
ADD autoReview VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_owners.owners_package
|
||||
SET autoReview = 'none' WHERE autoReview = '';
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_owners.owners_package
|
||||
ADD dominion VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_owners.owners_package
|
||||
SET dominion = 'strong' WHERE dominion = '';
|
16
resources/sql/autopatches/20160517.oauth.01.edge.sql
Normal file
16
resources/sql/autopatches/20160517.oauth.01.edge.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE {$NAMESPACE}_oauth_server.edge (
|
||||
src VARBINARY(64) NOT NULL,
|
||||
type INT UNSIGNED NOT NULL,
|
||||
dst VARBINARY(64) NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
seq INT UNSIGNED NOT NULL,
|
||||
dataID INT UNSIGNED,
|
||||
PRIMARY KEY (src, type, dst),
|
||||
KEY `src` (src, type, dateCreated, seq),
|
||||
UNIQUE KEY `key_dst` (dst, type, src)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_oauth_server.edgedata (
|
||||
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20160518.ssh.01.activecol.sql
Normal file
2
resources/sql/autopatches/20160518.ssh.01.activecol.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_auth.auth_sshkey
|
||||
ADD isActive BOOL;
|
2
resources/sql/autopatches/20160518.ssh.02.activeval.sql
Normal file
2
resources/sql/autopatches/20160518.ssh.02.activeval.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_auth.auth_sshkey
|
||||
SET isActive = 1;
|
2
resources/sql/autopatches/20160518.ssh.03.activekey.sql
Normal file
2
resources/sql/autopatches/20160518.ssh.03.activekey.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_auth.auth_sshkey
|
||||
ADD UNIQUE KEY `key_activeunique` (keyIndex, isActive);
|
19
resources/sql/autopatches/20160519.ssh.01.xaction.sql
Normal file
19
resources/sql/autopatches/20160519.ssh.01.xaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE {$NAMESPACE}_auth.auth_sshkeytransaction (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (`phid`),
|
||||
KEY `key_object` (`objectPHID`)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -14,6 +14,7 @@ try {
|
|||
$key = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withKeys(array($public_key))
|
||||
->withIsActive(true)
|
||||
->executeOne();
|
||||
if (!$key) {
|
||||
exit(1);
|
||||
|
|
|
@ -6,6 +6,7 @@ require_once $root.'/scripts/__init_script__.php';
|
|||
|
||||
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withIsActive(true)
|
||||
->execute();
|
||||
|
||||
if (!$keys) {
|
||||
|
|
|
@ -361,6 +361,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php',
|
||||
'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php',
|
||||
'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php',
|
||||
'DifferentialBlockingReviewerDatasource' => 'applications/differential/typeahead/DifferentialBlockingReviewerDatasource.php',
|
||||
'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php',
|
||||
'DifferentialChangeDetailMailView' => 'applications/differential/mail/DifferentialChangeDetailMailView.php',
|
||||
'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php',
|
||||
|
@ -435,6 +436,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php',
|
||||
'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php',
|
||||
'DifferentialEditPolicyField' => 'applications/differential/customfield/DifferentialEditPolicyField.php',
|
||||
'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php',
|
||||
'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php',
|
||||
'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php',
|
||||
'DifferentialFindConduitAPIMethod' => 'applications/differential/conduit/DifferentialFindConduitAPIMethod.php',
|
||||
|
@ -491,9 +493,13 @@ phutil_register_library_map(array(
|
|||
'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
|
||||
'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
|
||||
'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php',
|
||||
'DifferentialResponsibleDatasource' => 'applications/differential/typeahead/DifferentialResponsibleDatasource.php',
|
||||
'DifferentialResponsibleUserDatasource' => 'applications/differential/typeahead/DifferentialResponsibleUserDatasource.php',
|
||||
'DifferentialResponsibleViewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php',
|
||||
'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php',
|
||||
'DifferentialReviewedByField' => 'applications/differential/customfield/DifferentialReviewedByField.php',
|
||||
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
||||
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
|
||||
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
||||
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
|
||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
|
||||
|
@ -533,6 +539,8 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
|
||||
'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
|
||||
'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.php',
|
||||
'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php',
|
||||
'DifferentialRevisionResultBucket' => 'applications/differential/query/DifferentialRevisionResultBucket.php',
|
||||
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
|
||||
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
|
||||
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
|
||||
|
@ -1869,12 +1877,19 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
|
||||
'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php',
|
||||
'PhabricatorAuthSSHKeyController' => 'applications/auth/controller/PhabricatorAuthSSHKeyController.php',
|
||||
'PhabricatorAuthSSHKeyDeleteController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php',
|
||||
'PhabricatorAuthSSHKeyDeactivateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeactivateController.php',
|
||||
'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php',
|
||||
'PhabricatorAuthSSHKeyEditor' => 'applications/auth/editor/PhabricatorAuthSSHKeyEditor.php',
|
||||
'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php',
|
||||
'PhabricatorAuthSSHKeyListController' => 'applications/auth/controller/PhabricatorAuthSSHKeyListController.php',
|
||||
'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php',
|
||||
'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php',
|
||||
'PhabricatorAuthSSHKeyReplyHandler' => 'applications/auth/mail/PhabricatorAuthSSHKeyReplyHandler.php',
|
||||
'PhabricatorAuthSSHKeySearchEngine' => 'applications/auth/query/PhabricatorAuthSSHKeySearchEngine.php',
|
||||
'PhabricatorAuthSSHKeyTableView' => 'applications/auth/view/PhabricatorAuthSSHKeyTableView.php',
|
||||
'PhabricatorAuthSSHKeyTransaction' => 'applications/auth/storage/PhabricatorAuthSSHKeyTransaction.php',
|
||||
'PhabricatorAuthSSHKeyTransactionQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyTransactionQuery.php',
|
||||
'PhabricatorAuthSSHKeyViewController' => 'applications/auth/controller/PhabricatorAuthSSHKeyViewController.php',
|
||||
'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php',
|
||||
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
|
||||
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
|
||||
|
@ -2161,7 +2176,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php',
|
||||
'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php',
|
||||
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
|
||||
'PhabricatorCredentialsUsedByObjectEdgeType' => 'applications/passphrase/edge/PhabricatorCredentialsUsedByObjectEdgeType.php',
|
||||
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
|
||||
'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
|
||||
'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
|
||||
|
@ -2779,6 +2793,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php',
|
||||
'PhabricatorOAuthServerEditEngine' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php',
|
||||
'PhabricatorOAuthServerEditor' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditor.php',
|
||||
'PhabricatorOAuthServerSchemaSpec' => 'applications/oauthserver/query/PhabricatorOAuthServerSchemaSpec.php',
|
||||
'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php',
|
||||
'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php',
|
||||
'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php',
|
||||
|
@ -2802,7 +2817,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php',
|
||||
'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php',
|
||||
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
|
||||
'PhabricatorObjectUsesCredentialsEdgeType' => 'applications/transactions/edges/PhabricatorObjectUsesCredentialsEdgeType.php',
|
||||
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
|
||||
'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php',
|
||||
'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php',
|
||||
|
@ -2833,6 +2847,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
|
||||
'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php',
|
||||
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
|
||||
'PhabricatorOwnersPackageRemarkupRule' => 'applications/owners/remarkup/PhabricatorOwnersPackageRemarkupRule.php',
|
||||
'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
|
||||
'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php',
|
||||
'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php',
|
||||
|
@ -3316,6 +3331,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
|
||||
'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
|
||||
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
|
||||
'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php',
|
||||
'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php',
|
||||
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
|
||||
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
|
||||
'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
|
||||
|
@ -4554,6 +4571,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialAuthorField' => 'DifferentialCustomField',
|
||||
'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField',
|
||||
'DifferentialBlockHeraldAction' => 'HeraldAction',
|
||||
'DifferentialBlockingReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialBranchField' => 'DifferentialCustomField',
|
||||
'DifferentialChangeDetailMailView' => 'DifferentialMailView',
|
||||
'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
|
||||
|
@ -4638,6 +4656,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
|
||||
'DifferentialDraft' => 'DifferentialDAO',
|
||||
'DifferentialEditPolicyField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialFieldParseException' => 'Exception',
|
||||
'DifferentialFieldValidationException' => 'Exception',
|
||||
'DifferentialFindConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||
|
@ -4700,9 +4719,13 @@ phutil_register_library_map(array(
|
|||
'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialRepositoryLookup' => 'Phobject',
|
||||
'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialResponsibleDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialResponsibleUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialResponsibleViewerFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
|
||||
'DifferentialReviewedByField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialReviewer' => 'Phobject',
|
||||
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
||||
'DifferentialReviewerStatus' => 'Phobject',
|
||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
|
||||
|
@ -4757,6 +4780,8 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
|
||||
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
|
||||
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'DifferentialRevisionStatus' => 'Phobject',
|
||||
|
@ -6286,14 +6311,22 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
),
|
||||
'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthSSHKeyDeleteController' => 'PhabricatorAuthSSHKeyController',
|
||||
'PhabricatorAuthSSHKeyDeactivateController' => 'PhabricatorAuthSSHKeyController',
|
||||
'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController',
|
||||
'PhabricatorAuthSSHKeyEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController',
|
||||
'PhabricatorAuthSSHKeyListController' => 'PhabricatorAuthSSHKeyController',
|
||||
'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorAuthSSHKeyReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
|
||||
'PhabricatorAuthSSHKeySearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorAuthSSHKeyTableView' => 'AphrontView',
|
||||
'PhabricatorAuthSSHKeyTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorAuthSSHKeyTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorAuthSSHKeyViewController' => 'PhabricatorAuthSSHKeyController',
|
||||
'PhabricatorAuthSSHPublicKey' => 'Phobject',
|
||||
'PhabricatorAuthSession' => array(
|
||||
'PhabricatorAuthDAO',
|
||||
|
@ -6643,7 +6676,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorCountdownView' => 'AphrontView',
|
||||
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
|
||||
'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
|
||||
'PhabricatorCustomField' => 'Phobject',
|
||||
'PhabricatorCustomFieldAttachment' => 'Phobject',
|
||||
|
@ -7342,6 +7374,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorOAuthServerEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorOAuthServerEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorOAuthServerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorOAuthServerScope' => 'Phobject',
|
||||
'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
|
||||
|
@ -7368,7 +7401,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorObjectSelectorDialog' => 'Phobject',
|
||||
'PhabricatorObjectUsesCredentialsEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
|
||||
'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock',
|
||||
|
@ -7411,6 +7443,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorOwnersPackageRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction',
|
||||
|
@ -8007,6 +8040,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
|
||||
'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorSearchRelationship' => 'Phobject',
|
||||
'PhabricatorSearchResultBucket' => 'Phobject',
|
||||
'PhabricatorSearchResultBucketGroup' => 'Phobject',
|
||||
'PhabricatorSearchResultView' => 'AphrontView',
|
||||
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
|
||||
|
|
|
@ -146,6 +146,7 @@ final class AlmanacDeviceViewController
|
|||
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($device_phid))
|
||||
->withIsActive(true)
|
||||
->execute();
|
||||
|
||||
$table = id(new PhabricatorAuthSSHKeyTableView())
|
||||
|
@ -156,38 +157,13 @@ final class AlmanacDeviceViewController
|
|||
->setShowTrusted(true)
|
||||
->setNoDataString(pht('This device has no associated SSH public keys.'));
|
||||
|
||||
try {
|
||||
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
|
||||
$can_generate = true;
|
||||
} catch (Exception $ex) {
|
||||
$can_generate = false;
|
||||
}
|
||||
|
||||
$generate_uri = '/auth/sshkey/generate/?objectPHID='.$device_phid;
|
||||
$upload_uri = '/auth/sshkey/upload/?objectPHID='.$device_phid;
|
||||
$menu_button = PhabricatorAuthSSHKeyTableView::newKeyActionsMenu(
|
||||
$viewer,
|
||||
$device);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('SSH Public Keys'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($generate_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit || !$can_generate)
|
||||
->setText(pht('Generate Keypair'))
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->setIcon('fa-lock')))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($upload_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit)
|
||||
->setText(pht('Upload Public Key'))
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->setIcon('fa-upload')));
|
||||
->addActionLink($menu_button);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
|
|
|
@ -141,6 +141,7 @@ final class AlmanacManagementRegisterWorkflow
|
|||
$public_key = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withKeys(array($key_object))
|
||||
->withIsActive(true)
|
||||
->executeOne();
|
||||
|
||||
if (!$public_key) {
|
||||
|
|
|
@ -35,6 +35,11 @@ final class AlmanacManagementTrustKeyWorkflow
|
|||
pht('No public key exists with ID "%s".', $id));
|
||||
}
|
||||
|
||||
if (!$key->getIsActive()) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Public key "%s" is not an active key.', $id));
|
||||
}
|
||||
|
||||
if ($key->getIsTrusted()) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Public key with ID %s is already trusted.', $id));
|
||||
|
|
|
@ -227,6 +227,14 @@ final class AlmanacDevice
|
|||
return $this->getName();
|
||||
}
|
||||
|
||||
public function getSSHKeyNotifyPHIDs() {
|
||||
// Devices don't currently have anyone useful to notify about SSH key
|
||||
// edits, and they're usually a difficult vector to attack since you need
|
||||
// access to a cluster host. However, it would be nice to make them
|
||||
// subscribable at some point.
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
|
|
@ -159,7 +159,17 @@ final class PhabricatorAuditEditor
|
|||
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||
foreach ($add as $phid) {
|
||||
if (isset($requests[$phid])) {
|
||||
continue;
|
||||
$request = $requests[$phid];
|
||||
|
||||
// Only update an existing request if the current status is not
|
||||
// an interesting status.
|
||||
if ($request->isInteresting()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$request = id(new PhabricatorRepositoryAuditRequest())
|
||||
->setCommitPHID($object->getPHID())
|
||||
->setAuditorPHID($phid);
|
||||
}
|
||||
|
||||
if ($this->getIsHeraldEditor()) {
|
||||
|
@ -170,12 +180,13 @@ final class PhabricatorAuditEditor
|
|||
$audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED;
|
||||
$audit_reason = $this->getAuditReasons($phid);
|
||||
}
|
||||
$requests[] = id(new PhabricatorRepositoryAuditRequest())
|
||||
->setCommitPHID($object->getPHID())
|
||||
->setAuditorPHID($phid)
|
||||
|
||||
$request
|
||||
->setAuditStatus($audit_requested)
|
||||
->setAuditReasons($audit_reason)
|
||||
->save();
|
||||
|
||||
$requests[$phid] = $request;
|
||||
}
|
||||
|
||||
$object->attachAudits($requests);
|
||||
|
|
|
@ -75,10 +75,14 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
|
|||
'multifactor/'
|
||||
=> 'PhabricatorAuthNeedsMultiFactorController',
|
||||
'sshkey/' => array(
|
||||
$this->getQueryRoutePattern('for/(?P<forPHID>[^/]+)/')
|
||||
=> 'PhabricatorAuthSSHKeyListController',
|
||||
'generate/' => 'PhabricatorAuthSSHKeyGenerateController',
|
||||
'upload/' => 'PhabricatorAuthSSHKeyEditController',
|
||||
'edit/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyEditController',
|
||||
'delete/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyDeleteController',
|
||||
'deactivate/(?P<id>\d+)/'
|
||||
=> 'PhabricatorAuthSSHKeyDeactivateController',
|
||||
'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController',
|
||||
),
|
||||
),
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ final class PhabricatorAuthQueryPublicKeysConduitAPIMethod
|
|||
$viewer = $request->getUser();
|
||||
|
||||
$query = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer($viewer);
|
||||
->setViewer($viewer)
|
||||
->withIsActive(true);
|
||||
|
||||
$ids = $request->getValue('ids');
|
||||
if ($ids !== null) {
|
||||
|
|
|
@ -3,18 +3,34 @@
|
|||
abstract class PhabricatorAuthSSHKeyController
|
||||
extends PhabricatorAuthController {
|
||||
|
||||
protected function newKeyForObjectPHID($object_phid) {
|
||||
private $keyObject;
|
||||
|
||||
public function setSSHKeyObject(PhabricatorSSHPublicKeyInterface $object) {
|
||||
$this->keyObject = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSSHKeyObject() {
|
||||
return $this->keyObject;
|
||||
}
|
||||
|
||||
protected function loadSSHKeyObject($object_phid, $need_edit) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
$query = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->requireCapabilities(
|
||||
->withPHIDs(array($object_phid));
|
||||
|
||||
if ($need_edit) {
|
||||
$query->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
));
|
||||
}
|
||||
|
||||
$object = $query->executeOne();
|
||||
|
||||
if (!$object) {
|
||||
return null;
|
||||
}
|
||||
|
@ -25,9 +41,38 @@ abstract class PhabricatorAuthSSHKeyController
|
|||
return null;
|
||||
}
|
||||
|
||||
return id(new PhabricatorAuthSSHKey())
|
||||
->setObjectPHID($object_phid)
|
||||
->attachObject($object);
|
||||
$this->keyObject = $object;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
protected function newKeyForObjectPHID($object_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$object = $this->loadSSHKeyObject($object_phid, true);
|
||||
if (!$object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PhabricatorAuthSSHKey::initializeNewSSHKey($viewer, $object);
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$key_object = $this->getSSHKeyObject();
|
||||
if ($key_object) {
|
||||
$object_phid = $key_object->getPHID();
|
||||
$handles = $viewer->loadHandles(array($object_phid));
|
||||
$handle = $handles[$object_phid];
|
||||
|
||||
$uri = $key_object->getSSHPublicKeyManagementURI($viewer);
|
||||
|
||||
$crumbs->addTextCrumb($handle->getObjectName(), $uri);
|
||||
}
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyDeleteController
|
||||
final class PhabricatorAuthSSHKeyDeactivateController
|
||||
extends PhabricatorAuthSSHKeyController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
|
@ -19,7 +19,7 @@ final class PhabricatorAuthSSHKeyDeleteController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer);
|
||||
$cancel_uri = $key->getURI();
|
||||
|
||||
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
|
||||
$viewer,
|
||||
|
@ -27,21 +27,33 @@ final class PhabricatorAuthSSHKeyDeleteController
|
|||
$cancel_uri);
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
// TODO: It would be nice to write an edge transaction here or something.
|
||||
$key->delete();
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType(PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE)
|
||||
->setNewValue(true);
|
||||
|
||||
id(new PhabricatorAuthSSHKeyEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($key, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||
}
|
||||
|
||||
$name = phutil_tag('strong', array(), $key->getName());
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Really delete SSH Public Key?'))
|
||||
->setTitle(pht('Deactivate SSH Public Key'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'The key "%s" will be permanently deleted, and you will not longer '.
|
||||
'be able to use the corresponding private key to authenticate.',
|
||||
'The key "%s" will be permanently deactivated, and you will no '.
|
||||
'longer be able to use the corresponding private key to '.
|
||||
'authenticate.',
|
||||
$name))
|
||||
->addSubmitButton(pht('Delete Public Key'))
|
||||
->addSubmitButton(pht('Deactivate Public Key'))
|
||||
->addCancelButton($cancel_uri);
|
||||
}
|
||||
|
|
@ -59,51 +59,45 @@ final class PhabricatorAuthSSHKeyEditController
|
|||
$v_key = $key->getEntireKey();
|
||||
$e_key = strlen($v_key) ? null : true;
|
||||
|
||||
$errors = array();
|
||||
$validation_exception = null;
|
||||
if ($request->isFormPost()) {
|
||||
$type_create = PhabricatorTransactions::TYPE_CREATE;
|
||||
$type_name = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
|
||||
$type_key = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
|
||||
|
||||
$e_name = null;
|
||||
$e_key = null;
|
||||
|
||||
$v_name = $request->getStr('name');
|
||||
$v_key = $request->getStr('key');
|
||||
|
||||
if (!strlen($v_name)) {
|
||||
$errors[] = pht('You must provide a name for this public key.');
|
||||
$e_name = pht('Required');
|
||||
} else {
|
||||
$key->setName($v_name);
|
||||
$xactions = array();
|
||||
|
||||
if (!$key->getID()) {
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
|
||||
}
|
||||
|
||||
if (!strlen($v_key)) {
|
||||
$errors[] = pht('You must provide a public key.');
|
||||
$e_key = pht('Required');
|
||||
} else {
|
||||
try {
|
||||
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($v_key);
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType($type_name)
|
||||
->setNewValue($v_name);
|
||||
|
||||
$type = $public_key->getType();
|
||||
$body = $public_key->getBody();
|
||||
$comment = $public_key->getComment();
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType($type_key)
|
||||
->setNewValue($v_key);
|
||||
|
||||
$key->setKeyType($type);
|
||||
$key->setKeyBody($body);
|
||||
$key->setKeyComment($comment);
|
||||
$editor = id(new PhabricatorAuthSSHKeyEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
$e_key = null;
|
||||
} catch (Exception $ex) {
|
||||
$e_key = pht('Invalid');
|
||||
$errors[] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
try {
|
||||
$key->save();
|
||||
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||
} catch (Exception $ex) {
|
||||
$e_key = pht('Duplicate');
|
||||
$errors[] = pht(
|
||||
'This public key is already associated with another user or '.
|
||||
'device. Each key must unambiguously identify a single unique '.
|
||||
'owner.');
|
||||
}
|
||||
try {
|
||||
$editor->applyTransactions($key, $xactions);
|
||||
return id(new AphrontRedirectResponse())->setURI($key->getURI());
|
||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||
$validation_exception = $ex;
|
||||
$e_name = $ex->getShortMessage($type_name);
|
||||
$e_key = $ex->getShortMessage($type_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +128,7 @@ final class PhabricatorAuthSSHKeyEditController
|
|||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setErrors($errors)
|
||||
->setValidationException($validation_exception)
|
||||
->appendForm($form)
|
||||
->addSubmitButton($save_button)
|
||||
->addCancelButton($cancel_uri);
|
||||
|
|
|
@ -36,13 +36,31 @@ final class PhabricatorAuthSSHKeyGenerateController
|
|||
|
||||
$type = $public_key->getType();
|
||||
$body = $public_key->getBody();
|
||||
$comment = pht('Generated');
|
||||
|
||||
$key
|
||||
->setName($default_name)
|
||||
->setKeyType($type)
|
||||
->setKeyBody($body)
|
||||
->setKeyComment(pht('Generated'))
|
||||
->save();
|
||||
$entire_key = "{$type} {$body} {$comment}";
|
||||
|
||||
$type_create = PhabricatorTransactions::TYPE_CREATE;
|
||||
$type_name = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
|
||||
$type_key = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
|
||||
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType($type_name)
|
||||
->setNewValue($default_name);
|
||||
|
||||
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||
->setTransactionType($type_key)
|
||||
->setNewValue($entire_key);
|
||||
|
||||
$editor = id(new PhabricatorAuthSSHKeyEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($key, $xactions);
|
||||
|
||||
// NOTE: We're disabling workflow on submit so the download works. We're
|
||||
// disabling workflow on cancel so the page reloads, showing the new
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyListController
|
||||
extends PhabricatorAuthSSHKeyController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$object_phid = $request->getURIData('forPHID');
|
||||
$object = $this->loadSSHKeyObject($object_phid, false);
|
||||
if (!$object) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$engine = id(new PhabricatorAuthSSHKeySearchEngine())
|
||||
->setSSHKeyObject($object);
|
||||
|
||||
return id($engine)
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyViewController
|
||||
extends PhabricatorAuthSSHKeyController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$ssh_key = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$ssh_key) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setSSHKeyObject($ssh_key->getObject());
|
||||
|
||||
$title = pht('SSH Key %d', $ssh_key->getID());
|
||||
|
||||
$curtain = $this->buildCurtain($ssh_key);
|
||||
$details = $this->buildPropertySection($ssh_key);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setUser($viewer)
|
||||
->setHeader($ssh_key->getName())
|
||||
->setHeaderIcon('fa-key');
|
||||
|
||||
if ($ssh_key->getIsActive()) {
|
||||
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
|
||||
} else {
|
||||
$header->setStatus('fa-ban', 'dark', pht('Deactivated'));
|
||||
}
|
||||
|
||||
$header->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText(pht('View Active Keys'))
|
||||
->setHref($ssh_key->getObject()->getSSHPublicKeyManagementURI($viewer))
|
||||
->setIcon('fa-list-ul'));
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($title);
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$ssh_key,
|
||||
new PhabricatorAuthSSHKeyTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$details,
|
||||
$timeline,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
private function buildCurtain(PhabricatorAuthSSHKey $ssh_key) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$ssh_key,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$id = $ssh_key->getID();
|
||||
|
||||
$edit_uri = $this->getApplicationURI("sshkey/edit/{$id}/");
|
||||
$deactivate_uri = $this->getApplicationURI("sshkey/deactivate/{$id}/");
|
||||
|
||||
$curtain = $this->newCurtainView($ssh_key);
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil')
|
||||
->setName(pht('Edit SSH Key'))
|
||||
->setHref($edit_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-times')
|
||||
->setName(pht('Deactivate SSH Key'))
|
||||
->setHref($deactivate_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit));
|
||||
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
private function buildPropertySection(
|
||||
PhabricatorAuthSSHKey $ssh_key) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$properties = id(new PHUIPropertyListView())
|
||||
->setUser($viewer);
|
||||
|
||||
$properties->addProperty(pht('SSH Key Type'), $ssh_key->getKeyType());
|
||||
$properties->addProperty(
|
||||
pht('Created'),
|
||||
phabricator_datetime($ssh_key->getDateCreated(), $viewer));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Details'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($properties);
|
||||
}
|
||||
|
||||
}
|
244
src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
Normal file
244
src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
Normal file
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAuthApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('SSH Keys');
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
|
||||
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
|
||||
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
|
||||
return $object->getName();
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
|
||||
return $object->getEntireKey();
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
|
||||
return !$object->getIsActive();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
|
||||
return (bool)$xaction->getNewValue();
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$value = $xaction->getNewValue();
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
|
||||
$object->setName($value);
|
||||
return;
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
|
||||
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($value);
|
||||
|
||||
$type = $public_key->getType();
|
||||
$body = $public_key->getBody();
|
||||
$comment = $public_key->getComment();
|
||||
|
||||
$object->setKeyType($type);
|
||||
$object->setKeyBody($body);
|
||||
$object->setKeyComment($comment);
|
||||
return;
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
|
||||
if ($value) {
|
||||
$new = null;
|
||||
} else {
|
||||
$new = 1;
|
||||
}
|
||||
|
||||
$object->setIsActive($new);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('SSH key name is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('SSH key material is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
} else {
|
||||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
try {
|
||||
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new);
|
||||
} catch (Exception $ex) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
$ex->getMessage(),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
|
||||
foreach ($xactions as $xaction) {
|
||||
if (!$xaction->getNewValue()) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('SSH keys can not be reactivated.'),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
protected function didCatchDuplicateKeyException(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions,
|
||||
Exception $ex) {
|
||||
|
||||
$errors = array();
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
PhabricatorAuthSSHKeyTransaction::TYPE_KEY,
|
||||
pht('Duplicate'),
|
||||
pht(
|
||||
'This public key is already associated with another user or device. '.
|
||||
'Each key must unambiguously identify a single unique owner.'),
|
||||
null);
|
||||
|
||||
throw new PhabricatorApplicationTransactionValidationException($errors);
|
||||
}
|
||||
|
||||
|
||||
protected function shouldSendMail(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getMailSubjectPrefix() {
|
||||
return pht('[SSH Key]');
|
||||
}
|
||||
|
||||
protected function getMailThreadID(PhabricatorLiskDAO $object) {
|
||||
return 'ssh-key-'.$object->getPHID();
|
||||
}
|
||||
|
||||
protected function getMailTo(PhabricatorLiskDAO $object) {
|
||||
return $object->getObject()->getSSHKeyNotifyPHIDs();
|
||||
}
|
||||
|
||||
protected function getMailCC(PhabricatorLiskDAO $object) {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
|
||||
return id(new PhabricatorAuthSSHKeyReplyHandler())
|
||||
->setMailReceiver($object);
|
||||
}
|
||||
|
||||
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
|
||||
$id = $object->getID();
|
||||
$name = $object->getName();
|
||||
$phid = $object->getPHID();
|
||||
|
||||
$mail = id(new PhabricatorMetaMTAMail())
|
||||
->setSubject(pht('SSH Key %d: %s', $id, $name))
|
||||
->addHeader('Thread-Topic', $phid);
|
||||
|
||||
// The primary value of this mail is alerting users to account compromises,
|
||||
// so force delivery. In particular, this mail should still be delievered
|
||||
// even if "self mail" is disabled.
|
||||
$mail->setForceDelivery(true);
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
protected function buildMailBody(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
$body = parent::buildMailBody($object, $xactions);
|
||||
|
||||
$body->addTextSection(
|
||||
pht('SECURITY WARNING'),
|
||||
pht(
|
||||
'If you do not recognize this change, it may indicate your account '.
|
||||
'has been compromised.'));
|
||||
|
||||
$detail_uri = $object->getURI();
|
||||
$detail_uri = PhabricatorEnv::getProductionURI($detail_uri);
|
||||
|
||||
$body->addLinkSection(pht('SSH KEY DETAIL'), $detail_uri);
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyReplyHandler
|
||||
extends PhabricatorApplicationTransactionReplyHandler {
|
||||
|
||||
public function validateMailReceiver($mail_receiver) {
|
||||
if (!($mail_receiver instanceof PhabricatorAuthSSHKey)) {
|
||||
throw new Exception(
|
||||
pht('Mail receiver is not a %s!', 'PhabricatorAuthSSHKey'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getObjectPrefix() {
|
||||
return 'SSHKEY';
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,10 @@ final class PhabricatorAuthSSHKeyPHIDType
|
|||
foreach ($handles as $phid => $handle) {
|
||||
$key = $objects[$phid];
|
||||
$handle->setName(pht('SSH Key %d', $key->getID()));
|
||||
|
||||
if (!$key->getIsActive()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ final class PhabricatorAuthSSHKeyQuery
|
|||
private $phids;
|
||||
private $objectPHIDs;
|
||||
private $keys;
|
||||
private $isActive;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -29,6 +30,11 @@ final class PhabricatorAuthSSHKeyQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withIsActive($active) {
|
||||
$this->isActive = $active;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorAuthSSHKey();
|
||||
}
|
||||
|
@ -100,6 +106,19 @@ final class PhabricatorAuthSSHKeyQuery
|
|||
$where[] = implode(' OR ', $sql);
|
||||
}
|
||||
|
||||
if ($this->isActive !== null) {
|
||||
if ($this->isActive) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'isActive = %d',
|
||||
1);
|
||||
} else {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'isActive IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeySearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
private $sshKeyObject;
|
||||
|
||||
public function setSSHKeyObject(PhabricatorSSHPublicKeyInterface $object) {
|
||||
$this->sshKeyObject = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSSHKeyObject() {
|
||||
return $this->sshKeyObject;
|
||||
}
|
||||
|
||||
public function canUseInPanelContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('SSH Keys');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorAuthApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
$object = $this->getSSHKeyObject();
|
||||
$object_phid = $object->getPHID();
|
||||
|
||||
return id(new PhabricatorAuthSSHKeyQuery())
|
||||
->withObjectPHIDs(array($object_phid));
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
$object = $this->getSSHKeyObject();
|
||||
$object_phid = $object->getPHID();
|
||||
|
||||
return "/auth/sshkey/for/{$object_phid}/{$path}";
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'all' => pht('All Keys'),
|
||||
);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $keys,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($keys, 'PhabricatorAuthSSHKey');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
$list->setUser($viewer);
|
||||
foreach ($keys as $key) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName(pht('SSH Key %d', $key->getID()))
|
||||
->setHeader($key->getName())
|
||||
->setHref($key->getURI());
|
||||
|
||||
if (!$key->getIsActive()) {
|
||||
$item->setDisabled(true);
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setObjectList($list);
|
||||
$result->setNoDataString(pht('No matching SSH keys.'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhabricatorAuthSSHKeyTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,4 +17,6 @@ interface PhabricatorSSHPublicKeyInterface {
|
|||
*/
|
||||
public function getSSHKeyDefaultName();
|
||||
|
||||
public function getSSHKeyNotifyPHIDs();
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ final class PhabricatorAuthSSHKey
|
|||
extends PhabricatorAuthDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorApplicationTransactionInterface {
|
||||
|
||||
protected $objectPHID;
|
||||
protected $name;
|
||||
|
@ -13,9 +14,28 @@ final class PhabricatorAuthSSHKey
|
|||
protected $keyBody;
|
||||
protected $keyComment = '';
|
||||
protected $isTrusted = 0;
|
||||
protected $isActive;
|
||||
|
||||
private $object = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewSSHKey(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorSSHPublicKeyInterface $object) {
|
||||
|
||||
// You must be able to edit an object to create a new key on it.
|
||||
PhabricatorPolicyFilter::requireCapability(
|
||||
$viewer,
|
||||
$object,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$object_phid = $object->getPHID();
|
||||
|
||||
return id(new self())
|
||||
->setIsActive(1)
|
||||
->setObjectPHID($object_phid)
|
||||
->attachObject($object);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
@ -26,13 +46,19 @@ final class PhabricatorAuthSSHKey
|
|||
'keyBody' => 'text',
|
||||
'keyComment' => 'text255',
|
||||
'isTrusted' => 'bool',
|
||||
'isActive' => 'bool?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_object' => array(
|
||||
'columns' => array('objectPHID'),
|
||||
),
|
||||
'key_unique' => array(
|
||||
'columns' => array('keyIndex'),
|
||||
'key_active' => array(
|
||||
'columns' => array('isActive', 'objectPHID'),
|
||||
),
|
||||
// NOTE: This unique key includes a nullable column, effectively
|
||||
// constraining uniqueness on active keys only.
|
||||
'key_activeunique' => array(
|
||||
'columns' => array('keyIndex', 'isActive'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
|
@ -44,6 +70,12 @@ final class PhabricatorAuthSSHKey
|
|||
return parent::save();
|
||||
}
|
||||
|
||||
public function getMailKey() {
|
||||
// NOTE: We don't actually receive mail for these objects. It's OK for
|
||||
// the mail key to be predictable until we do.
|
||||
return PhabricatorHash::digestForIndex($this->getPHID());
|
||||
}
|
||||
|
||||
public function toPublicKey() {
|
||||
return PhabricatorAuthSSHPublicKey::newFromStoredKey($this);
|
||||
}
|
||||
|
@ -71,6 +103,11 @@ final class PhabricatorAuthSSHKey
|
|||
PhabricatorAuthSSHKeyPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$id = $this->getID();
|
||||
return "/auth/sshkey/view/{$id}/";
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
|
@ -82,14 +119,29 @@ final class PhabricatorAuthSSHKey
|
|||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
if (!$this->getIsActive()) {
|
||||
if ($capability == PhabricatorPolicyCapability::CAN_EDIT) {
|
||||
return PhabricatorPolicies::POLICY_NOONE;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getObject()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
if (!$this->getIsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
if (!$this->getIsACtive()) {
|
||||
return pht(
|
||||
'Deactivated SSH keys can not be edited or reactivated.');
|
||||
}
|
||||
|
||||
return pht(
|
||||
'SSH keys inherit the policies of the user or object they authenticate.');
|
||||
}
|
||||
|
@ -105,4 +157,26 @@ final class PhabricatorAuthSSHKey
|
|||
$this->saveTransaction();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new PhabricatorAuthSSHKeyEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorAuthSSHKeyTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSSHKeyTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_NAME = 'sshkey.name';
|
||||
const TYPE_KEY = 'sshkey.key';
|
||||
const TYPE_DEACTIVATE = 'sshkey.deactivate';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'auth';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return PhabricatorAuthSSHKeyPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case PhabricatorTransactions::TYPE_CREATE:
|
||||
return pht(
|
||||
'%s created this key.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_NAME:
|
||||
return pht(
|
||||
'%s renamed this key from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
case self::TYPE_KEY:
|
||||
return pht(
|
||||
'%s updated the public key material for this SSH key.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_DEACTIVATE:
|
||||
if ($new) {
|
||||
return pht(
|
||||
'%s deactivated this key.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s activated this key.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,58 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
|||
private $showTrusted;
|
||||
private $showID;
|
||||
|
||||
public static function newKeyActionsMenu(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorSSHPublicKeyInterface $object) {
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$object,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
try {
|
||||
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
|
||||
$can_generate = true;
|
||||
} catch (Exception $ex) {
|
||||
$can_generate = false;
|
||||
}
|
||||
|
||||
$object_phid = $object->getPHID();
|
||||
|
||||
$generate_uri = "/auth/sshkey/generate/?objectPHID={$object_phid}";
|
||||
$upload_uri = "/auth/sshkey/upload/?objectPHID={$object_phid}";
|
||||
$view_uri = "/auth/sshkey/for/{$object_phid}/";
|
||||
|
||||
$action_view = id(new PhabricatorActionListView())
|
||||
->setUser($viewer)
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($upload_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit)
|
||||
->setName(pht('Upload Public Key'))
|
||||
->setIcon('fa-upload'))
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($generate_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit || !$can_generate)
|
||||
->setName(pht('Generate Keypair'))
|
||||
->setIcon('fa-lock'))
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($view_uri)
|
||||
->setName(pht('View History'))
|
||||
->setIcon('fa-list-ul'));
|
||||
|
||||
return id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText(pht('SSH Key Actions'))
|
||||
->setHref('#')
|
||||
->setIcon('fa-gear')
|
||||
->setDropdownMenu($action_view);
|
||||
}
|
||||
|
||||
public function setNoDataString($no_data_string) {
|
||||
$this->noDataString = $no_data_string;
|
||||
return $this;
|
||||
|
@ -38,12 +90,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
|||
$keys = $this->keys;
|
||||
$viewer = $this->getUser();
|
||||
|
||||
if ($this->canEdit) {
|
||||
$delete_class = 'small grey button';
|
||||
} else {
|
||||
$delete_class = 'small grey button disabled';
|
||||
}
|
||||
|
||||
$trusted_icon = id(new PHUIIconView())
|
||||
->setIcon('fa-star blue');
|
||||
$untrusted_icon = id(new PHUIIconView())
|
||||
|
@ -56,22 +102,13 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
|||
javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/auth/sshkey/edit/'.$key->getID().'/',
|
||||
'sigil' => 'workflow',
|
||||
'href' => $key->getURI(),
|
||||
),
|
||||
$key->getName()),
|
||||
$key->getIsTrusted() ? $trusted_icon : $untrusted_icon,
|
||||
$key->getKeyComment(),
|
||||
$key->getKeyType(),
|
||||
phabricator_datetime($key->getDateCreated(), $viewer),
|
||||
javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/auth/sshkey/delete/'.$key->getID().'/',
|
||||
'class' => $delete_class,
|
||||
'sigil' => 'workflow',
|
||||
),
|
||||
pht('Delete')),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -85,7 +122,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
|||
pht('Comment'),
|
||||
pht('Type'),
|
||||
pht('Added'),
|
||||
null,
|
||||
))
|
||||
->setColumnVisibility(
|
||||
array(
|
||||
|
@ -101,7 +137,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
|||
'',
|
||||
'',
|
||||
'right',
|
||||
'action',
|
||||
));
|
||||
|
||||
return $table;
|
||||
|
|
|
@ -21,22 +21,4 @@ final class PhabricatorCalendarHoliday extends PhabricatorCalendarDAO {
|
|||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public static function getNthBusinessDay($epoch, $n) {
|
||||
// Sadly, there are not many holidays. So we can load all of them.
|
||||
$holidays = id(new PhabricatorCalendarHoliday())->loadAll();
|
||||
$holidays = mpull($holidays, null, 'getDay');
|
||||
$interval = ($n > 0 ? 1 : -1) * 24 * 60 * 60;
|
||||
|
||||
$return = $epoch;
|
||||
for ($i = abs($n); $i > 0; ) {
|
||||
$return += $interval;
|
||||
$weekday = date('w', $return);
|
||||
if ($weekday != 0 && $weekday != 6 && // Sunday and Saturday
|
||||
!isset($holidays[date('Y-m-d', $return)])) {
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,24 +16,4 @@ final class PhabricatorCalendarHolidayTestCase extends PhabricatorTestCase {
|
|||
->save();
|
||||
}
|
||||
|
||||
public function testNthBusinessDay() {
|
||||
$map = array(
|
||||
array('2011-12-30', 1, '2012-01-03'),
|
||||
array('2012-01-01', 1, '2012-01-03'),
|
||||
array('2012-01-01', 0, '2012-01-01'),
|
||||
array('2012-01-01', -1, '2011-12-30'),
|
||||
array('2012-01-04', -1, '2012-01-03'),
|
||||
);
|
||||
foreach ($map as $val) {
|
||||
list($date, $n, $expect) = $val;
|
||||
$actual = PhabricatorCalendarHoliday::getNthBusinessDay(
|
||||
strtotime($date),
|
||||
$n);
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
date('Y-m-d', $actual),
|
||||
pht("%d business days since '%s'", $n, $date));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -204,6 +204,7 @@ final class PhabricatorConduitAPIController
|
|||
$stored_key = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withKeys(array($public_key))
|
||||
->withIsActive(true)
|
||||
->executeOne();
|
||||
if (!$stored_key) {
|
||||
return array(
|
||||
|
|
|
@ -186,6 +186,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
|||
'Configuration of the notification server has changed substantially. '.
|
||||
'For discussion, see T10794.');
|
||||
|
||||
$stale_reason = pht(
|
||||
'The Differential revision list view age UI elements have been removed '.
|
||||
'to simplify the interface.');
|
||||
|
||||
$ancient_config += array(
|
||||
'phid.external-loaders' =>
|
||||
pht(
|
||||
|
@ -314,6 +318,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
|||
'metamta.differential.unified-comment-context' => pht(
|
||||
'Inline comments are now always rendered with a limited amount '.
|
||||
'of context.'),
|
||||
|
||||
'differential.days-fresh' => $stale_reason,
|
||||
'differential.days-stale' => $stale_reason,
|
||||
);
|
||||
|
||||
return $ancient_config;
|
||||
|
|
|
@ -102,68 +102,78 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public static function loadNeedAttentionRevisions(PhabricatorUser $viewer) {
|
||||
if (!$viewer->isLoggedIn()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$viewer_phid = $viewer->getPHID();
|
||||
|
||||
$responsible_phids = id(new DifferentialResponsibleDatasource())
|
||||
->setViewer($viewer)
|
||||
->evaluateTokens(array($viewer_phid));
|
||||
|
||||
$revision_query = id(new DifferentialRevisionQuery())
|
||||
->setViewer($viewer)
|
||||
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
|
||||
->withResponsibleUsers($responsible_phids)
|
||||
->needReviewerStatus(true)
|
||||
->needRelationships(true)
|
||||
->needFlags(true)
|
||||
->needDrafts(true)
|
||||
->setLimit(self::MAX_STATUS_ITEMS);
|
||||
|
||||
$revisions = $revision_query->execute();
|
||||
|
||||
$query = id(new PhabricatorSavedQuery())
|
||||
->attachParameterMap(
|
||||
array(
|
||||
'responsiblePHIDs' => $responsible_phids,
|
||||
));
|
||||
|
||||
$groups = id(new DifferentialRevisionRequiredActionResultBucket())
|
||||
->setViewer($viewer)
|
||||
->newResultGroups($query, $revisions);
|
||||
|
||||
$include = array();
|
||||
foreach ($groups as $group) {
|
||||
switch ($group->getKey()) {
|
||||
case DifferentialRevisionRequiredActionResultBucket::KEY_MUSTREVIEW:
|
||||
case DifferentialRevisionRequiredActionResultBucket::KEY_SHOULDREVIEW:
|
||||
foreach ($group->getObjects() as $object) {
|
||||
$include[] = $object;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $include;
|
||||
}
|
||||
|
||||
public function loadStatus(PhabricatorUser $user) {
|
||||
$revisions = self::loadNeedAttentionRevisions($user);
|
||||
$limit = self::MAX_STATUS_ITEMS;
|
||||
|
||||
$revisions = id(new DifferentialRevisionQuery())
|
||||
->setViewer($user)
|
||||
->withResponsibleUsers(array($user->getPHID()))
|
||||
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
|
||||
->needRelationships(true)
|
||||
->setLimit($limit)
|
||||
->execute();
|
||||
if (count($revisions) >= $limit) {
|
||||
$display_count = ($limit - 1);
|
||||
$display_label = pht(
|
||||
'%s+ Active Review(s)',
|
||||
new PhutilNumber($display_count));
|
||||
} else {
|
||||
$display_count = count($revisions);
|
||||
$display_label = pht(
|
||||
'%s Review(s) Need Attention',
|
||||
new PhutilNumber($display_count));
|
||||
}
|
||||
|
||||
$status = array();
|
||||
if (count($revisions) >= $limit) {
|
||||
$all_count = count($revisions);
|
||||
$all_count_str = pht(
|
||||
'%s+ Active Review(s)',
|
||||
new PhutilNumber($limit - 1));
|
||||
|
||||
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
|
||||
$status[] = id(new PhabricatorApplicationStatusView())
|
||||
->setType($type)
|
||||
->setText($all_count_str)
|
||||
->setCount($all_count);
|
||||
} else {
|
||||
list($blocking, $active, $waiting) =
|
||||
DifferentialRevisionQuery::splitResponsible(
|
||||
$revisions,
|
||||
array($user->getPHID()));
|
||||
|
||||
$blocking = count($blocking);
|
||||
$blocking_str = pht(
|
||||
'%s Review(s) Blocking Others',
|
||||
new PhutilNumber($blocking));
|
||||
|
||||
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
|
||||
$status[] = id(new PhabricatorApplicationStatusView())
|
||||
->setType($type)
|
||||
->setText($blocking_str)
|
||||
->setCount($blocking);
|
||||
|
||||
$active = count($active);
|
||||
$active_str = pht(
|
||||
'%s Review(s) Need Attention',
|
||||
new PhutilNumber($active));
|
||||
|
||||
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
|
||||
$status[] = id(new PhabricatorApplicationStatusView())
|
||||
->setType($type)
|
||||
->setText($active_str)
|
||||
->setCount($active);
|
||||
|
||||
$waiting = count($waiting);
|
||||
$waiting_str = pht(
|
||||
'%s Review(s) Waiting on Others',
|
||||
new PhutilNumber($waiting));
|
||||
|
||||
$type = PhabricatorApplicationStatusView::TYPE_INFO;
|
||||
$status[] = id(new PhabricatorApplicationStatusView())
|
||||
->setType($type)
|
||||
->setText($waiting_str)
|
||||
->setCount($waiting);
|
||||
}
|
||||
$status[] = id(new PhabricatorApplicationStatusView())
|
||||
->setType(PhabricatorApplicationStatusView::TYPE_WARNING)
|
||||
->setText($display_label)
|
||||
->setCount($display_count);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
|
|
@ -229,25 +229,6 @@ final class PhabricatorDifferentialConfigOptions
|
|||
"\n\n".
|
||||
'This sort of workflow is very unusual. Very few installs should '.
|
||||
'need to change this option.')),
|
||||
$this->newOption('differential.days-fresh', 'int', 1)
|
||||
->setSummary(
|
||||
pht(
|
||||
"For how many business days should a revision be considered ".
|
||||
"'fresh'?"))
|
||||
->setDescription(
|
||||
pht(
|
||||
'Revisions newer than this number of days are marked as fresh in '.
|
||||
'Action Required and Revisions Waiting on You views. Only work '.
|
||||
'days (not weekends and holidays) are included. Set to 0 to '.
|
||||
'disable this feature.')),
|
||||
$this->newOption('differential.days-stale', 'int', 3)
|
||||
->setSummary(
|
||||
pht("After this many days, a revision will be considered 'stale'."))
|
||||
->setDescription(
|
||||
pht(
|
||||
"Similar to `%s` but marks stale revisions. ".
|
||||
"If the revision is even older than it is when marked as 'old'.",
|
||||
'differential.days-fresh')),
|
||||
$this->newOption(
|
||||
'metamta.differential.subject-prefix',
|
||||
'string',
|
||||
|
|
|
@ -35,27 +35,39 @@ abstract class DifferentialCustomField
|
|||
protected function parseObjectList(
|
||||
$value,
|
||||
array $types,
|
||||
$allow_partial = false) {
|
||||
$allow_partial = false,
|
||||
array $suffixes = array()) {
|
||||
return id(new PhabricatorObjectListQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setAllowedTypes($types)
|
||||
->setObjectList($value)
|
||||
->setAllowPartialResults($allow_partial)
|
||||
->setSuffixes($suffixes)
|
||||
->execute();
|
||||
}
|
||||
|
||||
protected function renderObjectList(array $handles) {
|
||||
protected function renderObjectList(
|
||||
array $handles,
|
||||
array $suffixes = array()) {
|
||||
|
||||
if (!$handles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$out = array();
|
||||
foreach ($handles as $handle) {
|
||||
$phid = $handle->getPHID();
|
||||
|
||||
if ($handle->getPolicyFiltered()) {
|
||||
$out[] = $handle->getPHID();
|
||||
$token = $phid;
|
||||
} else if ($handle->isComplete()) {
|
||||
$out[] = $handle->getObjectName();
|
||||
$token = $handle->getCommandLineObjectName();
|
||||
}
|
||||
|
||||
$suffix = idx($suffixes, $phid);
|
||||
$token = $token.$suffix;
|
||||
|
||||
$out[] = $token;
|
||||
}
|
||||
|
||||
return implode(', ', $out);
|
||||
|
|
|
@ -8,7 +8,7 @@ final class DifferentialProjectReviewersField
|
|||
}
|
||||
|
||||
public function getFieldName() {
|
||||
return pht('Project Reviewers');
|
||||
return pht('Group Reviewers');
|
||||
}
|
||||
|
||||
public function getFieldDescription() {
|
||||
|
|
|
@ -36,43 +36,82 @@ final class DifferentialReviewersField
|
|||
}
|
||||
|
||||
public function readValueFromRequest(AphrontRequest $request) {
|
||||
// Compute a new set of reviewer objects. For reviewers who haven't been
|
||||
// added or removed, retain their existing status. Also, respect the new
|
||||
// order.
|
||||
|
||||
$old_status = $this->getValue();
|
||||
$old_status = mpull($old_status, null, 'getReviewerPHID');
|
||||
$datasource = id(new DifferentialBlockingReviewerDatasource())
|
||||
->setViewer($request->getViewer());
|
||||
|
||||
$new_phids = $request->getArr($this->getFieldKey());
|
||||
$new_phids = array_fuse($new_phids);
|
||||
$new_phids = $datasource->evaluateTokens($new_phids);
|
||||
|
||||
$new_status = array();
|
||||
foreach ($new_phids as $new_phid) {
|
||||
if (empty($old_status[$new_phid])) {
|
||||
$new_status[$new_phid] = new DifferentialReviewer(
|
||||
$new_phid,
|
||||
array(
|
||||
'status' => DifferentialReviewerStatus::STATUS_ADDED,
|
||||
));
|
||||
$reviewers = array();
|
||||
foreach ($new_phids as $spec) {
|
||||
if (!is_array($spec)) {
|
||||
$reviewers[$spec] = DifferentialReviewerStatus::STATUS_ADDED;
|
||||
} else {
|
||||
$new_status[$new_phid] = $old_status[$new_phid];
|
||||
$reviewers[$spec['phid']] = $spec['type'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->setValue($new_status);
|
||||
$this->updateReviewers($this->getValue(), $reviewers);
|
||||
}
|
||||
|
||||
private function updateReviewers(array $old_reviewers, array $new_map) {
|
||||
// Compute a new set of reviewer objects. We're going to respect the new
|
||||
// reviewer order, add or remove any new or missing reviewers, and respect
|
||||
// any blocking or unblocking changes. For reviewers who were there before
|
||||
// and are still there, we're going to keep the old value because it
|
||||
// may be something like "Accept", "Reject", etc.
|
||||
|
||||
$old_map = mpull($old_reviewers, 'getStatus', 'getReviewerPHID');
|
||||
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||
|
||||
$new_reviewers = array();
|
||||
foreach ($new_map as $phid => $new) {
|
||||
$old = idx($old_map, $phid);
|
||||
|
||||
// If we have an old status and this didn't make the reviewer blocking
|
||||
// or nonblocking, just retain the old status. This makes sure we don't
|
||||
// throw away rejects, accepts, etc.
|
||||
if ($old) {
|
||||
$is_block = ($old !== $status_blocking && $new === $status_blocking);
|
||||
$is_unblock = ($old === $status_blocking && $new !== $status_blocking);
|
||||
if (!$is_block && !$is_unblock) {
|
||||
$new_reviewers[$phid] = $old;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$new_reviewers[$phid] = $new;
|
||||
}
|
||||
|
||||
foreach ($new_reviewers as $phid => $status) {
|
||||
$new_reviewers[$phid] = new DifferentialReviewer(
|
||||
$phid,
|
||||
array(
|
||||
'status' => $status,
|
||||
));
|
||||
}
|
||||
|
||||
$this->setValue($new_reviewers);
|
||||
}
|
||||
|
||||
public function renderEditControl(array $handles) {
|
||||
$phids = array();
|
||||
if ($this->getValue()) {
|
||||
$phids = mpull($this->getValue(), 'getReviewerPHID');
|
||||
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||
|
||||
$value = array();
|
||||
foreach ($this->getValue() as $reviewer) {
|
||||
$phid = $reviewer->getReviewerPHID();
|
||||
if ($reviewer->getStatus() == $status_blocking) {
|
||||
$value[] = 'blocking('.$phid.')';
|
||||
} else {
|
||||
$value[] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
return id(new AphrontFormTokenizerControl())
|
||||
->setUser($this->getViewer())
|
||||
->setName($this->getFieldKey())
|
||||
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
||||
->setValue($phids)
|
||||
->setDatasource(new DifferentialReviewerDatasource())
|
||||
->setValue($value)
|
||||
->setError($this->getFieldError())
|
||||
->setLabel($this->getFieldName());
|
||||
}
|
||||
|
@ -141,12 +180,17 @@ final class DifferentialReviewersField
|
|||
}
|
||||
|
||||
public function parseValueFromCommitMessage($value) {
|
||||
return $this->parseObjectList(
|
||||
$results = $this->parseObjectList(
|
||||
$value,
|
||||
array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
));
|
||||
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
||||
),
|
||||
false,
|
||||
array('!'));
|
||||
|
||||
return $this->flattenReviewers($results);
|
||||
}
|
||||
|
||||
public function getRequiredHandlePHIDsForCommitMessage() {
|
||||
|
@ -154,29 +198,42 @@ final class DifferentialReviewersField
|
|||
}
|
||||
|
||||
public function readValueFromCommitMessage($value) {
|
||||
$current_reviewers = $this->getObject()->getReviewerStatus();
|
||||
$current_reviewers = mpull($current_reviewers, null, 'getReviewerPHID');
|
||||
$value = $this->inflateReviewers($value);
|
||||
|
||||
$reviewers = array();
|
||||
foreach ($value as $phid) {
|
||||
$reviewer = idx($current_reviewers, $phid);
|
||||
if ($reviewer) {
|
||||
$reviewers[] = $reviewer;
|
||||
foreach ($value as $spec) {
|
||||
$phid = $spec['phid'];
|
||||
|
||||
$is_blocking = isset($spec['suffixes']['!']);
|
||||
if ($is_blocking) {
|
||||
$status = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||
} else {
|
||||
$data = array(
|
||||
'status' => DifferentialReviewerStatus::STATUS_ADDED,
|
||||
);
|
||||
$reviewers[] = new DifferentialReviewer($phid, $data);
|
||||
$status = DifferentialReviewerStatus::STATUS_ADDED;
|
||||
}
|
||||
|
||||
$reviewers[$phid] = $status;
|
||||
}
|
||||
|
||||
$this->setValue($reviewers);
|
||||
$this->updateReviewers(
|
||||
$this->getObject()->getReviewerStatus(),
|
||||
$reviewers);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function renderCommitMessageValue(array $handles) {
|
||||
return $this->renderObjectList($handles);
|
||||
$suffixes = array();
|
||||
|
||||
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||
|
||||
foreach ($this->getValue() as $reviewer) {
|
||||
if ($reviewer->getStatus() == $status_blocking) {
|
||||
$phid = $reviewer->getReviewerPHID();
|
||||
$suffixes[$phid] = '!';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderObjectList($handles, $suffixes);
|
||||
}
|
||||
|
||||
public function validateCommitMessageValue($value) {
|
||||
|
@ -185,7 +242,9 @@ final class DifferentialReviewersField
|
|||
$config_self_accept_key = 'differential.allow-self-accept';
|
||||
$allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
|
||||
|
||||
foreach ($value as $phid) {
|
||||
foreach ($value as $spec) {
|
||||
$phid = $spec['phid'];
|
||||
|
||||
if (($phid == $author_phid) && !$allow_self_accept) {
|
||||
throw new DifferentialFieldValidationException(
|
||||
pht('The author of a revision can not be a reviewer.'));
|
||||
|
@ -224,4 +283,44 @@ final class DifferentialReviewersField
|
|||
return $warnings;
|
||||
}
|
||||
|
||||
public function getProTips() {
|
||||
return array(
|
||||
pht(
|
||||
'You can mark a reviewer as blocking by adding an exclamation '.
|
||||
'mark ("!") after their name.'),
|
||||
);
|
||||
}
|
||||
|
||||
private function flattenReviewers(array $values) {
|
||||
// NOTE: For now, `arc` relies on this field returning only scalars, so we
|
||||
// need to reduce the results into scalars. See T10981.
|
||||
$result = array();
|
||||
|
||||
foreach ($values as $value) {
|
||||
$result[] = $value['phid'].implode('', array_keys($value['suffixes']));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function inflateReviewers(array $values) {
|
||||
$result = array();
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (substr($value, -1) == '!') {
|
||||
$value = substr($value, 0, -1);
|
||||
$suffixes = array('!' => '!');
|
||||
} else {
|
||||
$suffixes = array();
|
||||
}
|
||||
|
||||
$result[] = array(
|
||||
'phid' => $value,
|
||||
'suffixes' => $suffixes,
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ final class DifferentialSubscribersField
|
|||
array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ final class DifferentialTransactionEditor
|
|||
private $isCloseByCommit;
|
||||
private $repositoryPHIDOverride = false;
|
||||
private $didExpandInlineState = false;
|
||||
private $affectedPaths;
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
|
@ -1200,7 +1201,13 @@ final class DifferentialTransactionEditor
|
|||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->setViewer($this->requireActor());
|
||||
|
||||
$this->addHeadersAndCommentsToMailBody($body, $xactions);
|
||||
$revision_uri = PhabricatorEnv::getProductionURI('/D'.$object->getID());
|
||||
|
||||
$this->addHeadersAndCommentsToMailBody(
|
||||
$body,
|
||||
$xactions,
|
||||
pht('View Revision'),
|
||||
$revision_uri);
|
||||
|
||||
$type_inline = DifferentialTransaction::TYPE_INLINE;
|
||||
|
||||
|
@ -1226,7 +1233,7 @@ final class DifferentialTransactionEditor
|
|||
|
||||
$body->addLinkSection(
|
||||
pht('REVISION DETAIL'),
|
||||
PhabricatorEnv::getProductionURI('/D'.$object->getID()));
|
||||
$revision_uri);
|
||||
|
||||
$update_xaction = null;
|
||||
foreach ($xactions as $xaction) {
|
||||
|
@ -1481,6 +1488,181 @@ final class DifferentialTransactionEditor
|
|||
return parent::shouldApplyHeraldRules($object, $xactions);
|
||||
}
|
||||
|
||||
protected function didApplyHeraldRules(
|
||||
PhabricatorLiskDAO $object,
|
||||
HeraldAdapter $adapter,
|
||||
HeraldTranscript $transcript) {
|
||||
|
||||
$repository = $object->getRepository();
|
||||
if (!$repository) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!$this->affectedPaths) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
|
||||
$repository,
|
||||
$this->affectedPaths);
|
||||
|
||||
foreach ($packages as $key => $package) {
|
||||
if ($package->isArchived()) {
|
||||
unset($packages[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$packages) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Remove packages that the revision author is an owner of. If you own
|
||||
// code, you don't need another owner to review it.
|
||||
$authority = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(mpull($packages, 'getPHID'))
|
||||
->withAuthorityPHIDs(array($object->getAuthorPHID()))
|
||||
->execute();
|
||||
$authority = mpull($authority, null, 'getPHID');
|
||||
|
||||
foreach ($packages as $key => $package) {
|
||||
$package_phid = $package->getPHID();
|
||||
if ($authority[$package_phid]) {
|
||||
unset($packages[$key]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$packages) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$auto_subscribe = array();
|
||||
$auto_review = array();
|
||||
$auto_block = array();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
switch ($package->getAutoReview()) {
|
||||
case PhabricatorOwnersPackage::AUTOREVIEW_SUBSCRIBE:
|
||||
$auto_subscribe[] = $package;
|
||||
break;
|
||||
case PhabricatorOwnersPackage::AUTOREVIEW_REVIEW:
|
||||
$auto_review[] = $package;
|
||||
break;
|
||||
case PhabricatorOwnersPackage::AUTOREVIEW_BLOCK:
|
||||
$auto_block[] = $package;
|
||||
break;
|
||||
case PhabricatorOwnersPackage::AUTOREVIEW_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$owners_phid = id(new PhabricatorOwnersApplication())
|
||||
->getPHID();
|
||||
|
||||
$xactions = array();
|
||||
if ($auto_subscribe) {
|
||||
$xactions[] = $object->getApplicationTransactionTemplate()
|
||||
->setAuthorPHID($owners_phid)
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
|
||||
->setNewValue(
|
||||
array(
|
||||
'+' => mpull($auto_subscribe, 'getPHID'),
|
||||
));
|
||||
}
|
||||
|
||||
$specs = array(
|
||||
array($auto_review, false),
|
||||
array($auto_block, true),
|
||||
);
|
||||
|
||||
foreach ($specs as $spec) {
|
||||
list($reviewers, $blocking) = $spec;
|
||||
if (!$reviewers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phids = mpull($reviewers, 'getPHID');
|
||||
$xaction = $this->newAutoReviewTransaction($object, $phids, $blocking);
|
||||
if ($xaction) {
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
private function newAutoReviewTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $phids,
|
||||
$is_blocking) {
|
||||
|
||||
// TODO: This is substantially similar to DifferentialReviewersHeraldAction
|
||||
// and both are needlessly complex. This logic should live in the normal
|
||||
// transaction application pipeline. See T10967.
|
||||
|
||||
$reviewers = $object->getReviewerStatus();
|
||||
$reviewers = mpull($reviewers, null, 'getReviewerPHID');
|
||||
|
||||
if ($is_blocking) {
|
||||
$new_status = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||
} else {
|
||||
$new_status = DifferentialReviewerStatus::STATUS_ADDED;
|
||||
}
|
||||
|
||||
$new_strength = DifferentialReviewerStatus::getStatusStrength(
|
||||
$new_status);
|
||||
|
||||
$current = array();
|
||||
foreach ($phids as $phid) {
|
||||
if (!isset($reviewers[$phid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're applying a stronger status (usually, upgrading a reviewer
|
||||
// into a blocking reviewer), skip this check so we apply the change.
|
||||
$old_strength = DifferentialReviewerStatus::getStatusStrength(
|
||||
$reviewers[$phid]->getStatus());
|
||||
if ($old_strength <= $new_strength) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current[] = $phid;
|
||||
}
|
||||
|
||||
$phids = array_diff($phids, $current);
|
||||
|
||||
if (!$phids) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$phids = array_fuse($phids);
|
||||
|
||||
$value = array();
|
||||
foreach ($phids as $phid) {
|
||||
$value[$phid] = array(
|
||||
'data' => array(
|
||||
'status' => $new_status,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$edgetype_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
||||
|
||||
$owners_phid = id(new PhabricatorOwnersApplication())
|
||||
->getPHID();
|
||||
|
||||
return $object->getApplicationTransactionTemplate()
|
||||
->setAuthorPHID($owners_phid)
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $edgetype_reviewer)
|
||||
->setNewValue(
|
||||
array(
|
||||
'+' => $value,
|
||||
));
|
||||
}
|
||||
|
||||
protected function buildHeraldAdapter(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
@ -1547,6 +1729,10 @@ final class DifferentialTransactionEditor
|
|||
$paths[] = $path_prefix.'/'.$changeset->getFilename();
|
||||
}
|
||||
|
||||
// Save the affected paths; we'll use them later to query Owners. This
|
||||
// uses the un-expanded paths.
|
||||
$this->affectedPaths = $paths;
|
||||
|
||||
// Mark this as also touching all parent paths, so you can see all pending
|
||||
// changes to any file within a directory.
|
||||
$all_paths = array();
|
||||
|
|
|
@ -22,7 +22,7 @@ final class DifferentialReviewersAddBlockingReviewersHeraldAction
|
|||
}
|
||||
|
||||
protected function getDatasource() {
|
||||
return new PhabricatorMetaMTAMailableDatasource();
|
||||
return new DiffusionAuditorDatasource();
|
||||
}
|
||||
|
||||
public function renderActionDescription($value) {
|
||||
|
|
|
@ -22,7 +22,7 @@ final class DifferentialReviewersAddReviewersHeraldAction
|
|||
}
|
||||
|
||||
protected function getDatasource() {
|
||||
return new PhabricatorMetaMTAMailableDatasource();
|
||||
return new DiffusionAuditorDatasource();
|
||||
}
|
||||
|
||||
public function renderActionDescription($value) {
|
||||
|
|
|
@ -69,6 +69,7 @@ abstract class DifferentialReviewersHeraldAction
|
|||
$allowed_types = array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
||||
);
|
||||
|
||||
$targets = $this->loadStandardTargets($phids, $allowed_types, $current);
|
||||
|
|
|
@ -91,18 +91,6 @@ final class DifferentialRevisionQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter results to revisions with comments authored by the given PHIDs.
|
||||
*
|
||||
* @param array List of PHIDs of authors
|
||||
* @return this
|
||||
* @task config
|
||||
*/
|
||||
public function withDraftRepliesByAuthors(array $author_phids) {
|
||||
$this->draftAuthors = $author_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter results to revisions which CC one of the listed people. Calling this
|
||||
* function will clear anything set by previous calls to @{method:withCCs}.
|
||||
|
@ -239,27 +227,6 @@ final class DifferentialRevisionQuery
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set result ordering. Provide a class constant, such as
|
||||
* `DifferentialRevisionQuery::ORDER_CREATED`.
|
||||
*
|
||||
* @task config
|
||||
*/
|
||||
public function setOrder($order_constant) {
|
||||
switch ($order_constant) {
|
||||
case self::ORDER_CREATED:
|
||||
$this->setOrderVector(array('id'));
|
||||
break;
|
||||
case self::ORDER_MODIFIED:
|
||||
$this->setOrderVector(array('updated', 'id'));
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unknown order "%s".', $order_constant));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether or not the query will load and attach relationships.
|
||||
|
@ -371,6 +338,11 @@ final class DifferentialRevisionQuery
|
|||
/* -( Query Execution )---------------------------------------------------- */
|
||||
|
||||
|
||||
public function newResultObject() {
|
||||
return new DifferentialRevision();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the query as configured, returning matching
|
||||
* @{class:DifferentialRevision} objects.
|
||||
|
@ -379,11 +351,9 @@ final class DifferentialRevisionQuery
|
|||
* @task exec
|
||||
*/
|
||||
protected function loadPage() {
|
||||
$table = new DifferentialRevision();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$data = $this->loadData();
|
||||
|
||||
$table = $this->newResultObject();
|
||||
return $table->loadAllFromArray($data);
|
||||
}
|
||||
|
||||
|
@ -519,7 +489,7 @@ final class DifferentialRevisionQuery
|
|||
}
|
||||
|
||||
private function loadData() {
|
||||
$table = new DifferentialRevision();
|
||||
$table = $this->newResultObject();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$selects = array();
|
||||
|
@ -531,25 +501,17 @@ final class DifferentialRevisionQuery
|
|||
$basic_authors = $this->authors;
|
||||
$basic_reviewers = $this->reviewers;
|
||||
|
||||
$authority_projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withMemberPHIDs($this->responsibles)
|
||||
->execute();
|
||||
$authority_phids = mpull($authority_projects, 'getPHID');
|
||||
|
||||
try {
|
||||
// Build the query where the responsible users are authors.
|
||||
$this->authors = array_merge($basic_authors, $this->responsibles);
|
||||
|
||||
$this->reviewers = $basic_reviewers;
|
||||
$selects[] = $this->buildSelectStatement($conn_r);
|
||||
|
||||
// Build the query where the responsible users are reviewers, or
|
||||
// projects they are members of are reviewers.
|
||||
$this->authors = $basic_authors;
|
||||
$this->reviewers = array_merge(
|
||||
$basic_reviewers,
|
||||
$this->responsibles,
|
||||
$authority_phids);
|
||||
$this->reviewers = array_merge($basic_reviewers, $this->responsibles);
|
||||
$selects[] = $this->buildSelectStatement($conn_r);
|
||||
|
||||
// Put everything back like it was.
|
||||
|
@ -591,7 +553,7 @@ final class DifferentialRevisionQuery
|
|||
|
||||
$joins = $this->buildJoinsClause($conn_r);
|
||||
$where = $this->buildWhereClause($conn_r);
|
||||
$group_by = $this->buildGroupByClause($conn_r);
|
||||
$group_by = $this->buildGroupClause($conn_r);
|
||||
$having = $this->buildHavingClause($conn_r);
|
||||
|
||||
$this->buildingGlobalOrder = false;
|
||||
|
@ -835,19 +797,37 @@ final class DifferentialRevisionQuery
|
|||
/**
|
||||
* @task internal
|
||||
*/
|
||||
private function buildGroupByClause($conn_r) {
|
||||
protected function shouldGroupQueryResultRows() {
|
||||
|
||||
$join_triggers = array_merge(
|
||||
$this->pathIDs,
|
||||
$this->ccs,
|
||||
$this->reviewers);
|
||||
|
||||
$needs_distinct = (count($join_triggers) > 1);
|
||||
|
||||
if ($needs_distinct) {
|
||||
return 'GROUP BY r.id';
|
||||
} else {
|
||||
return '';
|
||||
if (count($join_triggers) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldGroupQueryResultRows();
|
||||
}
|
||||
|
||||
public function getBuiltinOrders() {
|
||||
$orders = parent::getBuiltinOrders() + array(
|
||||
'updated' => array(
|
||||
'vector' => array('updated', 'id'),
|
||||
'name' => pht('Date Updated (Latest First)'),
|
||||
'aliases' => array(self::ORDER_MODIFIED),
|
||||
),
|
||||
'outdated' => array(
|
||||
'vector' => array('-updated', '-id'),
|
||||
'name' => pht('Date Updated (Oldest First)'),
|
||||
),
|
||||
);
|
||||
|
||||
// Alias the "newest" builtin to the historical key for it.
|
||||
$orders['newest']['aliases'][] = self::ORDER_CREATED;
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
protected function getDefaultOrderVector() {
|
||||
|
@ -1053,50 +1033,6 @@ final class DifferentialRevisionQuery
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static function splitResponsible(array $revisions, array $user_phids) {
|
||||
$blocking = array();
|
||||
$active = array();
|
||||
$waiting = array();
|
||||
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
|
||||
|
||||
// Bucket revisions into $blocking (revisions where you are blocking
|
||||
// others), $active (revisions you need to do something about) and $waiting
|
||||
// (revisions you're waiting on someone else to do something about).
|
||||
foreach ($revisions as $revision) {
|
||||
$needs_review = ($revision->getStatus() == $status_review);
|
||||
$filter_is_author = in_array($revision->getAuthorPHID(), $user_phids);
|
||||
if (!$revision->getReviewers()) {
|
||||
$needs_review = false;
|
||||
$author_is_reviewer = false;
|
||||
} else {
|
||||
$author_is_reviewer = in_array(
|
||||
$revision->getAuthorPHID(),
|
||||
$revision->getReviewers());
|
||||
}
|
||||
|
||||
// If exactly one of "needs review" and "the user is the author" is
|
||||
// true, the user needs to act on it. Otherwise, they're waiting on
|
||||
// it.
|
||||
if ($needs_review ^ $filter_is_author) {
|
||||
if ($needs_review) {
|
||||
array_unshift($blocking, $revision);
|
||||
} else {
|
||||
$active[] = $revision;
|
||||
}
|
||||
// User is author **and** reviewer. An exotic but configurable workflow.
|
||||
// User needs to act on it double.
|
||||
} else if ($needs_review && $author_is_reviewer) {
|
||||
array_unshift($blocking, $revision);
|
||||
$active[] = $revision;
|
||||
} else {
|
||||
$waiting[] = $revision;
|
||||
}
|
||||
}
|
||||
|
||||
return array($blocking, $active, $waiting);
|
||||
}
|
||||
|
||||
private function loadReviewerAuthority(
|
||||
array $revisions,
|
||||
array $edges,
|
||||
|
@ -1105,9 +1041,13 @@ final class DifferentialRevisionQuery
|
|||
$revision_map = mpull($revisions, null, 'getPHID');
|
||||
$viewer_phid = $this->getViewer()->getPHID();
|
||||
|
||||
// Find all the project reviewers which the user may have authority over.
|
||||
// Find all the project/package reviewers which the user may have authority
|
||||
// over.
|
||||
$project_phids = array();
|
||||
$package_phids = array();
|
||||
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
|
||||
$package_type = PhabricatorOwnersPackagePHIDType::TYPECONST;
|
||||
|
||||
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
||||
foreach ($edges as $src => $types) {
|
||||
if (!$allow_self) {
|
||||
|
@ -1121,14 +1061,20 @@ final class DifferentialRevisionQuery
|
|||
}
|
||||
$edge_data = idx($types, $edge_type, array());
|
||||
foreach ($edge_data as $dst => $data) {
|
||||
if (phid_get_type($dst) == $project_type) {
|
||||
$phid_type = phid_get_type($dst);
|
||||
if ($phid_type == $project_type) {
|
||||
$project_phids[] = $dst;
|
||||
}
|
||||
if ($phid_type == $package_type) {
|
||||
$package_phids[] = $dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, figure out which of these projects the viewer is actually a
|
||||
// member of.
|
||||
// The viewer has authority over themselves.
|
||||
$user_authority = array_fuse(array($viewer_phid));
|
||||
|
||||
// And over any projects they are a member of.
|
||||
$project_authority = array();
|
||||
if ($project_phids) {
|
||||
$project_authority = id(new PhabricatorProjectQuery())
|
||||
|
@ -1137,12 +1083,22 @@ final class DifferentialRevisionQuery
|
|||
->withMemberPHIDs(array($viewer_phid))
|
||||
->execute();
|
||||
$project_authority = mpull($project_authority, 'getPHID');
|
||||
$project_authority = array_fuse($project_authority);
|
||||
}
|
||||
|
||||
// Finally, the viewer has authority over themselves.
|
||||
return array(
|
||||
$viewer_phid => true,
|
||||
) + array_fuse($project_authority);
|
||||
// And over any packages they own.
|
||||
$package_authority = array();
|
||||
if ($package_phids) {
|
||||
$package_authority = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($package_phids)
|
||||
->withAuthorityPHIDs(array($viewer_phid))
|
||||
->execute();
|
||||
$package_authority = mpull($package_authority, 'getPHID');
|
||||
$package_authority = array_fuse($package_authority);
|
||||
}
|
||||
|
||||
return $user_authority + $project_authority + $package_authority;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionRequiredActionResultBucket
|
||||
extends DifferentialRevisionResultBucket {
|
||||
|
||||
const BUCKETKEY = 'action';
|
||||
|
||||
const KEY_MUSTREVIEW = 'must-review';
|
||||
const KEY_SHOULDREVIEW = 'should-review';
|
||||
|
||||
private $objects;
|
||||
|
||||
public function getResultBucketName() {
|
||||
return pht('Bucket by Required Action');
|
||||
}
|
||||
|
||||
protected function buildResultGroups(
|
||||
PhabricatorSavedQuery $query,
|
||||
array $objects) {
|
||||
|
||||
$this->objects = $objects;
|
||||
|
||||
$phids = $query->getEvaluatedParameter('responsiblePHIDs', array());
|
||||
if (!$phids) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not bucket results by required action without '.
|
||||
'specifying "Responsible Users".'));
|
||||
}
|
||||
$phids = array_fuse($phids);
|
||||
|
||||
$groups = array();
|
||||
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Must Review'))
|
||||
->setKey(self::KEY_MUSTREVIEW)
|
||||
->setNoDataString(pht('No revisions are blocked on your review.'))
|
||||
->setObjects($this->filterMustReview($phids));
|
||||
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Ready to Review'))
|
||||
->setKey(self::KEY_SHOULDREVIEW)
|
||||
->setNoDataString(pht('No revisions are waiting on you to review them.'))
|
||||
->setObjects($this->filterShouldReview($phids));
|
||||
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Ready to Land'))
|
||||
->setNoDataString(pht('No revisions are ready to land.'))
|
||||
->setObjects($this->filterShouldLand($phids));
|
||||
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Ready to Update'))
|
||||
->setNoDataString(pht('No revisions are waiting for updates.'))
|
||||
->setObjects($this->filterShouldUpdate($phids));
|
||||
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Waiting on Review'))
|
||||
->setNoDataString(pht('None of your revisions are waiting on review.'))
|
||||
->setObjects($this->filterWaitingForReview($phids));
|
||||
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Waiting on Authors'))
|
||||
->setNoDataString(pht('No revisions are waiting on author action.'))
|
||||
->setObjects($this->filterWaitingOnAuthors($phids));
|
||||
|
||||
// Because you can apply these buckets to queries which include revisions
|
||||
// that have been closed, add an "Other" bucket if we still have stuff
|
||||
// that didn't get filtered into any of the previous buckets.
|
||||
if ($this->objects) {
|
||||
$groups[] = $this->newGroup()
|
||||
->setName(pht('Other Revisions'))
|
||||
->setObjects($this->objects);
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
private function filterMustReview(array $phids) {
|
||||
$blocking = array(
|
||||
DifferentialReviewerStatus::STATUS_BLOCKING,
|
||||
DifferentialReviewerStatus::STATUS_REJECTED,
|
||||
DifferentialReviewerStatus::STATUS_REJECTED_OLDER,
|
||||
);
|
||||
$blocking = array_fuse($blocking);
|
||||
|
||||
$objects = $this->getRevisionsUnderReview($this->objects, $phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $key => $object) {
|
||||
if (!$this->hasReviewersWithStatus($object, $phids, $blocking)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
unset($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function filterShouldReview(array $phids) {
|
||||
$reviewing = array(
|
||||
DifferentialReviewerStatus::STATUS_ADDED,
|
||||
DifferentialReviewerStatus::STATUS_COMMENTED,
|
||||
);
|
||||
$reviewing = array_fuse($reviewing);
|
||||
|
||||
$objects = $this->getRevisionsUnderReview($this->objects, $phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $key => $object) {
|
||||
if (!$this->hasReviewersWithStatus($object, $phids, $reviewing)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
unset($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function filterShouldLand(array $phids) {
|
||||
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
|
||||
|
||||
$objects = $this->getRevisionsAuthored($this->objects, $phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $key => $object) {
|
||||
if ($object->getStatus() != $status_accepted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
unset($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function filterShouldUpdate(array $phids) {
|
||||
$statuses = array(
|
||||
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
|
||||
ArcanistDifferentialRevisionStatus::CHANGES_PLANNED,
|
||||
ArcanistDifferentialRevisionStatus::IN_PREPARATION,
|
||||
);
|
||||
$statuses = array_fuse($statuses);
|
||||
|
||||
$objects = $this->getRevisionsAuthored($this->objects, $phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $key => $object) {
|
||||
if (empty($statuses[$object->getStatus()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
unset($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function filterWaitingForReview(array $phids) {
|
||||
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
|
||||
|
||||
$objects = $this->getRevisionsAuthored($this->objects, $phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $key => $object) {
|
||||
if ($object->getStatus() != $status_review) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
unset($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function filterWaitingOnAuthors(array $phids) {
|
||||
$statuses = array(
|
||||
ArcanistDifferentialRevisionStatus::ACCEPTED,
|
||||
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
|
||||
ArcanistDifferentialRevisionStatus::CHANGES_PLANNED,
|
||||
ArcanistDifferentialRevisionStatus::IN_PREPARATION,
|
||||
);
|
||||
$statuses = array_fuse($statuses);
|
||||
|
||||
$objects = $this->getRevisionsNotAuthored($this->objects, $phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $key => $object) {
|
||||
if (empty($statuses[$object->getStatus()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
unset($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
abstract class DifferentialRevisionResultBucket
|
||||
extends PhabricatorSearchResultBucket {
|
||||
|
||||
public static function getAllResultBuckets() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getResultBucketKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
protected function getRevisionsUnderReview(array $objects, array $phids) {
|
||||
$results = array();
|
||||
|
||||
$objects = $this->getRevisionsNotAuthored($objects, $phids);
|
||||
|
||||
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
|
||||
foreach ($objects as $key => $object) {
|
||||
if ($object->getStatus() != $status_review) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = $object;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function getRevisionsAuthored(array $objects, array $phids) {
|
||||
$results = array();
|
||||
|
||||
foreach ($objects as $key => $object) {
|
||||
if (isset($phids[$object->getAuthorPHID()])) {
|
||||
$results[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function getRevisionsNotAuthored(array $objects, array $phids) {
|
||||
$results = array();
|
||||
|
||||
foreach ($objects as $key => $object) {
|
||||
if (empty($phids[$object->getAuthorPHID()])) {
|
||||
$results[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function hasReviewersWithStatus(
|
||||
DifferentialRevision $revision,
|
||||
array $phids,
|
||||
array $statuses) {
|
||||
|
||||
foreach ($revision->getReviewerStatus() as $reviewer) {
|
||||
$reviewer_phid = $reviewer->getReviewerPHID();
|
||||
if (empty($phids[$reviewer_phid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$status = $reviewer->getStatus();
|
||||
if (empty($statuses[$status])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -11,203 +11,80 @@ final class DifferentialRevisionSearchEngine
|
|||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
protected function newResultBuckets() {
|
||||
return DifferentialRevisionResultBucket::getAllResultBuckets();
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return id(new DifferentialRevisionQuery())
|
||||
->needFlags(true)
|
||||
->needDrafts(true)
|
||||
->needRelationships(true);
|
||||
->needRelationships(true)
|
||||
->needReviewerStatus(true);
|
||||
}
|
||||
|
||||
public function getPageSize(PhabricatorSavedQuery $saved) {
|
||||
if ($saved->getQueryKey() == 'active') {
|
||||
return 0xFFFF;
|
||||
}
|
||||
return parent::getPageSize($saved);
|
||||
}
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
||||
$saved = new PhabricatorSavedQuery();
|
||||
|
||||
$saved->setParameter(
|
||||
'responsiblePHIDs',
|
||||
$this->readUsersFromRequest($request, 'responsibles'));
|
||||
|
||||
$saved->setParameter(
|
||||
'authorPHIDs',
|
||||
$this->readUsersFromRequest($request, 'authors'));
|
||||
|
||||
$saved->setParameter(
|
||||
'reviewerPHIDs',
|
||||
$this->readUsersFromRequest(
|
||||
$request,
|
||||
'reviewers',
|
||||
array(
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
)));
|
||||
|
||||
$saved->setParameter(
|
||||
'subscriberPHIDs',
|
||||
$this->readSubscribersFromRequest($request, 'subscribers'));
|
||||
|
||||
$saved->setParameter(
|
||||
'repositoryPHIDs',
|
||||
$request->getArr('repositories'));
|
||||
|
||||
$saved->setParameter(
|
||||
'projects',
|
||||
$this->readProjectsFromRequest($request, 'projects'));
|
||||
|
||||
$saved->setParameter(
|
||||
'draft',
|
||||
$request->getBool('draft'));
|
||||
|
||||
$saved->setParameter(
|
||||
'order',
|
||||
$request->getStr('order'));
|
||||
|
||||
$saved->setParameter(
|
||||
'status',
|
||||
$request->getStr('status'));
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
||||
$query = id(new DifferentialRevisionQuery())
|
||||
->needFlags(true)
|
||||
->needDrafts(true)
|
||||
->needRelationships(true);
|
||||
|
||||
$user_datasource = id(new PhabricatorPeopleUserFunctionDatasource())
|
||||
->setViewer($this->requireViewer());
|
||||
|
||||
$responsible_phids = $saved->getParameter('responsiblePHIDs', array());
|
||||
$responsible_phids = $user_datasource->evaluateTokens($responsible_phids);
|
||||
if ($responsible_phids) {
|
||||
$query->withResponsibleUsers($responsible_phids);
|
||||
if ($map['responsiblePHIDs']) {
|
||||
$query->withResponsibleUsers($map['responsiblePHIDs']);
|
||||
}
|
||||
|
||||
$this->setQueryProjects($query, $saved);
|
||||
|
||||
$author_phids = $saved->getParameter('authorPHIDs', array());
|
||||
$author_phids = $user_datasource->evaluateTokens($author_phids);
|
||||
if ($author_phids) {
|
||||
$query->withAuthors($author_phids);
|
||||
if ($map['authorPHIDs']) {
|
||||
$query->withAuthors($map['authorPHIDs']);
|
||||
}
|
||||
|
||||
$reviewer_phids = $saved->getParameter('reviewerPHIDs', array());
|
||||
if ($reviewer_phids) {
|
||||
$query->withReviewers($reviewer_phids);
|
||||
if ($map['reviewerPHIDs']) {
|
||||
$query->withReviewers($map['reviewerPHIDs']);
|
||||
}
|
||||
|
||||
$sub_datasource = id(new PhabricatorMetaMTAMailableFunctionDatasource())
|
||||
->setViewer($this->requireViewer());
|
||||
$subscriber_phids = $saved->getParameter('subscriberPHIDs', array());
|
||||
$subscriber_phids = $sub_datasource->evaluateTokens($subscriber_phids);
|
||||
if ($subscriber_phids) {
|
||||
$query->withCCs($subscriber_phids);
|
||||
if ($map['repositoryPHIDs']) {
|
||||
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
|
||||
}
|
||||
|
||||
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
|
||||
if ($repository_phids) {
|
||||
$query->withRepositoryPHIDs($repository_phids);
|
||||
}
|
||||
|
||||
$draft = $saved->getParameter('draft', false);
|
||||
if ($draft && $this->requireViewer()->isLoggedIn()) {
|
||||
$query->withDraftRepliesByAuthors(
|
||||
array($this->requireViewer()->getPHID()));
|
||||
}
|
||||
|
||||
$status = $saved->getParameter('status');
|
||||
if (idx($this->getStatusOptions(), $status)) {
|
||||
$query->withStatus($status);
|
||||
}
|
||||
|
||||
$order = $saved->getParameter('order');
|
||||
if (idx($this->getOrderOptions(), $order)) {
|
||||
$query->setOrder($order);
|
||||
} else {
|
||||
$query->setOrder(DifferentialRevisionQuery::ORDER_CREATED);
|
||||
if ($map['status']) {
|
||||
$query->withStatus($map['status']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function buildSearchForm(
|
||||
AphrontFormView $form,
|
||||
PhabricatorSavedQuery $saved) {
|
||||
|
||||
$responsible_phids = $saved->getParameter('responsiblePHIDs', array());
|
||||
$author_phids = $saved->getParameter('authorPHIDs', array());
|
||||
$reviewer_phids = $saved->getParameter('reviewerPHIDs', array());
|
||||
$subscriber_phids = $saved->getParameter('subscriberPHIDs', array());
|
||||
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
|
||||
$only_draft = $saved->getParameter('draft', false);
|
||||
$projects = $saved->getParameter('projects', array());
|
||||
|
||||
$form
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Responsible Users'))
|
||||
->setName('responsibles')
|
||||
->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
|
||||
->setValue($responsible_phids))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Authors'))
|
||||
->setName('authors')
|
||||
->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
|
||||
->setValue($author_phids))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Reviewers'))
|
||||
->setName('reviewers')
|
||||
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
||||
->setValue($reviewer_phids))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Subscribers'))
|
||||
->setName('subscribers')
|
||||
->setDatasource(new PhabricatorMetaMTAMailableFunctionDatasource())
|
||||
->setValue($subscriber_phids))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Repositories'))
|
||||
->setName('repositories')
|
||||
->setDatasource(new DiffusionRepositoryDatasource())
|
||||
->setValue($repository_phids))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setDatasource(new PhabricatorProjectLogicalDatasource())
|
||||
->setValue($projects))
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel(pht('Status'))
|
||||
->setName('status')
|
||||
->setOptions($this->getStatusOptions())
|
||||
->setValue($saved->getParameter('status')));
|
||||
|
||||
if ($this->requireViewer()->isLoggedIn()) {
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormCheckboxControl())
|
||||
->addCheckbox(
|
||||
'draft',
|
||||
1,
|
||||
pht('Show only revisions with a draft comment.'),
|
||||
$only_draft));
|
||||
}
|
||||
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel(pht('Order'))
|
||||
->setName('order')
|
||||
->setOptions($this->getOrderOptions())
|
||||
->setValue($saved->getParameter('order')));
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
->setLabel(pht('Responsible Users'))
|
||||
->setKey('responsiblePHIDs')
|
||||
->setAliases(array('responsiblePHID', 'responsibles', 'responsible'))
|
||||
->setDatasource(new DifferentialResponsibleDatasource())
|
||||
->setDescription(
|
||||
pht('Find revisions that a given user is responsible for.')),
|
||||
id(new PhabricatorUsersSearchField())
|
||||
->setLabel(pht('Authors'))
|
||||
->setKey('authorPHIDs')
|
||||
->setAliases(array('author', 'authors', 'authorPHID'))
|
||||
->setDescription(
|
||||
pht('Find revisions with specific authors.')),
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
->setLabel(pht('Reviewers'))
|
||||
->setKey('reviewerPHIDs')
|
||||
->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
|
||||
->setDatasource(new DiffusionAuditorFunctionDatasource())
|
||||
->setDescription(
|
||||
pht('Find revisions with specific reviewers.')),
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
->setLabel(pht('Repositories'))
|
||||
->setKey('repositoryPHIDs')
|
||||
->setAliases(array('repository', 'repositories', 'repositoryPHID'))
|
||||
->setDatasource(new DiffusionRepositoryDatasource())
|
||||
->setDescription(
|
||||
pht('Find revisions from specific repositories.')),
|
||||
id(new PhabricatorSearchSelectField())
|
||||
->setLabel(pht('Status'))
|
||||
->setKey('status')
|
||||
->setOptions($this->getStatusOptions())
|
||||
->setDescription(
|
||||
pht('Find revisions with particular statuses.')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
|
@ -235,9 +112,12 @@ final class DifferentialRevisionSearchEngine
|
|||
|
||||
switch ($query_key) {
|
||||
case 'active':
|
||||
$bucket_key = DifferentialRevisionRequiredActionResultBucket::BUCKETKEY;
|
||||
|
||||
return $query
|
||||
->setParameter('responsiblePHIDs', array($viewer->getPHID()))
|
||||
->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN);
|
||||
->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN)
|
||||
->setParameter('bucket', $bucket_key);
|
||||
case 'authored':
|
||||
return $query
|
||||
->setParameter('authorPHIDs', array($viewer->getPHID()));
|
||||
|
@ -260,13 +140,6 @@ final class DifferentialRevisionSearchEngine
|
|||
);
|
||||
}
|
||||
|
||||
private function getOrderOptions() {
|
||||
return array(
|
||||
DifferentialRevisionQuery::ORDER_CREATED => pht('Created'),
|
||||
DifferentialRevisionQuery::ORDER_MODIFIED => pht('Updated'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $revisions,
|
||||
PhabricatorSavedQuery $query,
|
||||
|
@ -278,35 +151,26 @@ final class DifferentialRevisionSearchEngine
|
|||
->setUser($viewer)
|
||||
->setNoBox($this->isPanelContext());
|
||||
|
||||
$bucket = $this->getResultBucket($query);
|
||||
|
||||
$unlanded = $this->loadUnlandedDependencies($revisions);
|
||||
|
||||
$views = array();
|
||||
if ($query->getQueryKey() == 'active') {
|
||||
$split = DifferentialRevisionQuery::splitResponsible(
|
||||
$revisions,
|
||||
$query->getParameter('responsiblePHIDs'));
|
||||
list($blocking, $active, $waiting) = $split;
|
||||
if ($bucket) {
|
||||
$bucket->setViewer($viewer);
|
||||
|
||||
$views[] = id(clone $template)
|
||||
->setHeader(pht('Blocking Others'))
|
||||
->setNoDataString(
|
||||
pht('No revisions are blocked on your action.'))
|
||||
->setHighlightAge(true)
|
||||
->setRevisions($blocking)
|
||||
->setHandles(array());
|
||||
try {
|
||||
$groups = $bucket->newResultGroups($query, $revisions);
|
||||
|
||||
$views[] = id(clone $template)
|
||||
->setHeader(pht('Action Required'))
|
||||
->setNoDataString(
|
||||
pht('No revisions require your action.'))
|
||||
->setHighlightAge(true)
|
||||
->setRevisions($active)
|
||||
->setHandles(array());
|
||||
|
||||
$views[] = id(clone $template)
|
||||
->setHeader(pht('Waiting on Others'))
|
||||
->setNoDataString(
|
||||
pht('You have no revisions waiting on others.'))
|
||||
->setRevisions($waiting)
|
||||
->setHandles(array());
|
||||
foreach ($groups as $group) {
|
||||
$views[] = id(clone $template)
|
||||
->setHeader($group->getName())
|
||||
->setNoDataString($group->getNoDataString())
|
||||
->setRevisions($group->getObjects());
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$this->addError($ex->getMessage());
|
||||
}
|
||||
} else {
|
||||
$views[] = id(clone $template)
|
||||
->setRevisions($revisions)
|
||||
|
@ -325,6 +189,7 @@ final class DifferentialRevisionSearchEngine
|
|||
|
||||
foreach ($views as $view) {
|
||||
$view->setHandles($handles);
|
||||
$view->setUnlandedDependencies($unlanded);
|
||||
}
|
||||
|
||||
if (count($views) == 1) {
|
||||
|
@ -361,4 +226,56 @@ final class DifferentialRevisionSearchEngine
|
|||
return $view;
|
||||
}
|
||||
|
||||
private function loadUnlandedDependencies(array $revisions) {
|
||||
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
|
||||
|
||||
$phids = array();
|
||||
foreach ($revisions as $revision) {
|
||||
if ($revision->getStatus() != $status_accepted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phids[] = $revision->getPHID();
|
||||
}
|
||||
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($phids)
|
||||
->withEdgeTypes(
|
||||
array(
|
||||
DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST,
|
||||
));
|
||||
|
||||
$query->execute();
|
||||
|
||||
$revision_phids = $query->getDestinationPHIDs();
|
||||
if (!$revision_phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$blocking_revisions = id(new DifferentialRevisionQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($revision_phids)
|
||||
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
|
||||
->execute();
|
||||
$blocking_revisions = mpull($blocking_revisions, null, 'getPHID');
|
||||
|
||||
$result = array();
|
||||
foreach ($revisions as $revision) {
|
||||
$revision_phid = $revision->getPHID();
|
||||
$blocking_phids = $query->getDestinationPHIDs(array($revision_phid));
|
||||
$blocking = array_select_keys($blocking_revisions, $blocking_phids);
|
||||
if ($blocking) {
|
||||
$result[$revision_phid] = $blocking;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialBlockingReviewerDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Blocking Reviewers');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user, project, or package name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorPeopleDatasource(),
|
||||
new PhabricatorProjectDatasource(),
|
||||
new PhabricatorOwnersPackageDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDatasourceFunctions() {
|
||||
return array(
|
||||
'blocking' => array(
|
||||
'name' => pht('Blocking: ...'),
|
||||
'arguments' => pht('reviewer'),
|
||||
'summary' => pht('Select a blocking reviewer.'),
|
||||
'description' => pht(
|
||||
"This function allows you to add a reviewer as a blocking ".
|
||||
"reviewer. For example, this will add `%s` as a blocking reviewer: ".
|
||||
"\n\n%s\n\n",
|
||||
'alincoln',
|
||||
'> blocking(alincoln)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
protected function didLoadResults(array $results) {
|
||||
foreach ($results as $result) {
|
||||
$result
|
||||
->setColor('red')
|
||||
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
|
||||
->setIcon('fa-asterisk')
|
||||
->setPHID('blocking('.$result->getPHID().')')
|
||||
->setDisplayName(pht('Blocking: %s', $result->getDisplayName()))
|
||||
->setName($result->getName().' blocking');
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function evaluateFunction($function, array $argv_list) {
|
||||
$phids = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$phids[] = head($argv);
|
||||
}
|
||||
|
||||
$phids = $this->resolvePHIDs($phids);
|
||||
|
||||
$results = array();
|
||||
foreach ($phids as $phid) {
|
||||
$results[] = array(
|
||||
'type' => DifferentialReviewerStatus::STATUS_BLOCKING,
|
||||
'phid' => $phid,
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function renderFunctionTokens($function, array $argv_list) {
|
||||
$phids = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$phids[] = head($argv);
|
||||
}
|
||||
|
||||
$phids = $this->resolvePHIDs($phids);
|
||||
|
||||
$tokens = $this->renderTokens($phids);
|
||||
foreach ($tokens as $token) {
|
||||
$token->setColor(null);
|
||||
if ($token->isInvalid()) {
|
||||
$token
|
||||
->setValue(pht('Blocking: Invalid Reviewer'));
|
||||
} else {
|
||||
$token
|
||||
->setIcon('fa-asterisk')
|
||||
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
|
||||
->setColor('red')
|
||||
->setKey('blocking('.$token->getKey().')')
|
||||
->setValue(pht('Blocking: %s', $token->getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolvePHIDs(array $phids) {
|
||||
$usernames = array();
|
||||
foreach ($phids as $key => $phid) {
|
||||
if (phid_get_type($phid) != PhabricatorPeopleUserPHIDType::TYPECONST) {
|
||||
$usernames[$key] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($usernames) {
|
||||
$users = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withUsernames($usernames)
|
||||
->execute();
|
||||
$users = mpull($users, null, 'getUsername');
|
||||
foreach ($usernames as $key => $username) {
|
||||
$user = idx($users, $username);
|
||||
if ($user) {
|
||||
$phids[$key] = $user->getPHID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialExactUserFunctionDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Users');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type exact(<user>)...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorPeopleDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDatasourceFunctions() {
|
||||
return array(
|
||||
'exact' => array(
|
||||
'name' => pht('Exact: ...'),
|
||||
'arguments' => pht('username'),
|
||||
'summary' => pht('Find results matching users exactly.'),
|
||||
'description' => pht(
|
||||
"This function allows you to find results associated only with ".
|
||||
"a user, exactly, and not any of their projects or packages. For ".
|
||||
"example, this will find results associated with only `%s`:".
|
||||
"\n\n%s\n\n",
|
||||
'alincoln',
|
||||
'> exact(alincoln)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function didLoadResults(array $results) {
|
||||
foreach ($results as $result) {
|
||||
$result
|
||||
->setColor(null)
|
||||
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
|
||||
->setIcon('fa-asterisk')
|
||||
->setPHID('exact('.$result->getPHID().')')
|
||||
->setDisplayName(pht('Exact User: %s', $result->getDisplayName()))
|
||||
->setName($result->getName().' exact');
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function evaluateFunction($function, array $argv_list) {
|
||||
$phids = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$phids[] = head($argv);
|
||||
}
|
||||
|
||||
return $this->resolvePHIDs($phids);
|
||||
}
|
||||
|
||||
public function renderFunctionTokens($function, array $argv_list) {
|
||||
$phids = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$phids[] = head($argv);
|
||||
}
|
||||
|
||||
$phids = $this->resolvePHIDs($phids);
|
||||
|
||||
$tokens = $this->renderTokens($phids);
|
||||
foreach ($tokens as $token) {
|
||||
$token->setColor(null);
|
||||
if ($token->isInvalid()) {
|
||||
$token
|
||||
->setValue(pht('Exact User: Invalid User'));
|
||||
} else {
|
||||
$token
|
||||
->setIcon('fa-asterisk')
|
||||
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
|
||||
->setKey('exact('.$token->getKey().')')
|
||||
->setValue(pht('Exact User: %s', $token->getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolvePHIDs(array $phids) {
|
||||
$usernames = array();
|
||||
foreach ($phids as $key => $phid) {
|
||||
if (phid_get_type($phid) != PhabricatorPeopleUserPHIDType::TYPECONST) {
|
||||
$usernames[$key] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($usernames) {
|
||||
$users = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withUsernames($usernames)
|
||||
->execute();
|
||||
$users = mpull($users, null, 'getUsername');
|
||||
foreach ($usernames as $key => $username) {
|
||||
$user = idx($users, $username);
|
||||
if ($user) {
|
||||
$phids[$key] = $user->getPHID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialResponsibleDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Responsible Users');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user, project, or package name, or function...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new DifferentialResponsibleUserDatasource(),
|
||||
new DifferentialResponsibleViewerFunctionDatasource(),
|
||||
new DifferentialExactUserFunctionDatasource(),
|
||||
new PhabricatorProjectDatasource(),
|
||||
new PhabricatorOwnersPackageDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
public static function expandResponsibleUsers(
|
||||
PhabricatorUser $viewer,
|
||||
array $values) {
|
||||
|
||||
$phids = array();
|
||||
foreach ($values as $value) {
|
||||
if (phid_get_type($value) == PhabricatorPeopleUserPHIDType::TYPECONST) {
|
||||
$phids[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$phids) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs($phids)
|
||||
->execute();
|
||||
foreach ($projects as $project) {
|
||||
$phids[] = $project->getPHID();
|
||||
$values[] = $project->getPHID();
|
||||
}
|
||||
|
||||
$packages = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($viewer)
|
||||
->withOwnerPHIDs($phids)
|
||||
->execute();
|
||||
foreach ($packages as $package) {
|
||||
$values[] = $package->getPHID();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialResponsibleUserDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Users');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorPeopleDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function evaluateValues(array $values) {
|
||||
return DifferentialResponsibleDatasource::expandResponsibleUsers(
|
||||
$this->getViewer(),
|
||||
$values);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialResponsibleViewerFunctionDatasource
|
||||
extends PhabricatorTypeaheadDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Viewer');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type viewer()...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorPeopleApplication';
|
||||
}
|
||||
|
||||
public function getDatasourceFunctions() {
|
||||
return array(
|
||||
'viewer' => array(
|
||||
'name' => pht('Current Viewer'),
|
||||
'summary' => pht('Use the current viewing user.'),
|
||||
'description' => pht(
|
||||
'Show revisions the current viewer is responsible for. This '.
|
||||
'function inclues revisions the viewer is responsible for through '.
|
||||
'membership in projects and packages.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function loadResults() {
|
||||
if ($this->getViewer()->getPHID()) {
|
||||
$results = array($this->renderViewerFunctionToken());
|
||||
} else {
|
||||
$results = array();
|
||||
}
|
||||
|
||||
return $this->filterResultsAgainstTokens($results);
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction($function) {
|
||||
if (!$this->getViewer()->getPHID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canEvaluateFunction($function);
|
||||
}
|
||||
|
||||
protected function evaluateFunction($function, array $argv_list) {
|
||||
$results = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$results[] = $this->getViewer()->getPHID();
|
||||
}
|
||||
|
||||
return DifferentialResponsibleDatasource::expandResponsibleUsers(
|
||||
$this->getViewer(),
|
||||
$results);
|
||||
}
|
||||
|
||||
public function renderFunctionTokens($function, array $argv_list) {
|
||||
$tokens = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$tokens[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
|
||||
$this->renderViewerFunctionToken());
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function renderViewerFunctionToken() {
|
||||
return $this->newFunctionResult()
|
||||
->setName(pht('Current Viewer'))
|
||||
->setPHID('viewer()')
|
||||
->setIcon('fa-user')
|
||||
->setUnique(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialReviewerDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Reviewers');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user, project, or package name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorPeopleDatasource(),
|
||||
new PhabricatorProjectDatasource(),
|
||||
new PhabricatorOwnersPackageDatasource(),
|
||||
new DifferentialBlockingReviewerDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -69,7 +69,9 @@ final class DifferentialAddCommentView extends AphrontView {
|
|||
);
|
||||
|
||||
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
|
||||
$reviewer_source = new PhabricatorProjectOrUserDatasource();
|
||||
|
||||
// TODO: This should be a reviewers datasource, but it's a mess.
|
||||
$reviewer_source = new PhabricatorMetaMTAMailableDatasource();
|
||||
|
||||
$form = new AphrontFormView();
|
||||
$form
|
||||
|
|
|
@ -12,6 +12,16 @@ final class DifferentialRevisionListView extends AphrontView {
|
|||
private $noDataString;
|
||||
private $noBox;
|
||||
private $background = null;
|
||||
private $unlandedDependencies = array();
|
||||
|
||||
public function setUnlandedDependencies(array $unlanded_dependencies) {
|
||||
$this->unlandedDependencies = $unlanded_dependencies;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUnlandedDependencies() {
|
||||
return $this->unlandedDependencies;
|
||||
}
|
||||
|
||||
public function setNoDataString($no_data_string) {
|
||||
$this->noDataString = $no_data_string;
|
||||
|
@ -65,20 +75,6 @@ final class DifferentialRevisionListView extends AphrontView {
|
|||
public function render() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh');
|
||||
if ($fresh) {
|
||||
$fresh = PhabricatorCalendarHoliday::getNthBusinessDay(
|
||||
time(),
|
||||
-$fresh);
|
||||
}
|
||||
|
||||
$stale = PhabricatorEnv::getEnvConfig('differential.days-stale');
|
||||
if ($stale) {
|
||||
$stale = PhabricatorCalendarHoliday::getNthBusinessDay(
|
||||
time(),
|
||||
-$stale);
|
||||
}
|
||||
|
||||
$this->initBehavior('phabricator-tooltips', array());
|
||||
$this->requireResource('aphront-tooltip-css');
|
||||
|
||||
|
@ -109,18 +105,6 @@ final class DifferentialRevisionListView extends AphrontView {
|
|||
$modified = $revision->getDateModified();
|
||||
|
||||
$status = $revision->getStatus();
|
||||
$show_age = ($fresh || $stale) &&
|
||||
$this->highlightAge &&
|
||||
!$revision->isClosed();
|
||||
|
||||
if ($stale && $modified < $stale) {
|
||||
$object_age = PHUIObjectItemView::AGE_OLD;
|
||||
} else if ($fresh && $modified < $fresh) {
|
||||
$object_age = PHUIObjectItemView::AGE_STALE;
|
||||
} else {
|
||||
$object_age = PHUIObjectItemView::AGE_FRESH;
|
||||
}
|
||||
|
||||
$status_name =
|
||||
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status);
|
||||
|
||||
|
@ -143,15 +127,20 @@ final class DifferentialRevisionListView extends AphrontView {
|
|||
$item->addAttribute($draft);
|
||||
}
|
||||
|
||||
/* Most things 'Need Review', so accept it's the default */
|
||||
if ($status != ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
|
||||
$item->addAttribute($status_name);
|
||||
}
|
||||
|
||||
// Author
|
||||
$author_handle = $this->handles[$revision->getAuthorPHID()];
|
||||
$item->addByline(pht('Author: %s', $author_handle->renderLink()));
|
||||
|
||||
$unlanded = idx($this->unlandedDependencies, $phid);
|
||||
if ($unlanded) {
|
||||
$item->addAttribute(
|
||||
array(
|
||||
id(new PHUIIconView())->setIcon('fa-chain-broken', 'red'),
|
||||
' ',
|
||||
pht('Open Dependencies'),
|
||||
));
|
||||
}
|
||||
|
||||
$reviewers = array();
|
||||
// TODO: As above, this should be based on `getReviewerStatus()`.
|
||||
foreach ($revision->getReviewers() as $reviewer) {
|
||||
|
@ -164,7 +153,7 @@ final class DifferentialRevisionListView extends AphrontView {
|
|||
}
|
||||
|
||||
$item->addAttribute(pht('Reviewers: %s', $reviewers));
|
||||
$item->setEpoch($revision->getDateModified(), $object_age);
|
||||
$item->setEpoch($revision->getDateModified());
|
||||
|
||||
switch ($status) {
|
||||
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
|
||||
|
|
|
@ -455,7 +455,12 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
if ($audit_requests) {
|
||||
$user_requests = array();
|
||||
$other_requests = array();
|
||||
|
||||
foreach ($audit_requests as $audit_request) {
|
||||
if (!$audit_request->isInteresting()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($audit_request->isUser()) {
|
||||
$user_requests[] = $audit_request;
|
||||
} else {
|
||||
|
@ -471,7 +476,7 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
|
||||
if ($other_requests) {
|
||||
$view->addProperty(
|
||||
pht('Project/Package Auditors'),
|
||||
pht('Group Auditors'),
|
||||
$this->renderAuditStatusView($other_requests));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,13 @@ abstract class DiffusionAuditorsHeraldAction
|
|||
$object = $adapter->getObject();
|
||||
|
||||
$auditors = $object->getAudits();
|
||||
$auditors = mpull($auditors, null, 'getAuditorPHID');
|
||||
$current = array_keys($auditors);
|
||||
|
||||
$current = array();
|
||||
foreach ($auditors as $auditor) {
|
||||
if ($auditor->isInteresting()) {
|
||||
$current[] = $auditor->getAuditorPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$allowed_types = array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
|
|
|
@ -205,40 +205,29 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController {
|
|||
}
|
||||
|
||||
private function buildRevisionPanel() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
$user_phid = $user->getPHID();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$revision_query = id(new DifferentialRevisionQuery())
|
||||
->setViewer($user)
|
||||
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
|
||||
->withResponsibleUsers(array($user_phid))
|
||||
->needRelationships(true)
|
||||
->needFlags(true)
|
||||
->needDrafts(true);
|
||||
$revisions = PhabricatorDifferentialApplication::loadNeedAttentionRevisions(
|
||||
$viewer);
|
||||
|
||||
$revisions = $revision_query->execute();
|
||||
|
||||
list($blocking, $active) = DifferentialRevisionQuery::splitResponsible(
|
||||
$revisions,
|
||||
array($user_phid));
|
||||
|
||||
if (!$blocking && !$active) {
|
||||
if (!$revisions) {
|
||||
return $this->renderMiniPanel(
|
||||
pht('No Waiting Revisions'),
|
||||
pht('No revisions are waiting on you.'));
|
||||
}
|
||||
|
||||
$title = pht('Revisions Waiting on You');
|
||||
$href = '/differential';
|
||||
$href = '/differential/';
|
||||
$panel = new PHUIObjectBoxView();
|
||||
$panel->setHeader($this->renderSectionHeader($title, $href));
|
||||
|
||||
$revision_view = id(new DifferentialRevisionListView())
|
||||
->setHighlightAge(true)
|
||||
->setRevisions(array_merge($blocking, $active))
|
||||
->setUser($user);
|
||||
->setRevisions($revisions)
|
||||
->setUser($viewer);
|
||||
|
||||
$phids = array_merge(
|
||||
array($user_phid),
|
||||
array($viewer->getPHID()),
|
||||
$revision_view->getRequiredHandlePHIDs());
|
||||
$handles = $this->loadViewerHandles($phids);
|
||||
|
||||
|
|
|
@ -148,8 +148,7 @@ final class ManiphestTaskSearchEngine
|
|||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = id(new ManiphestTaskQuery())
|
||||
->needProjectPHIDs(true);
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['assignedPHIDs']) {
|
||||
$query->withOwners($map['assignedPHIDs']);
|
||||
|
|
|
@ -24,6 +24,8 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
|||
}
|
||||
|
||||
public function execute() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$phids = array_fuse($this->phids);
|
||||
$actors = array();
|
||||
$type_map = array();
|
||||
|
@ -33,6 +35,33 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
|||
|
||||
// TODO: Generalize this somewhere else.
|
||||
|
||||
|
||||
// If we have packages, break them down into their constituent user and
|
||||
// project owners first. Then we'll resolve those and build the packages
|
||||
// back up from the pieces.
|
||||
$package_type = PhabricatorOwnersPackagePHIDType::TYPECONST;
|
||||
$package_phids = idx($type_map, $package_type, array());
|
||||
unset($type_map[$package_type]);
|
||||
|
||||
$package_map = array();
|
||||
if ($package_phids) {
|
||||
$packages = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($package_phids)
|
||||
->execute();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$package_owners = array();
|
||||
foreach ($package->getOwners() as $owner) {
|
||||
$owner_phid = $owner->getUserPHID();
|
||||
$owner_type = phid_get_type($owner_phid);
|
||||
$type_map[$owner_type][] = $owner_phid;
|
||||
$package_owners[] = $owner_phid;
|
||||
}
|
||||
$package_map[$package->getPHID()] = $package_owners;
|
||||
}
|
||||
}
|
||||
|
||||
$results = array();
|
||||
foreach ($type_map as $type => $phids) {
|
||||
switch ($type) {
|
||||
|
@ -40,7 +69,7 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
|||
// NOTE: We're loading the projects here in order to respect policies.
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($phids)
|
||||
->needMembers(true)
|
||||
->needWatchers(true)
|
||||
|
@ -96,6 +125,21 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
|||
}
|
||||
}
|
||||
|
||||
// For any packages, stitch them back together from the resolved users
|
||||
// and projects.
|
||||
if ($package_map) {
|
||||
foreach ($package_map as $package_phid => $owner_phids) {
|
||||
$resolved = array();
|
||||
foreach ($owner_phids as $owner_phid) {
|
||||
$resolved_phids = idx($results, $owner_phid, array());
|
||||
foreach ($resolved_phids as $resolved_phid) {
|
||||
$resolved[] = $resolved_phid;
|
||||
}
|
||||
}
|
||||
$results[$package_phid] = $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,11 +107,15 @@ final class PhabricatorMailTarget extends Phobject {
|
|||
$cc_handles = iterator_to_array($cc_handles);
|
||||
|
||||
$body = '';
|
||||
|
||||
if ($to_handles) {
|
||||
$body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n";
|
||||
$to_names = mpull($to_handles, 'getCommandLineObjectName');
|
||||
$body .= "To: ".implode(', ', $to_names)."\n";
|
||||
}
|
||||
|
||||
if ($cc_handles) {
|
||||
$body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n";
|
||||
$cc_names = mpull($cc_handles, 'getCommandLineObjectName');
|
||||
$body .= "Cc: ".implode(', ', $cc_names)."\n";
|
||||
}
|
||||
|
||||
return $body;
|
||||
|
|
|
@ -634,7 +634,7 @@ final class PhabricatorMetaMTAMail
|
|||
}
|
||||
$mailer->setBody($body);
|
||||
|
||||
$html_emails = false;
|
||||
$html_emails = true;
|
||||
if ($use_prefs && $prefs) {
|
||||
$html_emails = $prefs->getPreference(
|
||||
PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS,
|
||||
|
|
|
@ -8,7 +8,7 @@ final class PhabricatorMetaMTAMailableDatasource
|
|||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user, project, or mailing list name...');
|
||||
return pht('Type a user, project, package, or mailing list name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
|
@ -19,6 +19,7 @@ final class PhabricatorMetaMTAMailableDatasource
|
|||
return array(
|
||||
new PhabricatorPeopleDatasource(),
|
||||
new PhabricatorProjectDatasource(),
|
||||
new PhabricatorOwnersPackageDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorOAuthServerSchemaSpec
|
||||
extends PhabricatorConfigSchemaSpec {
|
||||
|
||||
public function buildSchemata() {
|
||||
$this->buildEdgeSchemata(new PhabricatorOAuthServerClient());
|
||||
}
|
||||
|
||||
}
|
|
@ -39,6 +39,12 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication {
|
|||
return self::GROUP_UTILITIES;
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new PhabricatorOwnersPackageRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/owners/' => array(
|
||||
|
|
|
@ -144,7 +144,7 @@ final class PhabricatorOwnersDetailController
|
|||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($package->getName());
|
||||
$crumbs->addTextCrumb($package->getMonogram());
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
|
@ -184,6 +184,19 @@ final class PhabricatorOwnersDetailController
|
|||
}
|
||||
$view->addProperty(pht('Owners'), $owner_list);
|
||||
|
||||
|
||||
$dominion = $package->getDominion();
|
||||
$dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
|
||||
$spec = idx($dominion_map, $dominion, array());
|
||||
$name = idx($spec, 'short', $dominion);
|
||||
$view->addProperty(pht('Dominion'), $name);
|
||||
|
||||
$auto = $package->getAutoReview();
|
||||
$autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
|
||||
$spec = idx($autoreview_map, $auto, array());
|
||||
$name = idx($spec, 'name', $auto);
|
||||
$view->addProperty(pht('Auto Review'), $name);
|
||||
|
||||
if ($package->getAuditingEnabled()) {
|
||||
$auditing = pht('Enabled');
|
||||
} else {
|
||||
|
|
|
@ -64,7 +64,7 @@ final class PhabricatorOwnersPathsController
|
|||
$editor->applyTransactions($package, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/owners/package/'.$package->getID().'/');
|
||||
->setURI($package->getURI());
|
||||
} else {
|
||||
$paths = $package->getPaths();
|
||||
$path_refs = mpull($paths, 'getRef');
|
||||
|
@ -106,7 +106,7 @@ final class PhabricatorOwnersPathsController
|
|||
|
||||
require_celerity_resource('owners-path-editor-css');
|
||||
|
||||
$cancel_uri = '/owners/package/'.$package->getID().'/';
|
||||
$cancel_uri = $package->getURI();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
|
|
|
@ -51,8 +51,7 @@ final class PhabricatorOwnersPackageEditEngine
|
|||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
$id = $object->getID();
|
||||
return "/owners/package/{$id}/";
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
|
@ -85,6 +84,12 @@ applying a transaction of this type.
|
|||
EOTEXT
|
||||
);
|
||||
|
||||
$autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
|
||||
$autoreview_map = ipull($autoreview_map, 'name');
|
||||
|
||||
$dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
|
||||
$dominion_map = ipull($dominion_map, 'name');
|
||||
|
||||
return array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
|
@ -101,6 +106,28 @@ EOTEXT
|
|||
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
||||
->setIsCopyable(true)
|
||||
->setValue($object->getOwnerPHIDs()),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('dominion')
|
||||
->setLabel(pht('Dominion'))
|
||||
->setDescription(
|
||||
pht('Change package dominion rules.'))
|
||||
->setTransactionType(
|
||||
PhabricatorOwnersPackageTransaction::TYPE_DOMINION)
|
||||
->setIsCopyable(true)
|
||||
->setValue($object->getDominion())
|
||||
->setOptions($dominion_map),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('autoReview')
|
||||
->setLabel(pht('Auto Review'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'Automatically trigger reviews for commits affecting files in '.
|
||||
'this package.'))
|
||||
->setTransactionType(
|
||||
PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW)
|
||||
->setIsCopyable(true)
|
||||
->setValue($object->getAutoReview())
|
||||
->setOptions($autoreview_map),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('auditing')
|
||||
->setLabel(pht('Auditing'))
|
||||
|
|
|
@ -20,6 +20,8 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION;
|
||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS;
|
||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS;
|
||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW;
|
||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_DOMINION;
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
@ -47,6 +49,10 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
return mpull($paths, 'getRef');
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||
return $object->getStatus();
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||
return $object->getAutoReview();
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||
return $object->getDominion();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +64,8 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
case PhabricatorOwnersPackageTransaction::TYPE_NAME:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
|
||||
$new = $xaction->getNewValue();
|
||||
|
@ -113,6 +121,12 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||
$object->setStatus($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||
$object->setAutoReview($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||
$object->setDominion($xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
|
@ -127,6 +141,8 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||
return;
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
|
||||
$old = $xaction->getOldValue();
|
||||
|
@ -205,6 +221,61 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
if (preg_match('([,!])', $new)) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Package names may not contain commas (",") or exclamation '.
|
||||
'marks ("!"). These characters are ambiguous when package '.
|
||||
'names are parsed from the command line.'),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||
$map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
|
||||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
if (empty($map[$new])) {
|
||||
$valid = array_keys($map);
|
||||
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Autoreview setting "%s" is not valid. '.
|
||||
'Valid settings are: %s.',
|
||||
$new,
|
||||
implode(', ', $valid)),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||
$map = PhabricatorOwnersPackage::getDominionOptionsMap();
|
||||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
if (empty($map[$new])) {
|
||||
$valid = array_keys($map);
|
||||
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Dominion setting "%s" is not valid. '.
|
||||
'Valid settings are: %s.',
|
||||
$new,
|
||||
implode(', ', $valid)),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
|
||||
if (!$xactions) {
|
||||
|
@ -331,8 +402,7 @@ final class PhabricatorOwnersPackageTransactionEditor
|
|||
|
||||
$body = parent::buildMailBody($object, $xactions);
|
||||
|
||||
$detail_uri = PhabricatorEnv::getProductionURI(
|
||||
'/owners/package/'.$object->getID().'/');
|
||||
$detail_uri = PhabricatorEnv::getProductionURI($object->getURI());
|
||||
|
||||
$body->addLinkSection(
|
||||
pht('PACKAGE DETAIL'),
|
||||
|
|
|
@ -9,7 +9,7 @@ final class PhabricatorOwnersPackagePHIDType extends PhabricatorPHIDType {
|
|||
}
|
||||
|
||||
public function getTypeIcon() {
|
||||
return 'fa-list-alt';
|
||||
return 'fa-shopping-bag';
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
|
@ -36,12 +36,50 @@ final class PhabricatorOwnersPackagePHIDType extends PhabricatorPHIDType {
|
|||
foreach ($handles as $phid => $handle) {
|
||||
$package = $objects[$phid];
|
||||
|
||||
$monogram = $package->getMonogram();
|
||||
$name = $package->getName();
|
||||
$id = $package->getID();
|
||||
$uri = $package->getURI();
|
||||
|
||||
$handle->setName($name);
|
||||
$handle->setURI("/owners/package/{$id}/");
|
||||
$handle
|
||||
->setName($monogram)
|
||||
->setFullName("{$monogram}: {$name}")
|
||||
->setCommandLineObjectName("{$monogram} {$name}")
|
||||
->setURI($uri);
|
||||
|
||||
if ($package->isArchived()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function canLoadNamedObject($name) {
|
||||
return preg_match('/^O\d*[1-9]\d*$/i', $name);
|
||||
}
|
||||
|
||||
public function loadNamedObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $names) {
|
||||
|
||||
$id_map = array();
|
||||
foreach ($names as $name) {
|
||||
$id = (int)substr($name, 1);
|
||||
$id_map[$id][] = $name;
|
||||
}
|
||||
|
||||
$objects = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($query->getViewer())
|
||||
->withIDs(array_keys($id_map))
|
||||
->execute();
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $id => $object) {
|
||||
foreach (idx($id_map, $id, array()) as $name) {
|
||||
$results[$name] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -351,6 +351,7 @@ final class PhabricatorOwnersPackageQuery
|
|||
}
|
||||
|
||||
$packages = $this->controlResults;
|
||||
$weak_dominion = PhabricatorOwnersPackage::DOMINION_WEAK;
|
||||
|
||||
$matches = array();
|
||||
foreach ($packages as $package_id => $package) {
|
||||
|
@ -373,6 +374,7 @@ final class PhabricatorOwnersPackageQuery
|
|||
if ($best_match && $include) {
|
||||
$matches[$package_id] = array(
|
||||
'strength' => $best_match,
|
||||
'weak' => ($package->getDominion() == $weak_dominion),
|
||||
'package' => $package,
|
||||
);
|
||||
}
|
||||
|
@ -381,6 +383,18 @@ final class PhabricatorOwnersPackageQuery
|
|||
$matches = isort($matches, 'strength');
|
||||
$matches = array_reverse($matches);
|
||||
|
||||
$first_id = null;
|
||||
foreach ($matches as $package_id => $match) {
|
||||
if ($first_id === null) {
|
||||
$first_id = $package_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($match['weak']) {
|
||||
unset($matches[$package_id]);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(ipull($matches, 'package'));
|
||||
}
|
||||
|
||||
|
|
|
@ -136,9 +136,9 @@ final class PhabricatorOwnersPackageSearchEngine
|
|||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObject($package)
|
||||
->setObjectName(pht('Package %d', $id))
|
||||
->setObjectName($package->getMonogram())
|
||||
->setHeader($package->getName())
|
||||
->setHref('/owners/package/'.$id.'/');
|
||||
->setHref($package->getURI());
|
||||
|
||||
if ($package->isArchived()) {
|
||||
$item->setDisabled(true);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorOwnersPackageRemarkupRule
|
||||
extends PhabricatorObjectRemarkupRule {
|
||||
|
||||
protected function getObjectNamePrefix() {
|
||||
return 'O';
|
||||
}
|
||||
|
||||
protected function loadObjects(array $ids) {
|
||||
$viewer = $this->getEngine()->getConfig('viewer');
|
||||
|
||||
return id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs($ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -14,12 +14,14 @@ final class PhabricatorOwnersPackage
|
|||
protected $name;
|
||||
protected $originalName;
|
||||
protected $auditingEnabled;
|
||||
protected $autoReview;
|
||||
protected $description;
|
||||
protected $primaryOwnerPHID;
|
||||
protected $mailKey;
|
||||
protected $status;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $dominion;
|
||||
|
||||
private $paths = self::ATTACHABLE;
|
||||
private $owners = self::ATTACHABLE;
|
||||
|
@ -28,6 +30,14 @@ final class PhabricatorOwnersPackage
|
|||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_ARCHIVED = 'archived';
|
||||
|
||||
const AUTOREVIEW_NONE = 'none';
|
||||
const AUTOREVIEW_SUBSCRIBE = 'subscribe';
|
||||
const AUTOREVIEW_REVIEW = 'review';
|
||||
const AUTOREVIEW_BLOCK = 'block';
|
||||
|
||||
const DOMINION_STRONG = 'strong';
|
||||
const DOMINION_WEAK = 'weak';
|
||||
|
||||
public static function initializeNewPackage(PhabricatorUser $actor) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($actor)
|
||||
|
@ -41,6 +51,8 @@ final class PhabricatorOwnersPackage
|
|||
|
||||
return id(new PhabricatorOwnersPackage())
|
||||
->setAuditingEnabled(0)
|
||||
->setAutoReview(self::AUTOREVIEW_NONE)
|
||||
->setDominion(self::DOMINION_STRONG)
|
||||
->setViewPolicy($view_policy)
|
||||
->setEditPolicy($edit_policy)
|
||||
->attachPaths(array())
|
||||
|
@ -56,6 +68,36 @@ final class PhabricatorOwnersPackage
|
|||
);
|
||||
}
|
||||
|
||||
public static function getAutoreviewOptionsMap() {
|
||||
return array(
|
||||
self::AUTOREVIEW_NONE => array(
|
||||
'name' => pht('No Autoreview'),
|
||||
),
|
||||
self::AUTOREVIEW_SUBSCRIBE => array(
|
||||
'name' => pht('Subscribe to Changes'),
|
||||
),
|
||||
self::AUTOREVIEW_REVIEW => array(
|
||||
'name' => pht('Review Changes'),
|
||||
),
|
||||
self::AUTOREVIEW_BLOCK => array(
|
||||
'name' => pht('Review Changes (Blocking)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static function getDominionOptionsMap() {
|
||||
return array(
|
||||
self::DOMINION_STRONG => array(
|
||||
'name' => pht('Strong (Control All Paths)'),
|
||||
'short' => pht('Strong'),
|
||||
),
|
||||
self::DOMINION_WEAK => array(
|
||||
'name' => pht('Weak (Control Unowned Paths)'),
|
||||
'short' => pht('Weak'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
// This information is better available from the history table.
|
||||
|
@ -69,6 +111,8 @@ final class PhabricatorOwnersPackage
|
|||
'auditingEnabled' => 'bool',
|
||||
'mailKey' => 'bytes20',
|
||||
'status' => 'text32',
|
||||
'autoReview' => 'text32',
|
||||
'dominion' => 'text32',
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@ -165,7 +209,7 @@ final class PhabricatorOwnersPackage
|
|||
foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
|
||||
$rows[] = queryfx_all(
|
||||
$conn,
|
||||
'SELECT pkg.id, p.excluded, p.path
|
||||
'SELECT pkg.id, pkg.dominion, p.excluded, p.path
|
||||
FROM %T pkg JOIN %T p ON p.packageID = pkg.id
|
||||
WHERE p.path IN (%Ls) %Q',
|
||||
$package->getTableName(),
|
||||
|
@ -207,35 +251,100 @@ final class PhabricatorOwnersPackage
|
|||
}
|
||||
|
||||
public static function findLongestPathsPerPackage(array $rows, array $paths) {
|
||||
$ids = array();
|
||||
|
||||
foreach (igroup($rows, 'id') as $id => $package_paths) {
|
||||
$relevant_paths = array_select_keys(
|
||||
$paths,
|
||||
ipull($package_paths, 'path'));
|
||||
// Build a map from each path to all the package paths which match it.
|
||||
$path_hits = array();
|
||||
$weak = array();
|
||||
foreach ($rows as $row) {
|
||||
$id = $row['id'];
|
||||
$path = $row['path'];
|
||||
$length = strlen($path);
|
||||
$excluded = $row['excluded'];
|
||||
|
||||
// For every package, remove all excluded paths.
|
||||
$remove = array();
|
||||
foreach ($package_paths as $package_path) {
|
||||
if ($package_path['excluded']) {
|
||||
$remove += idx($relevant_paths, $package_path['path'], array());
|
||||
unset($relevant_paths[$package_path['path']]);
|
||||
}
|
||||
if ($row['dominion'] === self::DOMINION_WEAK) {
|
||||
$weak[$id] = true;
|
||||
}
|
||||
|
||||
if ($remove) {
|
||||
foreach ($relevant_paths as $fragment => $fragment_paths) {
|
||||
$relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove);
|
||||
}
|
||||
}
|
||||
|
||||
$relevant_paths = array_filter($relevant_paths);
|
||||
if ($relevant_paths) {
|
||||
$ids[$id] = max(array_map('strlen', array_keys($relevant_paths)));
|
||||
$matches = $paths[$path];
|
||||
foreach ($matches as $match => $ignored) {
|
||||
$path_hits[$match][] = array(
|
||||
'id' => $id,
|
||||
'excluded' => $excluded,
|
||||
'length' => $length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
// For each path, process the matching package paths to figure out which
|
||||
// packages actually own it.
|
||||
$path_packages = array();
|
||||
foreach ($path_hits as $match => $hits) {
|
||||
$hits = isort($hits, 'length');
|
||||
|
||||
$packages = array();
|
||||
foreach ($hits as $hit) {
|
||||
$package_id = $hit['id'];
|
||||
if ($hit['excluded']) {
|
||||
unset($packages[$package_id]);
|
||||
} else {
|
||||
$packages[$package_id] = $hit;
|
||||
}
|
||||
}
|
||||
|
||||
$path_packages[$match] = $packages;
|
||||
}
|
||||
|
||||
// Remove packages with weak dominion rules that should cede control to
|
||||
// a more specific package.
|
||||
if ($weak) {
|
||||
foreach ($path_packages as $match => $packages) {
|
||||
$packages = isort($packages, 'length');
|
||||
$packages = array_reverse($packages, true);
|
||||
|
||||
$first = null;
|
||||
foreach ($packages as $package_id => $package) {
|
||||
// If this is the first package we've encountered, note it and
|
||||
// continue. We're iterating over the packages from longest to
|
||||
// shortest match, so this package always has the strongest claim
|
||||
// on the path.
|
||||
if ($first === null) {
|
||||
$first = $package_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is the first package we saw, its claim stands even if it
|
||||
// is a weak package.
|
||||
if ($first === $package_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a weak package and not the first package we saw,
|
||||
// cede its claim to the stronger package.
|
||||
if (isset($weak[$package_id])) {
|
||||
unset($packages[$package_id]);
|
||||
}
|
||||
}
|
||||
|
||||
$path_packages[$match] = $packages;
|
||||
}
|
||||
}
|
||||
|
||||
// For each package that owns at least one path, identify the longest
|
||||
// path it owns.
|
||||
$package_lengths = array();
|
||||
foreach ($path_packages as $match => $hits) {
|
||||
foreach ($hits as $hit) {
|
||||
$length = $hit['length'];
|
||||
$id = $hit['id'];
|
||||
if (empty($package_lengths[$id])) {
|
||||
$package_lengths[$id] = $length;
|
||||
} else {
|
||||
$package_lengths[$id] = max($package_lengths[$id], $length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $package_lengths;
|
||||
}
|
||||
|
||||
public static function splitPath($path) {
|
||||
|
@ -289,6 +398,14 @@ final class PhabricatorOwnersPackage
|
|||
return isset($owner_phids[$phid]);
|
||||
}
|
||||
|
||||
public function getMonogram() {
|
||||
return 'O'.$this->getID();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
// TODO: Move these to "/O123" for consistency.
|
||||
return '/owners/package/'.$this->getID().'/';
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ final class PhabricatorOwnersPackageTransaction
|
|||
const TYPE_DESCRIPTION = 'owners.description';
|
||||
const TYPE_PATHS = 'owners.paths';
|
||||
const TYPE_STATUS = 'owners.status';
|
||||
const TYPE_AUTOREVIEW = 'owners.autoreview';
|
||||
const TYPE_DOMINION = 'owners.dominion';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'owners';
|
||||
|
@ -143,6 +145,30 @@ final class PhabricatorOwnersPackageTransaction
|
|||
'%s archived this package.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
case self::TYPE_AUTOREVIEW:
|
||||
$map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
|
||||
$map = ipull($map, 'name');
|
||||
|
||||
$old = idx($map, $old, $old);
|
||||
$new = idx($map, $new, $new);
|
||||
|
||||
return pht(
|
||||
'%s adjusted autoreview from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
case self::TYPE_DOMINION:
|
||||
$map = PhabricatorOwnersPackage::getDominionOptionsMap();
|
||||
$map = ipull($map, 'short');
|
||||
|
||||
$old = idx($map, $old, $old);
|
||||
$new = idx($map, $new, $new);
|
||||
|
||||
return pht(
|
||||
'%s adjusted package dominion rules from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
|
|
|
@ -4,9 +4,24 @@ final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase {
|
|||
|
||||
public function testFindLongestPathsPerPackage() {
|
||||
$rows = array(
|
||||
array('id' => 1, 'excluded' => 0, 'path' => 'src/'),
|
||||
array('id' => 1, 'excluded' => 1, 'path' => 'src/releeph/'),
|
||||
array('id' => 2, 'excluded' => 0, 'path' => 'src/releeph/'),
|
||||
array(
|
||||
'id' => 1,
|
||||
'excluded' => 0,
|
||||
'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG,
|
||||
'path' => 'src/',
|
||||
),
|
||||
array(
|
||||
'id' => 1,
|
||||
'excluded' => 1,
|
||||
'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG,
|
||||
'path' => 'src/releeph/',
|
||||
),
|
||||
array(
|
||||
'id' => 2,
|
||||
'excluded' => 0,
|
||||
'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG,
|
||||
'path' => 'src/releeph/',
|
||||
),
|
||||
);
|
||||
|
||||
$paths = array(
|
||||
|
@ -29,6 +44,62 @@ final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase {
|
|||
2 => strlen('src/releeph/'),
|
||||
),
|
||||
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
|
||||
|
||||
|
||||
// Test packages with weak dominion. Here, only package #2 should own the
|
||||
// path. Package #1's claim is ceded to Package #2 because it uses weak
|
||||
// rules. Package #2 gets the claim even though it also has weak rules
|
||||
// because there is no more-specific package.
|
||||
|
||||
$rows = array(
|
||||
array(
|
||||
'id' => 1,
|
||||
'excluded' => 0,
|
||||
'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK,
|
||||
'path' => 'src/',
|
||||
),
|
||||
array(
|
||||
'id' => 2,
|
||||
'excluded' => 0,
|
||||
'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK,
|
||||
'path' => 'src/applications/',
|
||||
),
|
||||
);
|
||||
|
||||
$pvalue = array('src/applications/main/main.c' => true);
|
||||
|
||||
$paths = array(
|
||||
'src/' => $pvalue,
|
||||
'src/applications/' => $pvalue,
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
array(
|
||||
2 => strlen('src/applications/'),
|
||||
),
|
||||
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
|
||||
|
||||
|
||||
// Now, add a more specific path to Package #1. This tests nested ownership
|
||||
// in packages with weak dominion rules. This time, Package #1 should end
|
||||
// up back on top, with Package #2 cedeing control to its more specific
|
||||
// path.
|
||||
$rows[] = array(
|
||||
'id' => 1,
|
||||
'excluded' => 0,
|
||||
'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK,
|
||||
'path' => 'src/applications/main/',
|
||||
);
|
||||
|
||||
$paths['src/applications/main/'] = $pvalue;
|
||||
|
||||
$this->assertEqual(
|
||||
array(
|
||||
1 => strlen('src/applications/main/'),
|
||||
),
|
||||
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,9 +27,12 @@ final class PhabricatorOwnersPackageDatasource
|
|||
|
||||
$packages = $this->executeQuery($query);
|
||||
foreach ($packages as $package) {
|
||||
$name = $package->getName();
|
||||
$monogram = $package->getMonogram();
|
||||
|
||||
$results[] = id(new PhabricatorTypeaheadResult())
|
||||
->setName($package->getName())
|
||||
->setURI('/owners/package/'.$package->getID().'/')
|
||||
->setName("{$monogram}: {$name}")
|
||||
->setURI($package->getURI())
|
||||
->setPHID($package->getPHID());
|
||||
}
|
||||
|
||||
|
|
|
@ -63,9 +63,12 @@ final class PassphraseQueryConduitAPIMethod
|
|||
|
||||
$material = array();
|
||||
|
||||
$is_locked = $credential->getIsLocked();
|
||||
$allow_api = ($credential->getAllowConduit() && !$is_locked);
|
||||
|
||||
$secret = null;
|
||||
if ($request->getValue('needSecrets')) {
|
||||
if ($credential->getAllowConduit()) {
|
||||
if ($allow_api) {
|
||||
$secret = $credential->getSecret();
|
||||
if ($secret) {
|
||||
$secret = $secret->openEnvelope();
|
||||
|
@ -102,7 +105,7 @@ final class PassphraseQueryConduitAPIMethod
|
|||
break;
|
||||
}
|
||||
|
||||
if (!$credential->getAllowConduit()) {
|
||||
if (!$allow_api) {
|
||||
$material['noAPIAccess'] = pht(
|
||||
'This private material for this credential is not accessible via '.
|
||||
'API calls.');
|
||||
|
|
|
@ -33,8 +33,22 @@ final class PassphraseCredentialConduitController
|
|||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
||||
}
|
||||
|
||||
$is_locked = $credential->getIsLocked();
|
||||
|
||||
if ($is_locked) {
|
||||
return $this->newDialog()
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Credential Locked'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'This credential can not be made available via Conduit because '.
|
||||
'it is locked.'))
|
||||
->addCancelButton($view_uri);
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT)
|
||||
->setNewValue(!$credential->getAllowConduit());
|
||||
|
|
|
@ -270,8 +270,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
|||
}
|
||||
|
||||
if ($type->shouldRequireUsername()) {
|
||||
$form
|
||||
->appendChild(
|
||||
$form->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('username')
|
||||
->setLabel(pht('Login/Username'))
|
||||
|
@ -279,13 +278,13 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
|||
->setDisabled($credential_is_locked)
|
||||
->setError($e_username));
|
||||
}
|
||||
$form
|
||||
->appendChild(
|
||||
$secret_control
|
||||
->setName('secret')
|
||||
->setLabel($type->getSecretLabel())
|
||||
->setDisabled($credential_is_locked)
|
||||
->setValue($v_secret));
|
||||
|
||||
$form->appendChild(
|
||||
$secret_control
|
||||
->setName('secret')
|
||||
->setLabel($type->getSecretLabel())
|
||||
->setDisabled($credential_is_locked)
|
||||
->setValue($v_secret));
|
||||
|
||||
if ($type->shouldShowPasswordField()) {
|
||||
$form->appendChild(
|
||||
|
|
|
@ -32,15 +32,17 @@ final class PassphraseCredentialLockController
|
|||
return $this->newDialog()
|
||||
->setTitle(pht('Credential Already Locked'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'This credential has been locked and the secret is '.
|
||||
'hidden forever. Anything relying on this credential will '.
|
||||
'still function. This operation can not be undone.'))
|
||||
pht('This credential is already locked.'))
|
||||
->addCancelButton($view_uri, pht('Close'));
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT)
|
||||
->setNewValue(0);
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK)
|
||||
->setNewValue(1);
|
||||
|
@ -48,6 +50,7 @@ final class PassphraseCredentialLockController
|
|||
$editor = id(new PassphraseCredentialTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($credential, $xactions);
|
||||
|
||||
|
@ -55,12 +58,13 @@ final class PassphraseCredentialLockController
|
|||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Really lock credential?'))
|
||||
->setTitle(pht('Lock Credential'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'This credential will be locked and the secret will be '.
|
||||
'hidden forever. Anything relying on this credential will '.
|
||||
'still function. This operation can not be undone.'))
|
||||
'This credential will be locked and the secret will be hidden '.
|
||||
'forever. If Conduit access is enabled, it will be revoked. '.
|
||||
'Anything relying on this credential will still function. This '.
|
||||
'operation can not be undone.'))
|
||||
->addSubmitButton(pht('Lock Credential'))
|
||||
->addCancelButton($view_uri);
|
||||
}
|
||||
|
|
|
@ -119,6 +119,8 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
$credential,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$can_conduit = ($can_edit && !$is_locked);
|
||||
|
||||
$curtain = $this->newCurtainView($credential);
|
||||
|
||||
$curtain->addAction(
|
||||
|
@ -161,7 +163,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
->setName($credential_conduit_text)
|
||||
->setIcon($credential_conduit_icon)
|
||||
->setHref($this->getApplicationURI("conduit/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setDisabled(!$can_conduit)
|
||||
->setWorkflow(true));
|
||||
|
||||
$curtain->addAction(
|
||||
|
@ -202,16 +204,6 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
$credential->getUsername());
|
||||
}
|
||||
|
||||
$used_by_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$credential->getPHID(),
|
||||
PhabricatorCredentialsUsedByObjectEdgeType::EDGECONST);
|
||||
|
||||
if ($used_by_phids) {
|
||||
$properties->addProperty(
|
||||
pht('Used By'),
|
||||
$viewer->renderHandleList($used_by_phids));
|
||||
}
|
||||
|
||||
$description = $credential->getDescription();
|
||||
if (strlen($description)) {
|
||||
$properties->addSectionHeader(
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCredentialsUsedByObjectEdgeType
|
||||
extends PhabricatorEdgeType {
|
||||
|
||||
const EDGECONST = 40;
|
||||
|
||||
public function getInverseEdgeConstant() {
|
||||
return PhabricatorObjectUsesCredentialsEdgeType::EDGECONST;
|
||||
}
|
||||
|
||||
public function shouldWriteInverseTransactions() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1291,11 +1291,12 @@ final class PhabricatorUser
|
|||
$profile->delete();
|
||||
}
|
||||
|
||||
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
|
||||
'objectPHID = %s',
|
||||
$this->getPHID());
|
||||
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||
->setViewer($engine->getViewer())
|
||||
->withObjectPHIDs(array($this->getPHID()))
|
||||
->execute();
|
||||
foreach ($keys as $key) {
|
||||
$key->delete();
|
||||
$engine->destroyObject($key);
|
||||
}
|
||||
|
||||
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
||||
|
@ -1341,6 +1342,12 @@ final class PhabricatorUser
|
|||
return 'id_rsa_phabricator';
|
||||
}
|
||||
|
||||
public function getSSHKeyNotifyPHIDs() {
|
||||
return array(
|
||||
$this->getPHID(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ final class PhabricatorObjectHandle
|
|||
private $policyFiltered;
|
||||
private $subtitle;
|
||||
private $tokenIcon;
|
||||
private $commandLineObjectName;
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
|
@ -196,6 +197,19 @@ final class PhabricatorObjectHandle
|
|||
return $this->getName();
|
||||
}
|
||||
|
||||
public function setCommandLineObjectName($command_line_object_name) {
|
||||
$this->commandLineObjectName = $command_line_object_name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommandLineObjectName() {
|
||||
if ($this->commandLineObjectName !== null) {
|
||||
return $this->commandLineObjectName;
|
||||
}
|
||||
|
||||
return $this->getObjectName();
|
||||
}
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
|
|
|
@ -6,6 +6,7 @@ final class PhabricatorObjectListQuery extends Phobject {
|
|||
private $objectList;
|
||||
private $allowedTypes = array();
|
||||
private $allowPartialResults;
|
||||
private $suffixes = array();
|
||||
|
||||
public function setAllowPartialResults($allow_partial_results) {
|
||||
$this->allowPartialResults = $allow_partial_results;
|
||||
|
@ -16,6 +17,15 @@ final class PhabricatorObjectListQuery extends Phobject {
|
|||
return $this->allowPartialResults;
|
||||
}
|
||||
|
||||
public function setSuffixes(array $suffixes) {
|
||||
$this->suffixes = $suffixes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSuffixes() {
|
||||
return $this->suffixes;
|
||||
}
|
||||
|
||||
public function setAllowedTypes(array $allowed_types) {
|
||||
$this->allowedTypes = $allowed_types;
|
||||
return $this;
|
||||
|
@ -45,9 +55,80 @@ final class PhabricatorObjectListQuery extends Phobject {
|
|||
|
||||
public function execute() {
|
||||
$names = $this->getObjectList();
|
||||
$names = array_unique(array_filter(preg_split('/[\s,]+/', $names)));
|
||||
|
||||
$objects = $this->loadObjects($names);
|
||||
// First, normalize any internal whitespace so we don't get weird results
|
||||
// if linebreaks hit in weird spots.
|
||||
$names = preg_replace('/\s+/', ' ', $names);
|
||||
|
||||
// Split the list on commas.
|
||||
$names = explode(',', $names);
|
||||
|
||||
// Trim and remove empty tokens.
|
||||
foreach ($names as $key => $name) {
|
||||
$name = trim($name);
|
||||
|
||||
if (!strlen($name)) {
|
||||
unset($names[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$names[$key] = $name;
|
||||
}
|
||||
|
||||
// Remove duplicates.
|
||||
$names = array_unique($names);
|
||||
|
||||
$name_map = array();
|
||||
foreach ($names as $name) {
|
||||
$parts = explode(' ', $name);
|
||||
|
||||
// If this looks like a monogram, ignore anything after the first token.
|
||||
// This allows us to parse "O123 Package Name" as though it was "O123",
|
||||
// which we can look up.
|
||||
if (preg_match('/^[A-Z]\d+\z/', $parts[0])) {
|
||||
$name_map[$parts[0]] = $name;
|
||||
} else {
|
||||
// For anything else, split it on spaces and use each token as a
|
||||
// value. This means "alincoln htaft", separated with a space instead
|
||||
// of with a comma, is two different users.
|
||||
foreach ($parts as $part) {
|
||||
$name_map[$part] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're parsing with suffixes, strip them off any tokens and keep
|
||||
// track of them for later.
|
||||
$suffixes = $this->getSuffixes();
|
||||
if ($suffixes) {
|
||||
$suffixes = array_fuse($suffixes);
|
||||
$suffix_map = array();
|
||||
$stripped_map = array();
|
||||
foreach ($name_map as $key => $name) {
|
||||
$found_suffixes = array();
|
||||
do {
|
||||
$has_any_suffix = false;
|
||||
foreach ($suffixes as $suffix) {
|
||||
if (!$this->hasSuffix($name, $suffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $this->stripSuffix($key, $suffix);
|
||||
$name = $this->stripSuffix($name, $suffix);
|
||||
|
||||
$found_suffixes[] = $suffix;
|
||||
$has_any_suffix = true;
|
||||
break;
|
||||
}
|
||||
} while ($has_any_suffix);
|
||||
|
||||
$stripped_map[$key] = $name;
|
||||
$suffix_map[$key] = array_fuse($found_suffixes);
|
||||
}
|
||||
$name_map = $stripped_map;
|
||||
}
|
||||
|
||||
$objects = $this->loadObjects(array_keys($name_map));
|
||||
|
||||
$types = array();
|
||||
foreach ($objects as $name => $object) {
|
||||
|
@ -66,8 +147,8 @@ final class PhabricatorObjectListQuery extends Phobject {
|
|||
$invalid = array_mergev($invalid);
|
||||
|
||||
$missing = array();
|
||||
foreach ($names as $name) {
|
||||
if (empty($objects[$name])) {
|
||||
foreach ($name_map as $key => $name) {
|
||||
if (empty($objects[$key])) {
|
||||
$missing[] = $name;
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +181,18 @@ final class PhabricatorObjectListQuery extends Phobject {
|
|||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique(mpull($objects, 'getPHID')));
|
||||
$result = array_unique(mpull($objects, 'getPHID'));
|
||||
|
||||
if ($suffixes) {
|
||||
foreach ($result as $key => $phid) {
|
||||
$result[$key] = array(
|
||||
'phid' => $phid,
|
||||
'suffixes' => idx($suffix_map, $key, array()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
private function loadObjects($names) {
|
||||
|
@ -146,5 +238,16 @@ final class PhabricatorObjectListQuery extends Phobject {
|
|||
return $results;
|
||||
}
|
||||
|
||||
private function hasSuffix($key, $suffix) {
|
||||
return (substr($key, -strlen($suffix)) === $suffix);
|
||||
}
|
||||
|
||||
private function stripSuffix($key, $suffix) {
|
||||
if ($this->hasSuffix($key, $suffix)) {
|
||||
return substr($key, 0, -strlen($suffix));
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
|||
$name = $user->getUsername();
|
||||
$phid = $user->getPHID();
|
||||
|
||||
|
||||
$result = $this->parseObjectList("@{$name}");
|
||||
$this->assertEqual(array($phid), $result);
|
||||
|
||||
|
@ -29,6 +28,47 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
|||
$result = $this->parseObjectList('');
|
||||
$this->assertEqual(array(), $result);
|
||||
|
||||
$result = $this->parseObjectList("{$name}!", array(), false, array('!'));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
array(
|
||||
'phid' => $phid,
|
||||
'suffixes' => array('!' => '!'),
|
||||
),
|
||||
),
|
||||
$result);
|
||||
|
||||
$package = PhabricatorOwnersPackage::initializeNewPackage($user)
|
||||
->setName(pht('Query Test Package'))
|
||||
->save();
|
||||
|
||||
$package_phid = $package->getPHID();
|
||||
$package_mono = $package->getMonogram();
|
||||
|
||||
$result = $this->parseObjectList("{$package_mono} Any Ignored Text");
|
||||
$this->assertEqual(array($package_phid), $result);
|
||||
|
||||
$result = $this->parseObjectList("{$package_mono} Any Text, {$name}");
|
||||
$this->assertEqual(array($package_phid, $phid), $result);
|
||||
|
||||
$result = $this->parseObjectList(
|
||||
"{$package_mono} Any Text!, {$name}",
|
||||
array(),
|
||||
false,
|
||||
array('!'));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
array(
|
||||
'phid' => $package_phid,
|
||||
'suffixes' => array('!' => '!'),
|
||||
),
|
||||
array(
|
||||
'phid' => $phid,
|
||||
'suffixes' => array(),
|
||||
),
|
||||
),
|
||||
$result);
|
||||
|
||||
// Expect failure when loading a user if objects must be of type "DUCK".
|
||||
$caught = null;
|
||||
try {
|
||||
|
@ -67,7 +107,8 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
|||
private function parseObjectList(
|
||||
$list,
|
||||
array $types = array(),
|
||||
$allow_partial = false) {
|
||||
$allow_partial = false,
|
||||
$suffixes = array()) {
|
||||
|
||||
$query = id(new PhabricatorObjectListQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
|
@ -81,6 +122,10 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
|||
$query->setAllowPartialResults(true);
|
||||
}
|
||||
|
||||
if ($suffixes) {
|
||||
$query->setSuffixes($suffixes);
|
||||
}
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
|
|
|
@ -42,12 +42,19 @@ final class PholioUploadedImageView extends AphrontView {
|
|||
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
|
||||
$thumbnail_uri = $file->getURIForTransform($xform);
|
||||
|
||||
$thumb_img = phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'class' => 'pholio-thumb-img',
|
||||
'src' => $thumbnail_uri,
|
||||
));
|
||||
|
||||
$thumb_frame = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'pholio-thumb-frame',
|
||||
'style' => 'background-image: url('.$thumbnail_uri.');',
|
||||
));
|
||||
),
|
||||
$thumb_img);
|
||||
|
||||
$handle = javelin_tag(
|
||||
'div',
|
||||
|
|
|
@ -100,9 +100,10 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
if ($points !== null) {
|
||||
$points_tag = id(new PHUITagView())
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setShade(PHUITagView::COLOR_BLUE)
|
||||
->setShade(PHUITagView::COLOR_GREY)
|
||||
->setSlimShady(true)
|
||||
->setName($points);
|
||||
->setName($points)
|
||||
->addClass('phui-workcard-points');
|
||||
$card->addAttribute($points_tag);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,16 @@ final class PhabricatorRepositoryAuditRequest
|
|||
return $this->assertAttached($this->commit);
|
||||
}
|
||||
|
||||
public function isInteresting() {
|
||||
switch ($this->getAuditStatus()) {
|
||||
case PhabricatorAuditStatusConstants::NONE:
|
||||
case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -33,108 +33,132 @@ final class PhabricatorRepositoryCommitOwnersWorker
|
|||
$repository,
|
||||
$commit,
|
||||
PhabricatorUser::getOmnipotentUser());
|
||||
|
||||
$affected_packages = PhabricatorOwnersPackage::loadAffectedPackages(
|
||||
$repository,
|
||||
$affected_paths);
|
||||
|
||||
if ($affected_packages) {
|
||||
$requests = id(new PhabricatorRepositoryAuditRequest())
|
||||
->loadAllWhere(
|
||||
'commitPHID = %s',
|
||||
$commit->getPHID());
|
||||
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||
|
||||
foreach ($affected_packages as $package) {
|
||||
$request = idx($requests, $package->getPHID());
|
||||
if ($request) {
|
||||
// Don't update request if it exists already.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($package->isArchived()) {
|
||||
// Don't trigger audits if the package is archived.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($package->getAuditingEnabled()) {
|
||||
$reasons = $this->checkAuditReasons($commit, $package);
|
||||
if ($reasons) {
|
||||
$audit_status =
|
||||
PhabricatorAuditStatusConstants::AUDIT_REQUIRED;
|
||||
} else {
|
||||
$audit_status =
|
||||
PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED;
|
||||
}
|
||||
} else {
|
||||
$reasons = array();
|
||||
$audit_status = PhabricatorAuditStatusConstants::NONE;
|
||||
}
|
||||
|
||||
$relationship = new PhabricatorRepositoryAuditRequest();
|
||||
$relationship->setAuditorPHID($package->getPHID());
|
||||
$relationship->setCommitPHID($commit->getPHID());
|
||||
$relationship->setAuditReasons($reasons);
|
||||
$relationship->setAuditStatus($audit_status);
|
||||
|
||||
$relationship->save();
|
||||
|
||||
$requests[$package->getPHID()] = $relationship;
|
||||
}
|
||||
|
||||
$commit->updateAuditStatus($requests);
|
||||
$commit->save();
|
||||
if (!$affected_packages) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function checkAuditReasons(
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
PhabricatorOwnersPackage $package) {
|
||||
|
||||
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
|
||||
'commitID = %d',
|
||||
$commit->getID());
|
||||
$commit->attachCommitData($data);
|
||||
|
||||
$reasons = array();
|
||||
|
||||
if ($data->getCommitDetail('vsDiff')) {
|
||||
$reasons[] = pht('Changed After Revision Was Accepted');
|
||||
}
|
||||
|
||||
$commit_author_phid = $data->getCommitDetail('authorPHID');
|
||||
if (!$commit_author_phid) {
|
||||
$reasons[] = pht('Commit Author Not Recognized');
|
||||
}
|
||||
|
||||
$author_phid = $data->getCommitDetail('authorPHID');
|
||||
$revision_id = $data->getCommitDetail('differential.revisionID');
|
||||
|
||||
$revision_author_phid = null;
|
||||
$commit_reviewedby_phid = null;
|
||||
|
||||
if ($revision_id) {
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withIDs(array($revision_id))
|
||||
->needReviewerStatus(true)
|
||||
->executeOne();
|
||||
if ($revision) {
|
||||
$revision_author_phid = $revision->getAuthorPHID();
|
||||
$commit_reviewedby_phid = $data->getCommitDetail('reviewerPHID');
|
||||
if ($revision_author_phid !== $commit_author_phid) {
|
||||
$reasons[] = pht('Author Not Matching with Revision');
|
||||
}
|
||||
} else {
|
||||
$reasons[] = pht('Revision Not Found');
|
||||
}
|
||||
|
||||
} else {
|
||||
$reasons[] = pht('No Revision Specified');
|
||||
$revision = null;
|
||||
}
|
||||
|
||||
$owners_phids = PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
|
||||
array($package->getID()));
|
||||
$requests = id(new PhabricatorRepositoryAuditRequest())
|
||||
->loadAllWhere(
|
||||
'commitPHID = %s',
|
||||
$commit->getPHID());
|
||||
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||
|
||||
if (!($commit_author_phid && in_array($commit_author_phid, $owners_phids) ||
|
||||
$commit_reviewedby_phid && in_array($commit_reviewedby_phid,
|
||||
$owners_phids))) {
|
||||
|
||||
foreach ($affected_packages as $package) {
|
||||
$request = idx($requests, $package->getPHID());
|
||||
if ($request) {
|
||||
// Don't update request if it exists already.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($package->isArchived()) {
|
||||
// Don't trigger audits if the package is archived.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($package->getAuditingEnabled()) {
|
||||
$reasons = $this->checkAuditReasons(
|
||||
$commit,
|
||||
$package,
|
||||
$author_phid,
|
||||
$revision);
|
||||
|
||||
if ($reasons) {
|
||||
$audit_status = PhabricatorAuditStatusConstants::AUDIT_REQUIRED;
|
||||
} else {
|
||||
$audit_status = PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED;
|
||||
}
|
||||
} else {
|
||||
$reasons = array();
|
||||
$audit_status = PhabricatorAuditStatusConstants::NONE;
|
||||
}
|
||||
|
||||
$relationship = new PhabricatorRepositoryAuditRequest();
|
||||
$relationship->setAuditorPHID($package->getPHID());
|
||||
$relationship->setCommitPHID($commit->getPHID());
|
||||
$relationship->setAuditReasons($reasons);
|
||||
$relationship->setAuditStatus($audit_status);
|
||||
|
||||
$relationship->save();
|
||||
|
||||
$requests[$package->getPHID()] = $relationship;
|
||||
}
|
||||
|
||||
$commit->updateAuditStatus($requests);
|
||||
$commit->save();
|
||||
}
|
||||
|
||||
private function checkAuditReasons(
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
PhabricatorOwnersPackage $package,
|
||||
$author_phid,
|
||||
$revision) {
|
||||
|
||||
$owner_phids = PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
|
||||
array(
|
||||
$package->getID(),
|
||||
));
|
||||
$owner_phids = array_fuse($owner_phids);
|
||||
|
||||
$reasons = array();
|
||||
|
||||
if (!$author_phid) {
|
||||
$reasons[] = pht('Commit Author Not Recognized');
|
||||
} else if (isset($owner_phids[$author_phid])) {
|
||||
return $reasons;
|
||||
}
|
||||
|
||||
if (!$revision) {
|
||||
$reasons[] = pht('No Revision Specified');
|
||||
return $reasons;
|
||||
}
|
||||
|
||||
$accepted_statuses = array(
|
||||
DifferentialReviewerStatus::STATUS_ACCEPTED,
|
||||
DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER,
|
||||
);
|
||||
$accepted_statuses = array_fuse($accepted_statuses);
|
||||
|
||||
$found_accept = false;
|
||||
foreach ($revision->getReviewerStatus() as $reviewer) {
|
||||
$reviewer_phid = $reviewer->getReviewerPHID();
|
||||
|
||||
// If this reviewer isn't a package owner, just ignore them.
|
||||
if (empty($owner_phids[$reviewer_phid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this reviewer accepted the revision and owns the package, we're
|
||||
// all clear and do not need to trigger an audit.
|
||||
if (isset($accepted_statuses[$reviewer->getStatus()])) {
|
||||
$found_accept = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found_accept) {
|
||||
$reasons[] = pht('Owners Not Involved');
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorSearchResultBucket
|
||||
extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $pageSize;
|
||||
|
||||
final public function setPageSize($page_size) {
|
||||
$this->pageSize = $page_size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getPageSize() {
|
||||
if ($this->pageSize === null) {
|
||||
return $this->getDefaultPageSize();
|
||||
}
|
||||
|
||||
return $this->pageSize;
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
protected function getDefaultPageSize() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
abstract public function getResultBucketName();
|
||||
abstract protected function buildResultGroups(
|
||||
PhabricatorSavedQuery $query,
|
||||
array $objects);
|
||||
|
||||
final public function newResultGroups(
|
||||
PhabricatorSavedQuery $query,
|
||||
array $objects) {
|
||||
return $this->buildResultGroups($query, $objects);
|
||||
}
|
||||
|
||||
final public function getResultBucketKey() {
|
||||
return $this->getPhobjectClassConstant('BUCKETKEY');
|
||||
}
|
||||
|
||||
final protected function newGroup() {
|
||||
return new PhabricatorSearchResultBucketGroup();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSearchResultBucketGroup
|
||||
extends Phobject {
|
||||
|
||||
private $name;
|
||||
private $key;
|
||||
private $noDataString;
|
||||
private $objects;
|
||||
|
||||
public function setNoDataString($no_data_string) {
|
||||
$this->noDataString = $no_data_string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNoDataString() {
|
||||
return $this->noDataString;
|
||||
}
|
||||
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKey() {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setObjects(array $objects) {
|
||||
$this->objects = $objects;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjects() {
|
||||
return $this->objects;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue