mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-28 17:52:43 +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(
|
return array(
|
||||||
'names' => array(
|
'names' => array(
|
||||||
'core.pkg.css' => 'b7b8d101',
|
'core.pkg.css' => '204cabae',
|
||||||
'core.pkg.js' => '6972d365',
|
'core.pkg.js' => '6972d365',
|
||||||
'darkconsole.pkg.js' => 'e7393ebb',
|
'darkconsole.pkg.js' => 'e7393ebb',
|
||||||
'differential.pkg.css' => '7ba78475',
|
'differential.pkg.css' => '33da0633',
|
||||||
'differential.pkg.js' => 'd0cd0df6',
|
'differential.pkg.js' => 'd0cd0df6',
|
||||||
'diffusion.pkg.css' => '91c5d3a6',
|
'diffusion.pkg.css' => '91c5d3a6',
|
||||||
'diffusion.pkg.js' => '3a9a8bfa',
|
'diffusion.pkg.js' => '3a9a8bfa',
|
||||||
|
@ -57,7 +57,7 @@ return array(
|
||||||
'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
|
'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
|
||||||
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
|
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
|
||||||
'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
|
'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/core.css' => '5b7b8ff4',
|
||||||
'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
|
'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
|
||||||
'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
|
'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
|
||||||
|
@ -82,7 +82,7 @@ return array(
|
||||||
'rsrc/css/application/paste/paste.css' => '1898e534',
|
'rsrc/css/application/paste/paste.css' => '1898e534',
|
||||||
'rsrc/css/application/people/people-profile.css' => '2473d929',
|
'rsrc/css/application/people/people-profile.css' => '2473d929',
|
||||||
'rsrc/css/application/phame/phame.css' => '737792d6',
|
'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-inline-comments.css' => '8e545e49',
|
||||||
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
|
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
|
||||||
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02',
|
'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/application/uiexample/example.css' => '528b19de',
|
||||||
'rsrc/css/core/core.css' => 'd0801452',
|
'rsrc/css/core/core.css' => 'd0801452',
|
||||||
'rsrc/css/core/remarkup.css' => '787105d6',
|
'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/core/z-index.css' => '5b6fcf3f',
|
||||||
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
||||||
'rsrc/css/font/font-aleo.css' => '8bdb2835',
|
'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/phui-two-column-view.css' => 'b9538af1',
|
||||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
|
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
|
||||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
|
'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/phui/workboards/phui-workpanel.css' => '92197373',
|
||||||
'rsrc/css/sprite-login.css' => '60e8560e',
|
'rsrc/css/sprite-login.css' => '60e8560e',
|
||||||
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
||||||
|
@ -550,7 +550,7 @@ return array(
|
||||||
'conpherence-update-css' => 'faf6be09',
|
'conpherence-update-css' => 'faf6be09',
|
||||||
'conpherence-widget-pane-css' => '775eaaba',
|
'conpherence-widget-pane-css' => '775eaaba',
|
||||||
'd3' => 'a11a5ff2',
|
'd3' => 'a11a5ff2',
|
||||||
'differential-changeset-view-css' => '3e3b0b76',
|
'differential-changeset-view-css' => '7bcbe615',
|
||||||
'differential-core-view-css' => '5b7b8ff4',
|
'differential-core-view-css' => '5b7b8ff4',
|
||||||
'differential-inline-comment-editor' => '64a5550f',
|
'differential-inline-comment-editor' => '64a5550f',
|
||||||
'differential-revision-add-comment-css' => 'c47f8c40',
|
'differential-revision-add-comment-css' => 'c47f8c40',
|
||||||
|
@ -804,7 +804,7 @@ return array(
|
||||||
'phabricator-zindex-css' => '5b6fcf3f',
|
'phabricator-zindex-css' => '5b6fcf3f',
|
||||||
'phame-css' => '737792d6',
|
'phame-css' => '737792d6',
|
||||||
'pholio-css' => 'ca89d380',
|
'pholio-css' => 'ca89d380',
|
||||||
'pholio-edit-css' => '3ad9d1ee',
|
'pholio-edit-css' => 'b15fec4a',
|
||||||
'pholio-inline-comments-css' => '8e545e49',
|
'pholio-inline-comments-css' => '8e545e49',
|
||||||
'phortune-credit-card-form' => '2290aeef',
|
'phortune-credit-card-form' => '2290aeef',
|
||||||
'phortune-credit-card-form-css' => '8391eb02',
|
'phortune-credit-card-form-css' => '8391eb02',
|
||||||
|
@ -858,7 +858,7 @@ return array(
|
||||||
'phui-two-column-view-css' => 'b9538af1',
|
'phui-two-column-view-css' => 'b9538af1',
|
||||||
'phui-workboard-color-css' => 'ac6fe6a7',
|
'phui-workboard-color-css' => 'ac6fe6a7',
|
||||||
'phui-workboard-view-css' => 'e6d89647',
|
'phui-workboard-view-css' => 'e6d89647',
|
||||||
'phui-workcard-view-css' => '3646fb96',
|
'phui-workcard-view-css' => '0c62d7c5',
|
||||||
'phui-workpanel-view-css' => '92197373',
|
'phui-workpanel-view-css' => '92197373',
|
||||||
'phuix-action-list-view' => 'b5c256b8',
|
'phuix-action-list-view' => 'b5c256b8',
|
||||||
'phuix-action-view' => '8cf6d262',
|
'phuix-action-view' => '8cf6d262',
|
||||||
|
@ -881,7 +881,7 @@ return array(
|
||||||
'sprite-menu-css' => '9dd65b92',
|
'sprite-menu-css' => '9dd65b92',
|
||||||
'sprite-tokens-css' => '4f399012',
|
'sprite-tokens-css' => '4f399012',
|
||||||
'syntax-default-css' => '9923583c',
|
'syntax-default-css' => '9923583c',
|
||||||
'syntax-highlighting-css' => '5101175d',
|
'syntax-highlighting-css' => '9fc496d5',
|
||||||
'tokens-css' => '3d0f239e',
|
'tokens-css' => '3d0f239e',
|
||||||
'typeahead-browse-css' => 'd8581d2c',
|
'typeahead-browse-css' => 'd8581d2c',
|
||||||
'unhandled-exception-css' => '4c96257a',
|
'unhandled-exception-css' => '4c96257a',
|
||||||
|
@ -1148,9 +1148,6 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'javelin-uri',
|
'javelin-uri',
|
||||||
),
|
),
|
||||||
'3e3b0b76' => array(
|
|
||||||
'phui-inline-comment-view-css',
|
|
||||||
),
|
|
||||||
'3f5d6dbf' => array(
|
'3f5d6dbf' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1243,9 +1240,6 @@ return array(
|
||||||
'javelin-typeahead-source',
|
'javelin-typeahead-source',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
'5101175d' => array(
|
|
||||||
'syntax-default-css',
|
|
||||||
),
|
|
||||||
'519705ea' => array(
|
'519705ea' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1495,6 +1489,9 @@ return array(
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
|
'7bcbe615' => array(
|
||||||
|
'phui-inline-comment-view-css',
|
||||||
|
),
|
||||||
'7cbe244b' => array(
|
'7cbe244b' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
|
@ -1660,6 +1657,9 @@ return array(
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-vector',
|
'javelin-vector',
|
||||||
),
|
),
|
||||||
|
'9fc496d5' => array(
|
||||||
|
'syntax-default-css',
|
||||||
|
),
|
||||||
'a0b57eb8' => array(
|
'a0b57eb8' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'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())
|
$key = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->withKeys(array($public_key))
|
->withKeys(array($public_key))
|
||||||
|
->withIsActive(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$key) {
|
if (!$key) {
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
@ -6,6 +6,7 @@ require_once $root.'/scripts/__init_script__.php';
|
||||||
|
|
||||||
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withIsActive(true)
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
if (!$keys) {
|
if (!$keys) {
|
||||||
|
|
|
@ -361,6 +361,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php',
|
'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php',
|
||||||
'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php',
|
'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php',
|
||||||
'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php',
|
'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php',
|
||||||
|
'DifferentialBlockingReviewerDatasource' => 'applications/differential/typeahead/DifferentialBlockingReviewerDatasource.php',
|
||||||
'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php',
|
'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php',
|
||||||
'DifferentialChangeDetailMailView' => 'applications/differential/mail/DifferentialChangeDetailMailView.php',
|
'DifferentialChangeDetailMailView' => 'applications/differential/mail/DifferentialChangeDetailMailView.php',
|
||||||
'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php',
|
'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php',
|
||||||
|
@ -435,6 +436,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php',
|
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php',
|
||||||
'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php',
|
'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php',
|
||||||
'DifferentialEditPolicyField' => 'applications/differential/customfield/DifferentialEditPolicyField.php',
|
'DifferentialEditPolicyField' => 'applications/differential/customfield/DifferentialEditPolicyField.php',
|
||||||
|
'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php',
|
||||||
'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php',
|
'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php',
|
||||||
'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php',
|
'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php',
|
||||||
'DifferentialFindConduitAPIMethod' => 'applications/differential/conduit/DifferentialFindConduitAPIMethod.php',
|
'DifferentialFindConduitAPIMethod' => 'applications/differential/conduit/DifferentialFindConduitAPIMethod.php',
|
||||||
|
@ -491,9 +493,13 @@ phutil_register_library_map(array(
|
||||||
'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
|
'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
|
||||||
'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
|
'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
|
||||||
'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.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',
|
'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php',
|
||||||
'DifferentialReviewedByField' => 'applications/differential/customfield/DifferentialReviewedByField.php',
|
'DifferentialReviewedByField' => 'applications/differential/customfield/DifferentialReviewedByField.php',
|
||||||
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
||||||
|
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
|
||||||
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
||||||
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
|
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
|
||||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
|
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
|
||||||
|
@ -533,6 +539,8 @@ phutil_register_library_map(array(
|
||||||
'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
|
'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
|
||||||
'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
|
'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
|
||||||
'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.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',
|
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
|
||||||
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
|
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
|
||||||
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
|
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
|
||||||
|
@ -1869,12 +1877,19 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
|
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
|
||||||
'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php',
|
'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php',
|
||||||
'PhabricatorAuthSSHKeyController' => 'applications/auth/controller/PhabricatorAuthSSHKeyController.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',
|
'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php',
|
||||||
|
'PhabricatorAuthSSHKeyEditor' => 'applications/auth/editor/PhabricatorAuthSSHKeyEditor.php',
|
||||||
'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php',
|
'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php',
|
||||||
|
'PhabricatorAuthSSHKeyListController' => 'applications/auth/controller/PhabricatorAuthSSHKeyListController.php',
|
||||||
'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php',
|
'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php',
|
||||||
'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.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',
|
'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',
|
'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php',
|
||||||
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
|
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
|
||||||
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
|
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
|
||||||
|
@ -2161,7 +2176,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php',
|
'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php',
|
||||||
'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php',
|
'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php',
|
||||||
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
|
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
|
||||||
'PhabricatorCredentialsUsedByObjectEdgeType' => 'applications/passphrase/edge/PhabricatorCredentialsUsedByObjectEdgeType.php',
|
|
||||||
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
|
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
|
||||||
'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
|
'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
|
||||||
'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
|
'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
|
||||||
|
@ -2779,6 +2793,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php',
|
'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php',
|
||||||
'PhabricatorOAuthServerEditEngine' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php',
|
'PhabricatorOAuthServerEditEngine' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php',
|
||||||
'PhabricatorOAuthServerEditor' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditor.php',
|
'PhabricatorOAuthServerEditor' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditor.php',
|
||||||
|
'PhabricatorOAuthServerSchemaSpec' => 'applications/oauthserver/query/PhabricatorOAuthServerSchemaSpec.php',
|
||||||
'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php',
|
'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php',
|
||||||
'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php',
|
'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php',
|
||||||
'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php',
|
'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php',
|
||||||
|
@ -2802,7 +2817,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php',
|
'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php',
|
||||||
'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php',
|
'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php',
|
||||||
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
|
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
|
||||||
'PhabricatorObjectUsesCredentialsEdgeType' => 'applications/transactions/edges/PhabricatorObjectUsesCredentialsEdgeType.php',
|
|
||||||
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
|
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
|
||||||
'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php',
|
'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php',
|
||||||
'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php',
|
'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php',
|
||||||
|
@ -2833,6 +2847,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
|
'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
|
||||||
'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php',
|
'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php',
|
||||||
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
|
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
|
||||||
|
'PhabricatorOwnersPackageRemarkupRule' => 'applications/owners/remarkup/PhabricatorOwnersPackageRemarkupRule.php',
|
||||||
'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
|
'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
|
||||||
'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php',
|
'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php',
|
||||||
'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php',
|
'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php',
|
||||||
|
@ -3316,6 +3331,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
|
'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
|
||||||
'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
|
'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
|
||||||
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.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',
|
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
|
||||||
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
|
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
|
||||||
'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
|
'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
|
||||||
|
@ -4554,6 +4571,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialAuthorField' => 'DifferentialCustomField',
|
'DifferentialAuthorField' => 'DifferentialCustomField',
|
||||||
'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField',
|
'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField',
|
||||||
'DifferentialBlockHeraldAction' => 'HeraldAction',
|
'DifferentialBlockHeraldAction' => 'HeraldAction',
|
||||||
|
'DifferentialBlockingReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'DifferentialBranchField' => 'DifferentialCustomField',
|
'DifferentialBranchField' => 'DifferentialCustomField',
|
||||||
'DifferentialChangeDetailMailView' => 'DifferentialMailView',
|
'DifferentialChangeDetailMailView' => 'DifferentialMailView',
|
||||||
'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
|
'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
|
||||||
|
@ -4638,6 +4656,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
|
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
|
||||||
'DifferentialDraft' => 'DifferentialDAO',
|
'DifferentialDraft' => 'DifferentialDAO',
|
||||||
'DifferentialEditPolicyField' => 'DifferentialCoreCustomField',
|
'DifferentialEditPolicyField' => 'DifferentialCoreCustomField',
|
||||||
|
'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'DifferentialFieldParseException' => 'Exception',
|
'DifferentialFieldParseException' => 'Exception',
|
||||||
'DifferentialFieldValidationException' => 'Exception',
|
'DifferentialFieldValidationException' => 'Exception',
|
||||||
'DifferentialFindConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
'DifferentialFindConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||||
|
@ -4700,9 +4719,13 @@ phutil_register_library_map(array(
|
||||||
'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
|
'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
|
||||||
'DifferentialRepositoryLookup' => 'Phobject',
|
'DifferentialRepositoryLookup' => 'Phobject',
|
||||||
'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
|
'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
|
||||||
|
'DifferentialResponsibleDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
|
'DifferentialResponsibleUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
|
'DifferentialResponsibleViewerFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
|
'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
|
||||||
'DifferentialReviewedByField' => 'DifferentialCoreCustomField',
|
'DifferentialReviewedByField' => 'DifferentialCoreCustomField',
|
||||||
'DifferentialReviewer' => 'Phobject',
|
'DifferentialReviewer' => 'Phobject',
|
||||||
|
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
||||||
'DifferentialReviewerStatus' => 'Phobject',
|
'DifferentialReviewerStatus' => 'Phobject',
|
||||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
|
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
|
||||||
|
@ -4757,6 +4780,8 @@ phutil_register_library_map(array(
|
||||||
'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
|
'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
|
||||||
'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField',
|
'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField',
|
||||||
|
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
|
||||||
|
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
|
||||||
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
|
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
|
||||||
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'DifferentialRevisionStatus' => 'Phobject',
|
'DifferentialRevisionStatus' => 'Phobject',
|
||||||
|
@ -6286,14 +6311,22 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthDAO',
|
'PhabricatorAuthDAO',
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
'PhabricatorDestructibleInterface',
|
'PhabricatorDestructibleInterface',
|
||||||
|
'PhabricatorApplicationTransactionInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController',
|
'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorAuthSSHKeyDeleteController' => 'PhabricatorAuthSSHKeyController',
|
'PhabricatorAuthSSHKeyDeactivateController' => 'PhabricatorAuthSSHKeyController',
|
||||||
'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController',
|
'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController',
|
||||||
|
'PhabricatorAuthSSHKeyEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController',
|
'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController',
|
||||||
|
'PhabricatorAuthSSHKeyListController' => 'PhabricatorAuthSSHKeyController',
|
||||||
'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType',
|
'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType',
|
||||||
'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
'PhabricatorAuthSSHKeyReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
|
||||||
|
'PhabricatorAuthSSHKeySearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'PhabricatorAuthSSHKeyTableView' => 'AphrontView',
|
'PhabricatorAuthSSHKeyTableView' => 'AphrontView',
|
||||||
|
'PhabricatorAuthSSHKeyTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
|
'PhabricatorAuthSSHKeyTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
|
'PhabricatorAuthSSHKeyViewController' => 'PhabricatorAuthSSHKeyController',
|
||||||
'PhabricatorAuthSSHPublicKey' => 'Phobject',
|
'PhabricatorAuthSSHPublicKey' => 'Phobject',
|
||||||
'PhabricatorAuthSession' => array(
|
'PhabricatorAuthSession' => array(
|
||||||
'PhabricatorAuthDAO',
|
'PhabricatorAuthDAO',
|
||||||
|
@ -6643,7 +6676,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorCountdownView' => 'AphrontView',
|
'PhabricatorCountdownView' => 'AphrontView',
|
||||||
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
|
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
|
||||||
'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType',
|
|
||||||
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
|
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
|
||||||
'PhabricatorCustomField' => 'Phobject',
|
'PhabricatorCustomField' => 'Phobject',
|
||||||
'PhabricatorCustomFieldAttachment' => 'Phobject',
|
'PhabricatorCustomFieldAttachment' => 'Phobject',
|
||||||
|
@ -7342,6 +7374,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhabricatorOAuthServerEditEngine' => 'PhabricatorEditEngine',
|
'PhabricatorOAuthServerEditEngine' => 'PhabricatorEditEngine',
|
||||||
'PhabricatorOAuthServerEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorOAuthServerEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
'PhabricatorOAuthServerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||||
'PhabricatorOAuthServerScope' => 'Phobject',
|
'PhabricatorOAuthServerScope' => 'Phobject',
|
||||||
'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
|
'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
|
'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
|
||||||
|
@ -7368,7 +7401,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule',
|
'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule',
|
||||||
'PhabricatorObjectSelectorDialog' => 'Phobject',
|
'PhabricatorObjectSelectorDialog' => 'Phobject',
|
||||||
'PhabricatorObjectUsesCredentialsEdgeType' => 'PhabricatorEdgeType',
|
|
||||||
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
|
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
|
||||||
'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource',
|
'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource',
|
||||||
'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock',
|
'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock',
|
||||||
|
@ -7411,6 +7443,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
|
'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
|
||||||
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
'PhabricatorOwnersPackageRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||||
'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase',
|
'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction',
|
'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
|
@ -8007,6 +8040,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
|
'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
|
||||||
'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
'PhabricatorSearchRelationship' => 'Phobject',
|
'PhabricatorSearchRelationship' => 'Phobject',
|
||||||
|
'PhabricatorSearchResultBucket' => 'Phobject',
|
||||||
|
'PhabricatorSearchResultBucketGroup' => 'Phobject',
|
||||||
'PhabricatorSearchResultView' => 'AphrontView',
|
'PhabricatorSearchResultView' => 'AphrontView',
|
||||||
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||||
'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
|
'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
|
||||||
|
|
|
@ -146,6 +146,7 @@ final class AlmanacDeviceViewController
|
||||||
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withObjectPHIDs(array($device_phid))
|
->withObjectPHIDs(array($device_phid))
|
||||||
|
->withIsActive(true)
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
$table = id(new PhabricatorAuthSSHKeyTableView())
|
$table = id(new PhabricatorAuthSSHKeyTableView())
|
||||||
|
@ -156,38 +157,13 @@ final class AlmanacDeviceViewController
|
||||||
->setShowTrusted(true)
|
->setShowTrusted(true)
|
||||||
->setNoDataString(pht('This device has no associated SSH public keys.'));
|
->setNoDataString(pht('This device has no associated SSH public keys.'));
|
||||||
|
|
||||||
try {
|
$menu_button = PhabricatorAuthSSHKeyTableView::newKeyActionsMenu(
|
||||||
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
|
$viewer,
|
||||||
$can_generate = true;
|
$device);
|
||||||
} catch (Exception $ex) {
|
|
||||||
$can_generate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$generate_uri = '/auth/sshkey/generate/?objectPHID='.$device_phid;
|
|
||||||
$upload_uri = '/auth/sshkey/upload/?objectPHID='.$device_phid;
|
|
||||||
|
|
||||||
$header = id(new PHUIHeaderView())
|
$header = id(new PHUIHeaderView())
|
||||||
->setHeader(pht('SSH Public Keys'))
|
->setHeader(pht('SSH Public Keys'))
|
||||||
->addActionLink(
|
->addActionLink($menu_button);
|
||||||
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')));
|
|
||||||
|
|
||||||
return id(new PHUIObjectBoxView())
|
return id(new PHUIObjectBoxView())
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
|
|
|
@ -141,6 +141,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
$public_key = id(new PhabricatorAuthSSHKeyQuery())
|
$public_key = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer($this->getViewer())
|
->setViewer($this->getViewer())
|
||||||
->withKeys(array($key_object))
|
->withKeys(array($key_object))
|
||||||
|
->withIsActive(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if (!$public_key) {
|
if (!$public_key) {
|
||||||
|
|
|
@ -35,6 +35,11 @@ final class AlmanacManagementTrustKeyWorkflow
|
||||||
pht('No public key exists with ID "%s".', $id));
|
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()) {
|
if ($key->getIsTrusted()) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht('Public key with ID %s is already trusted.', $id));
|
pht('Public key with ID %s is already trusted.', $id));
|
||||||
|
|
|
@ -227,6 +227,14 @@ final class AlmanacDevice
|
||||||
return $this->getName();
|
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 )----------------------------------- */
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -159,8 +159,18 @@ final class PhabricatorAuditEditor
|
||||||
$requests = mpull($requests, null, 'getAuditorPHID');
|
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||||
foreach ($add as $phid) {
|
foreach ($add as $phid) {
|
||||||
if (isset($requests[$phid])) {
|
if (isset($requests[$phid])) {
|
||||||
|
$request = $requests[$phid];
|
||||||
|
|
||||||
|
// Only update an existing request if the current status is not
|
||||||
|
// an interesting status.
|
||||||
|
if ($request->isInteresting()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$request = id(new PhabricatorRepositoryAuditRequest())
|
||||||
|
->setCommitPHID($object->getPHID())
|
||||||
|
->setAuditorPHID($phid);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->getIsHeraldEditor()) {
|
if ($this->getIsHeraldEditor()) {
|
||||||
$audit_requested = $xaction->getMetadataValue('auditStatus');
|
$audit_requested = $xaction->getMetadataValue('auditStatus');
|
||||||
|
@ -170,12 +180,13 @@ final class PhabricatorAuditEditor
|
||||||
$audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED;
|
$audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED;
|
||||||
$audit_reason = $this->getAuditReasons($phid);
|
$audit_reason = $this->getAuditReasons($phid);
|
||||||
}
|
}
|
||||||
$requests[] = id(new PhabricatorRepositoryAuditRequest())
|
|
||||||
->setCommitPHID($object->getPHID())
|
$request
|
||||||
->setAuditorPHID($phid)
|
|
||||||
->setAuditStatus($audit_requested)
|
->setAuditStatus($audit_requested)
|
||||||
->setAuditReasons($audit_reason)
|
->setAuditReasons($audit_reason)
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
$requests[$phid] = $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
$object->attachAudits($requests);
|
$object->attachAudits($requests);
|
||||||
|
|
|
@ -75,10 +75,14 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
|
||||||
'multifactor/'
|
'multifactor/'
|
||||||
=> 'PhabricatorAuthNeedsMultiFactorController',
|
=> 'PhabricatorAuthNeedsMultiFactorController',
|
||||||
'sshkey/' => array(
|
'sshkey/' => array(
|
||||||
|
$this->getQueryRoutePattern('for/(?P<forPHID>[^/]+)/')
|
||||||
|
=> 'PhabricatorAuthSSHKeyListController',
|
||||||
'generate/' => 'PhabricatorAuthSSHKeyGenerateController',
|
'generate/' => 'PhabricatorAuthSSHKeyGenerateController',
|
||||||
'upload/' => 'PhabricatorAuthSSHKeyEditController',
|
'upload/' => 'PhabricatorAuthSSHKeyEditController',
|
||||||
'edit/(?P<id>\d+)/' => '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();
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
$query = id(new PhabricatorAuthSSHKeyQuery())
|
$query = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer($viewer);
|
->setViewer($viewer)
|
||||||
|
->withIsActive(true);
|
||||||
|
|
||||||
$ids = $request->getValue('ids');
|
$ids = $request->getValue('ids');
|
||||||
if ($ids !== null) {
|
if ($ids !== null) {
|
||||||
|
|
|
@ -3,18 +3,34 @@
|
||||||
abstract class PhabricatorAuthSSHKeyController
|
abstract class PhabricatorAuthSSHKeyController
|
||||||
extends PhabricatorAuthController {
|
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();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$object = id(new PhabricatorObjectQuery())
|
$query = id(new PhabricatorObjectQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withPHIDs(array($object_phid))
|
->withPHIDs(array($object_phid));
|
||||||
->requireCapabilities(
|
|
||||||
|
if ($need_edit) {
|
||||||
|
$query->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT,
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
))
|
));
|
||||||
->executeOne();
|
}
|
||||||
|
|
||||||
|
$object = $query->executeOne();
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -25,9 +41,38 @@ abstract class PhabricatorAuthSSHKeyController
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return id(new PhabricatorAuthSSHKey())
|
$this->keyObject = $object;
|
||||||
->setObjectPHID($object_phid)
|
|
||||||
->attachObject($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
|
<?php
|
||||||
|
|
||||||
final class PhabricatorAuthSSHKeyDeleteController
|
final class PhabricatorAuthSSHKeyDeactivateController
|
||||||
extends PhabricatorAuthSSHKeyController {
|
extends PhabricatorAuthSSHKeyController {
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
@ -19,7 +19,7 @@ final class PhabricatorAuthSSHKeyDeleteController
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
$cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer);
|
$cancel_uri = $key->getURI();
|
||||||
|
|
||||||
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
|
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
|
||||||
$viewer,
|
$viewer,
|
||||||
|
@ -27,21 +27,33 @@ final class PhabricatorAuthSSHKeyDeleteController
|
||||||
$cancel_uri);
|
$cancel_uri);
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
// TODO: It would be nice to write an edge transaction here or something.
|
$xactions = array();
|
||||||
$key->delete();
|
|
||||||
|
$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);
|
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = phutil_tag('strong', array(), $key->getName());
|
$name = phutil_tag('strong', array(), $key->getName());
|
||||||
|
|
||||||
return $this->newDialog()
|
return $this->newDialog()
|
||||||
->setTitle(pht('Really delete SSH Public Key?'))
|
->setTitle(pht('Deactivate SSH Public Key'))
|
||||||
->appendParagraph(
|
->appendParagraph(
|
||||||
pht(
|
pht(
|
||||||
'The key "%s" will be permanently deleted, and you will not longer '.
|
'The key "%s" will be permanently deactivated, and you will no '.
|
||||||
'be able to use the corresponding private key to authenticate.',
|
'longer be able to use the corresponding private key to '.
|
||||||
|
'authenticate.',
|
||||||
$name))
|
$name))
|
||||||
->addSubmitButton(pht('Delete Public Key'))
|
->addSubmitButton(pht('Deactivate Public Key'))
|
||||||
->addCancelButton($cancel_uri);
|
->addCancelButton($cancel_uri);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,51 +59,45 @@ final class PhabricatorAuthSSHKeyEditController
|
||||||
$v_key = $key->getEntireKey();
|
$v_key = $key->getEntireKey();
|
||||||
$e_key = strlen($v_key) ? null : true;
|
$e_key = strlen($v_key) ? null : true;
|
||||||
|
|
||||||
$errors = array();
|
$validation_exception = null;
|
||||||
if ($request->isFormPost()) {
|
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_name = $request->getStr('name');
|
||||||
$v_key = $request->getStr('key');
|
$v_key = $request->getStr('key');
|
||||||
|
|
||||||
if (!strlen($v_name)) {
|
$xactions = array();
|
||||||
$errors[] = pht('You must provide a name for this public key.');
|
|
||||||
$e_name = pht('Required');
|
if (!$key->getID()) {
|
||||||
} else {
|
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||||
$key->setName($v_name);
|
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strlen($v_key)) {
|
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||||
$errors[] = pht('You must provide a public key.');
|
->setTransactionType($type_name)
|
||||||
$e_key = pht('Required');
|
->setNewValue($v_name);
|
||||||
} else {
|
|
||||||
|
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
|
||||||
|
->setTransactionType($type_key)
|
||||||
|
->setNewValue($v_key);
|
||||||
|
|
||||||
|
$editor = id(new PhabricatorAuthSSHKeyEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($v_key);
|
$editor->applyTransactions($key, $xactions);
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($key->getURI());
|
||||||
$type = $public_key->getType();
|
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||||
$body = $public_key->getBody();
|
$validation_exception = $ex;
|
||||||
$comment = $public_key->getComment();
|
$e_name = $ex->getShortMessage($type_name);
|
||||||
|
$e_key = $ex->getShortMessage($type_key);
|
||||||
$key->setKeyType($type);
|
|
||||||
$key->setKeyBody($body);
|
|
||||||
$key->setKeyComment($comment);
|
|
||||||
|
|
||||||
$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.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +128,7 @@ final class PhabricatorAuthSSHKeyEditController
|
||||||
return $this->newDialog()
|
return $this->newDialog()
|
||||||
->setTitle($title)
|
->setTitle($title)
|
||||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
->setErrors($errors)
|
->setValidationException($validation_exception)
|
||||||
->appendForm($form)
|
->appendForm($form)
|
||||||
->addSubmitButton($save_button)
|
->addSubmitButton($save_button)
|
||||||
->addCancelButton($cancel_uri);
|
->addCancelButton($cancel_uri);
|
||||||
|
|
|
@ -36,13 +36,31 @@ final class PhabricatorAuthSSHKeyGenerateController
|
||||||
|
|
||||||
$type = $public_key->getType();
|
$type = $public_key->getType();
|
||||||
$body = $public_key->getBody();
|
$body = $public_key->getBody();
|
||||||
|
$comment = pht('Generated');
|
||||||
|
|
||||||
$key
|
$entire_key = "{$type} {$body} {$comment}";
|
||||||
->setName($default_name)
|
|
||||||
->setKeyType($type)
|
$type_create = PhabricatorTransactions::TYPE_CREATE;
|
||||||
->setKeyBody($body)
|
$type_name = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
|
||||||
->setKeyComment(pht('Generated'))
|
$type_key = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
|
||||||
->save();
|
|
||||||
|
$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
|
// NOTE: We're disabling workflow on submit so the download works. We're
|
||||||
// disabling workflow on cancel so the page reloads, showing the new
|
// 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) {
|
foreach ($handles as $phid => $handle) {
|
||||||
$key = $objects[$phid];
|
$key = $objects[$phid];
|
||||||
$handle->setName(pht('SSH Key %d', $key->getID()));
|
$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 $phids;
|
||||||
private $objectPHIDs;
|
private $objectPHIDs;
|
||||||
private $keys;
|
private $keys;
|
||||||
|
private $isActive;
|
||||||
|
|
||||||
public function withIDs(array $ids) {
|
public function withIDs(array $ids) {
|
||||||
$this->ids = $ids;
|
$this->ids = $ids;
|
||||||
|
@ -29,6 +30,11 @@ final class PhabricatorAuthSSHKeyQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withIsActive($active) {
|
||||||
|
$this->isActive = $active;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function newResultObject() {
|
public function newResultObject() {
|
||||||
return new PhabricatorAuthSSHKey();
|
return new PhabricatorAuthSSHKey();
|
||||||
}
|
}
|
||||||
|
@ -100,6 +106,19 @@ final class PhabricatorAuthSSHKeyQuery
|
||||||
$where[] = implode(' OR ', $sql);
|
$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;
|
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 getSSHKeyDefaultName();
|
||||||
|
|
||||||
|
public function getSSHKeyNotifyPHIDs();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ final class PhabricatorAuthSSHKey
|
||||||
extends PhabricatorAuthDAO
|
extends PhabricatorAuthDAO
|
||||||
implements
|
implements
|
||||||
PhabricatorPolicyInterface,
|
PhabricatorPolicyInterface,
|
||||||
PhabricatorDestructibleInterface {
|
PhabricatorDestructibleInterface,
|
||||||
|
PhabricatorApplicationTransactionInterface {
|
||||||
|
|
||||||
protected $objectPHID;
|
protected $objectPHID;
|
||||||
protected $name;
|
protected $name;
|
||||||
|
@ -13,9 +14,28 @@ final class PhabricatorAuthSSHKey
|
||||||
protected $keyBody;
|
protected $keyBody;
|
||||||
protected $keyComment = '';
|
protected $keyComment = '';
|
||||||
protected $isTrusted = 0;
|
protected $isTrusted = 0;
|
||||||
|
protected $isActive;
|
||||||
|
|
||||||
private $object = self::ATTACHABLE;
|
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() {
|
protected function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
self::CONFIG_AUX_PHID => true,
|
self::CONFIG_AUX_PHID => true,
|
||||||
|
@ -26,13 +46,19 @@ final class PhabricatorAuthSSHKey
|
||||||
'keyBody' => 'text',
|
'keyBody' => 'text',
|
||||||
'keyComment' => 'text255',
|
'keyComment' => 'text255',
|
||||||
'isTrusted' => 'bool',
|
'isTrusted' => 'bool',
|
||||||
|
'isActive' => 'bool?',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_object' => array(
|
'key_object' => array(
|
||||||
'columns' => array('objectPHID'),
|
'columns' => array('objectPHID'),
|
||||||
),
|
),
|
||||||
'key_unique' => array(
|
'key_active' => array(
|
||||||
'columns' => array('keyIndex'),
|
'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,
|
'unique' => true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -44,6 +70,12 @@ final class PhabricatorAuthSSHKey
|
||||||
return parent::save();
|
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() {
|
public function toPublicKey() {
|
||||||
return PhabricatorAuthSSHPublicKey::newFromStoredKey($this);
|
return PhabricatorAuthSSHPublicKey::newFromStoredKey($this);
|
||||||
}
|
}
|
||||||
|
@ -71,6 +103,11 @@ final class PhabricatorAuthSSHKey
|
||||||
PhabricatorAuthSSHKeyPHIDType::TYPECONST);
|
PhabricatorAuthSSHKeyPHIDType::TYPECONST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
$id = $this->getID();
|
||||||
|
return "/auth/sshkey/view/{$id}/";
|
||||||
|
}
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,14 +119,29 @@ final class PhabricatorAuthSSHKey
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPolicy($capability) {
|
public function getPolicy($capability) {
|
||||||
|
if (!$this->getIsActive()) {
|
||||||
|
if ($capability == PhabricatorPolicyCapability::CAN_EDIT) {
|
||||||
|
return PhabricatorPolicies::POLICY_NOONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->getObject()->getPolicy($capability);
|
return $this->getObject()->getPolicy($capability);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
if (!$this->getIsActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
|
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function describeAutomaticCapability($capability) {
|
public function describeAutomaticCapability($capability) {
|
||||||
|
if (!$this->getIsACtive()) {
|
||||||
|
return pht(
|
||||||
|
'Deactivated SSH keys can not be edited or reactivated.');
|
||||||
|
}
|
||||||
|
|
||||||
return pht(
|
return pht(
|
||||||
'SSH keys inherit the policies of the user or object they authenticate.');
|
'SSH keys inherit the policies of the user or object they authenticate.');
|
||||||
}
|
}
|
||||||
|
@ -105,4 +157,26 @@ final class PhabricatorAuthSSHKey
|
||||||
$this->saveTransaction();
|
$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 $showTrusted;
|
||||||
private $showID;
|
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) {
|
public function setNoDataString($no_data_string) {
|
||||||
$this->noDataString = $no_data_string;
|
$this->noDataString = $no_data_string;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -38,12 +90,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
||||||
$keys = $this->keys;
|
$keys = $this->keys;
|
||||||
$viewer = $this->getUser();
|
$viewer = $this->getUser();
|
||||||
|
|
||||||
if ($this->canEdit) {
|
|
||||||
$delete_class = 'small grey button';
|
|
||||||
} else {
|
|
||||||
$delete_class = 'small grey button disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
$trusted_icon = id(new PHUIIconView())
|
$trusted_icon = id(new PHUIIconView())
|
||||||
->setIcon('fa-star blue');
|
->setIcon('fa-star blue');
|
||||||
$untrusted_icon = id(new PHUIIconView())
|
$untrusted_icon = id(new PHUIIconView())
|
||||||
|
@ -56,22 +102,13 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
||||||
javelin_tag(
|
javelin_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
'href' => '/auth/sshkey/edit/'.$key->getID().'/',
|
'href' => $key->getURI(),
|
||||||
'sigil' => 'workflow',
|
|
||||||
),
|
),
|
||||||
$key->getName()),
|
$key->getName()),
|
||||||
$key->getIsTrusted() ? $trusted_icon : $untrusted_icon,
|
$key->getIsTrusted() ? $trusted_icon : $untrusted_icon,
|
||||||
$key->getKeyComment(),
|
$key->getKeyComment(),
|
||||||
$key->getKeyType(),
|
$key->getKeyType(),
|
||||||
phabricator_datetime($key->getDateCreated(), $viewer),
|
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('Comment'),
|
||||||
pht('Type'),
|
pht('Type'),
|
||||||
pht('Added'),
|
pht('Added'),
|
||||||
null,
|
|
||||||
))
|
))
|
||||||
->setColumnVisibility(
|
->setColumnVisibility(
|
||||||
array(
|
array(
|
||||||
|
@ -101,7 +137,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'right',
|
'right',
|
||||||
'action',
|
|
||||||
));
|
));
|
||||||
|
|
||||||
return $table;
|
return $table;
|
||||||
|
|
|
@ -21,22 +21,4 @@ final class PhabricatorCalendarHoliday extends PhabricatorCalendarDAO {
|
||||||
) + parent::getConfiguration();
|
) + 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();
|
->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())
|
$stored_key = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->withKeys(array($public_key))
|
->withKeys(array($public_key))
|
||||||
|
->withIsActive(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$stored_key) {
|
if (!$stored_key) {
|
||||||
return array(
|
return array(
|
||||||
|
|
|
@ -186,6 +186,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
||||||
'Configuration of the notification server has changed substantially. '.
|
'Configuration of the notification server has changed substantially. '.
|
||||||
'For discussion, see T10794.');
|
'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(
|
$ancient_config += array(
|
||||||
'phid.external-loaders' =>
|
'phid.external-loaders' =>
|
||||||
pht(
|
pht(
|
||||||
|
@ -314,6 +318,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
||||||
'metamta.differential.unified-comment-context' => pht(
|
'metamta.differential.unified-comment-context' => pht(
|
||||||
'Inline comments are now always rendered with a limited amount '.
|
'Inline comments are now always rendered with a limited amount '.
|
||||||
'of context.'),
|
'of context.'),
|
||||||
|
|
||||||
|
'differential.days-fresh' => $stale_reason,
|
||||||
|
'differential.days-stale' => $stale_reason,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $ancient_config;
|
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) {
|
public function loadStatus(PhabricatorUser $user) {
|
||||||
|
$revisions = self::loadNeedAttentionRevisions($user);
|
||||||
$limit = self::MAX_STATUS_ITEMS;
|
$limit = self::MAX_STATUS_ITEMS;
|
||||||
|
|
||||||
$revisions = id(new DifferentialRevisionQuery())
|
if (count($revisions) >= $limit) {
|
||||||
->setViewer($user)
|
$display_count = ($limit - 1);
|
||||||
->withResponsibleUsers(array($user->getPHID()))
|
$display_label = pht(
|
||||||
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
|
'%s+ Active Review(s)',
|
||||||
->needRelationships(true)
|
new PhutilNumber($display_count));
|
||||||
->setLimit($limit)
|
} else {
|
||||||
->execute();
|
$display_count = count($revisions);
|
||||||
|
$display_label = pht(
|
||||||
|
'%s Review(s) Need Attention',
|
||||||
|
new PhutilNumber($display_count));
|
||||||
|
}
|
||||||
|
|
||||||
$status = array();
|
$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())
|
$status[] = id(new PhabricatorApplicationStatusView())
|
||||||
->setType($type)
|
->setType(PhabricatorApplicationStatusView::TYPE_WARNING)
|
||||||
->setText($all_count_str)
|
->setText($display_label)
|
||||||
->setCount($all_count);
|
->setCount($display_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,25 +229,6 @@ final class PhabricatorDifferentialConfigOptions
|
||||||
"\n\n".
|
"\n\n".
|
||||||
'This sort of workflow is very unusual. Very few installs should '.
|
'This sort of workflow is very unusual. Very few installs should '.
|
||||||
'need to change this option.')),
|
'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(
|
$this->newOption(
|
||||||
'metamta.differential.subject-prefix',
|
'metamta.differential.subject-prefix',
|
||||||
'string',
|
'string',
|
||||||
|
|
|
@ -35,27 +35,39 @@ abstract class DifferentialCustomField
|
||||||
protected function parseObjectList(
|
protected function parseObjectList(
|
||||||
$value,
|
$value,
|
||||||
array $types,
|
array $types,
|
||||||
$allow_partial = false) {
|
$allow_partial = false,
|
||||||
|
array $suffixes = array()) {
|
||||||
return id(new PhabricatorObjectListQuery())
|
return id(new PhabricatorObjectListQuery())
|
||||||
->setViewer($this->getViewer())
|
->setViewer($this->getViewer())
|
||||||
->setAllowedTypes($types)
|
->setAllowedTypes($types)
|
||||||
->setObjectList($value)
|
->setObjectList($value)
|
||||||
->setAllowPartialResults($allow_partial)
|
->setAllowPartialResults($allow_partial)
|
||||||
|
->setSuffixes($suffixes)
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function renderObjectList(array $handles) {
|
protected function renderObjectList(
|
||||||
|
array $handles,
|
||||||
|
array $suffixes = array()) {
|
||||||
|
|
||||||
if (!$handles) {
|
if (!$handles) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$out = array();
|
$out = array();
|
||||||
foreach ($handles as $handle) {
|
foreach ($handles as $handle) {
|
||||||
|
$phid = $handle->getPHID();
|
||||||
|
|
||||||
if ($handle->getPolicyFiltered()) {
|
if ($handle->getPolicyFiltered()) {
|
||||||
$out[] = $handle->getPHID();
|
$token = $phid;
|
||||||
} else if ($handle->isComplete()) {
|
} else if ($handle->isComplete()) {
|
||||||
$out[] = $handle->getObjectName();
|
$token = $handle->getCommandLineObjectName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$suffix = idx($suffixes, $phid);
|
||||||
|
$token = $token.$suffix;
|
||||||
|
|
||||||
|
$out[] = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $out);
|
return implode(', ', $out);
|
||||||
|
|
|
@ -8,7 +8,7 @@ final class DifferentialProjectReviewersField
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFieldName() {
|
public function getFieldName() {
|
||||||
return pht('Project Reviewers');
|
return pht('Group Reviewers');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFieldDescription() {
|
public function getFieldDescription() {
|
||||||
|
|
|
@ -36,43 +36,82 @@ final class DifferentialReviewersField
|
||||||
}
|
}
|
||||||
|
|
||||||
public function readValueFromRequest(AphrontRequest $request) {
|
public function readValueFromRequest(AphrontRequest $request) {
|
||||||
// Compute a new set of reviewer objects. For reviewers who haven't been
|
$datasource = id(new DifferentialBlockingReviewerDatasource())
|
||||||
// added or removed, retain their existing status. Also, respect the new
|
->setViewer($request->getViewer());
|
||||||
// order.
|
|
||||||
|
|
||||||
$old_status = $this->getValue();
|
|
||||||
$old_status = mpull($old_status, null, 'getReviewerPHID');
|
|
||||||
|
|
||||||
$new_phids = $request->getArr($this->getFieldKey());
|
$new_phids = $request->getArr($this->getFieldKey());
|
||||||
$new_phids = array_fuse($new_phids);
|
$new_phids = $datasource->evaluateTokens($new_phids);
|
||||||
|
|
||||||
$new_status = array();
|
$reviewers = array();
|
||||||
foreach ($new_phids as $new_phid) {
|
foreach ($new_phids as $spec) {
|
||||||
if (empty($old_status[$new_phid])) {
|
if (!is_array($spec)) {
|
||||||
$new_status[$new_phid] = new DifferentialReviewer(
|
$reviewers[$spec] = DifferentialReviewerStatus::STATUS_ADDED;
|
||||||
$new_phid,
|
|
||||||
array(
|
|
||||||
'status' => DifferentialReviewerStatus::STATUS_ADDED,
|
|
||||||
));
|
|
||||||
} else {
|
} 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) {
|
public function renderEditControl(array $handles) {
|
||||||
$phids = array();
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||||
if ($this->getValue()) {
|
|
||||||
$phids = mpull($this->getValue(), 'getReviewerPHID');
|
$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())
|
return id(new AphrontFormTokenizerControl())
|
||||||
->setUser($this->getViewer())
|
->setUser($this->getViewer())
|
||||||
->setName($this->getFieldKey())
|
->setName($this->getFieldKey())
|
||||||
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
->setDatasource(new DifferentialReviewerDatasource())
|
||||||
->setValue($phids)
|
->setValue($value)
|
||||||
->setError($this->getFieldError())
|
->setError($this->getFieldError())
|
||||||
->setLabel($this->getFieldName());
|
->setLabel($this->getFieldName());
|
||||||
}
|
}
|
||||||
|
@ -141,12 +180,17 @@ final class DifferentialReviewersField
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseValueFromCommitMessage($value) {
|
public function parseValueFromCommitMessage($value) {
|
||||||
return $this->parseObjectList(
|
$results = $this->parseObjectList(
|
||||||
$value,
|
$value,
|
||||||
array(
|
array(
|
||||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||||
));
|
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
array('!'));
|
||||||
|
|
||||||
|
return $this->flattenReviewers($results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequiredHandlePHIDsForCommitMessage() {
|
public function getRequiredHandlePHIDsForCommitMessage() {
|
||||||
|
@ -154,29 +198,42 @@ final class DifferentialReviewersField
|
||||||
}
|
}
|
||||||
|
|
||||||
public function readValueFromCommitMessage($value) {
|
public function readValueFromCommitMessage($value) {
|
||||||
$current_reviewers = $this->getObject()->getReviewerStatus();
|
$value = $this->inflateReviewers($value);
|
||||||
$current_reviewers = mpull($current_reviewers, null, 'getReviewerPHID');
|
|
||||||
|
|
||||||
$reviewers = array();
|
$reviewers = array();
|
||||||
foreach ($value as $phid) {
|
foreach ($value as $spec) {
|
||||||
$reviewer = idx($current_reviewers, $phid);
|
$phid = $spec['phid'];
|
||||||
if ($reviewer) {
|
|
||||||
$reviewers[] = $reviewer;
|
$is_blocking = isset($spec['suffixes']['!']);
|
||||||
|
if ($is_blocking) {
|
||||||
|
$status = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||||
} else {
|
} else {
|
||||||
$data = array(
|
$status = DifferentialReviewerStatus::STATUS_ADDED;
|
||||||
'status' => DifferentialReviewerStatus::STATUS_ADDED,
|
|
||||||
);
|
|
||||||
$reviewers[] = new DifferentialReviewer($phid, $data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setValue($reviewers);
|
$reviewers[$phid] = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateReviewers(
|
||||||
|
$this->getObject()->getReviewerStatus(),
|
||||||
|
$reviewers);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderCommitMessageValue(array $handles) {
|
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) {
|
public function validateCommitMessageValue($value) {
|
||||||
|
@ -185,7 +242,9 @@ final class DifferentialReviewersField
|
||||||
$config_self_accept_key = 'differential.allow-self-accept';
|
$config_self_accept_key = 'differential.allow-self-accept';
|
||||||
$allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
|
$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) {
|
if (($phid == $author_phid) && !$allow_self_accept) {
|
||||||
throw new DifferentialFieldValidationException(
|
throw new DifferentialFieldValidationException(
|
||||||
pht('The author of a revision can not be a reviewer.'));
|
pht('The author of a revision can not be a reviewer.'));
|
||||||
|
@ -224,4 +283,44 @@ final class DifferentialReviewersField
|
||||||
return $warnings;
|
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(
|
array(
|
||||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||||
|
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ final class DifferentialTransactionEditor
|
||||||
private $isCloseByCommit;
|
private $isCloseByCommit;
|
||||||
private $repositoryPHIDOverride = false;
|
private $repositoryPHIDOverride = false;
|
||||||
private $didExpandInlineState = false;
|
private $didExpandInlineState = false;
|
||||||
|
private $affectedPaths;
|
||||||
|
|
||||||
public function getEditorApplicationClass() {
|
public function getEditorApplicationClass() {
|
||||||
return 'PhabricatorDifferentialApplication';
|
return 'PhabricatorDifferentialApplication';
|
||||||
|
@ -1200,7 +1201,13 @@ final class DifferentialTransactionEditor
|
||||||
$body = new PhabricatorMetaMTAMailBody();
|
$body = new PhabricatorMetaMTAMailBody();
|
||||||
$body->setViewer($this->requireActor());
|
$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;
|
$type_inline = DifferentialTransaction::TYPE_INLINE;
|
||||||
|
|
||||||
|
@ -1226,7 +1233,7 @@ final class DifferentialTransactionEditor
|
||||||
|
|
||||||
$body->addLinkSection(
|
$body->addLinkSection(
|
||||||
pht('REVISION DETAIL'),
|
pht('REVISION DETAIL'),
|
||||||
PhabricatorEnv::getProductionURI('/D'.$object->getID()));
|
$revision_uri);
|
||||||
|
|
||||||
$update_xaction = null;
|
$update_xaction = null;
|
||||||
foreach ($xactions as $xaction) {
|
foreach ($xactions as $xaction) {
|
||||||
|
@ -1481,6 +1488,181 @@ final class DifferentialTransactionEditor
|
||||||
return parent::shouldApplyHeraldRules($object, $xactions);
|
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(
|
protected function buildHeraldAdapter(
|
||||||
PhabricatorLiskDAO $object,
|
PhabricatorLiskDAO $object,
|
||||||
array $xactions) {
|
array $xactions) {
|
||||||
|
@ -1547,6 +1729,10 @@ final class DifferentialTransactionEditor
|
||||||
$paths[] = $path_prefix.'/'.$changeset->getFilename();
|
$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
|
// Mark this as also touching all parent paths, so you can see all pending
|
||||||
// changes to any file within a directory.
|
// changes to any file within a directory.
|
||||||
$all_paths = array();
|
$all_paths = array();
|
||||||
|
|
|
@ -22,7 +22,7 @@ final class DifferentialReviewersAddBlockingReviewersHeraldAction
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDatasource() {
|
protected function getDatasource() {
|
||||||
return new PhabricatorMetaMTAMailableDatasource();
|
return new DiffusionAuditorDatasource();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderActionDescription($value) {
|
public function renderActionDescription($value) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ final class DifferentialReviewersAddReviewersHeraldAction
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDatasource() {
|
protected function getDatasource() {
|
||||||
return new PhabricatorMetaMTAMailableDatasource();
|
return new DiffusionAuditorDatasource();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderActionDescription($value) {
|
public function renderActionDescription($value) {
|
||||||
|
|
|
@ -69,6 +69,7 @@ abstract class DifferentialReviewersHeraldAction
|
||||||
$allowed_types = array(
|
$allowed_types = array(
|
||||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||||
|
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
||||||
);
|
);
|
||||||
|
|
||||||
$targets = $this->loadStandardTargets($phids, $allowed_types, $current);
|
$targets = $this->loadStandardTargets($phids, $allowed_types, $current);
|
||||||
|
|
|
@ -91,18 +91,6 @@ final class DifferentialRevisionQuery
|
||||||
return $this;
|
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
|
* Filter results to revisions which CC one of the listed people. Calling this
|
||||||
* function will clear anything set by previous calls to @{method:withCCs}.
|
* 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.
|
* Set whether or not the query will load and attach relationships.
|
||||||
|
@ -371,6 +338,11 @@ final class DifferentialRevisionQuery
|
||||||
/* -( Query Execution )---------------------------------------------------- */
|
/* -( Query Execution )---------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function newResultObject() {
|
||||||
|
return new DifferentialRevision();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the query as configured, returning matching
|
* Execute the query as configured, returning matching
|
||||||
* @{class:DifferentialRevision} objects.
|
* @{class:DifferentialRevision} objects.
|
||||||
|
@ -379,11 +351,9 @@ final class DifferentialRevisionQuery
|
||||||
* @task exec
|
* @task exec
|
||||||
*/
|
*/
|
||||||
protected function loadPage() {
|
protected function loadPage() {
|
||||||
$table = new DifferentialRevision();
|
|
||||||
$conn_r = $table->establishConnection('r');
|
|
||||||
|
|
||||||
$data = $this->loadData();
|
$data = $this->loadData();
|
||||||
|
|
||||||
|
$table = $this->newResultObject();
|
||||||
return $table->loadAllFromArray($data);
|
return $table->loadAllFromArray($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,7 +489,7 @@ final class DifferentialRevisionQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadData() {
|
private function loadData() {
|
||||||
$table = new DifferentialRevision();
|
$table = $this->newResultObject();
|
||||||
$conn_r = $table->establishConnection('r');
|
$conn_r = $table->establishConnection('r');
|
||||||
|
|
||||||
$selects = array();
|
$selects = array();
|
||||||
|
@ -531,25 +501,17 @@ final class DifferentialRevisionQuery
|
||||||
$basic_authors = $this->authors;
|
$basic_authors = $this->authors;
|
||||||
$basic_reviewers = $this->reviewers;
|
$basic_reviewers = $this->reviewers;
|
||||||
|
|
||||||
$authority_projects = id(new PhabricatorProjectQuery())
|
|
||||||
->setViewer($this->getViewer())
|
|
||||||
->withMemberPHIDs($this->responsibles)
|
|
||||||
->execute();
|
|
||||||
$authority_phids = mpull($authority_projects, 'getPHID');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Build the query where the responsible users are authors.
|
// Build the query where the responsible users are authors.
|
||||||
$this->authors = array_merge($basic_authors, $this->responsibles);
|
$this->authors = array_merge($basic_authors, $this->responsibles);
|
||||||
|
|
||||||
$this->reviewers = $basic_reviewers;
|
$this->reviewers = $basic_reviewers;
|
||||||
$selects[] = $this->buildSelectStatement($conn_r);
|
$selects[] = $this->buildSelectStatement($conn_r);
|
||||||
|
|
||||||
// Build the query where the responsible users are reviewers, or
|
// Build the query where the responsible users are reviewers, or
|
||||||
// projects they are members of are reviewers.
|
// projects they are members of are reviewers.
|
||||||
$this->authors = $basic_authors;
|
$this->authors = $basic_authors;
|
||||||
$this->reviewers = array_merge(
|
$this->reviewers = array_merge($basic_reviewers, $this->responsibles);
|
||||||
$basic_reviewers,
|
|
||||||
$this->responsibles,
|
|
||||||
$authority_phids);
|
|
||||||
$selects[] = $this->buildSelectStatement($conn_r);
|
$selects[] = $this->buildSelectStatement($conn_r);
|
||||||
|
|
||||||
// Put everything back like it was.
|
// Put everything back like it was.
|
||||||
|
@ -591,7 +553,7 @@ final class DifferentialRevisionQuery
|
||||||
|
|
||||||
$joins = $this->buildJoinsClause($conn_r);
|
$joins = $this->buildJoinsClause($conn_r);
|
||||||
$where = $this->buildWhereClause($conn_r);
|
$where = $this->buildWhereClause($conn_r);
|
||||||
$group_by = $this->buildGroupByClause($conn_r);
|
$group_by = $this->buildGroupClause($conn_r);
|
||||||
$having = $this->buildHavingClause($conn_r);
|
$having = $this->buildHavingClause($conn_r);
|
||||||
|
|
||||||
$this->buildingGlobalOrder = false;
|
$this->buildingGlobalOrder = false;
|
||||||
|
@ -835,19 +797,37 @@ final class DifferentialRevisionQuery
|
||||||
/**
|
/**
|
||||||
* @task internal
|
* @task internal
|
||||||
*/
|
*/
|
||||||
private function buildGroupByClause($conn_r) {
|
protected function shouldGroupQueryResultRows() {
|
||||||
|
|
||||||
$join_triggers = array_merge(
|
$join_triggers = array_merge(
|
||||||
$this->pathIDs,
|
$this->pathIDs,
|
||||||
$this->ccs,
|
$this->ccs,
|
||||||
$this->reviewers);
|
$this->reviewers);
|
||||||
|
|
||||||
$needs_distinct = (count($join_triggers) > 1);
|
if (count($join_triggers) > 1) {
|
||||||
|
return true;
|
||||||
if ($needs_distinct) {
|
|
||||||
return 'GROUP BY r.id';
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
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(
|
private function loadReviewerAuthority(
|
||||||
array $revisions,
|
array $revisions,
|
||||||
array $edges,
|
array $edges,
|
||||||
|
@ -1105,9 +1041,13 @@ final class DifferentialRevisionQuery
|
||||||
$revision_map = mpull($revisions, null, 'getPHID');
|
$revision_map = mpull($revisions, null, 'getPHID');
|
||||||
$viewer_phid = $this->getViewer()->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();
|
$project_phids = array();
|
||||||
|
$package_phids = array();
|
||||||
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
|
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
|
||||||
|
$package_type = PhabricatorOwnersPackagePHIDType::TYPECONST;
|
||||||
|
|
||||||
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
||||||
foreach ($edges as $src => $types) {
|
foreach ($edges as $src => $types) {
|
||||||
if (!$allow_self) {
|
if (!$allow_self) {
|
||||||
|
@ -1121,14 +1061,20 @@ final class DifferentialRevisionQuery
|
||||||
}
|
}
|
||||||
$edge_data = idx($types, $edge_type, array());
|
$edge_data = idx($types, $edge_type, array());
|
||||||
foreach ($edge_data as $dst => $data) {
|
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;
|
$project_phids[] = $dst;
|
||||||
}
|
}
|
||||||
|
if ($phid_type == $package_type) {
|
||||||
|
$package_phids[] = $dst;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, figure out which of these projects the viewer is actually a
|
// The viewer has authority over themselves.
|
||||||
// member of.
|
$user_authority = array_fuse(array($viewer_phid));
|
||||||
|
|
||||||
|
// And over any projects they are a member of.
|
||||||
$project_authority = array();
|
$project_authority = array();
|
||||||
if ($project_phids) {
|
if ($project_phids) {
|
||||||
$project_authority = id(new PhabricatorProjectQuery())
|
$project_authority = id(new PhabricatorProjectQuery())
|
||||||
|
@ -1137,12 +1083,22 @@ final class DifferentialRevisionQuery
|
||||||
->withMemberPHIDs(array($viewer_phid))
|
->withMemberPHIDs(array($viewer_phid))
|
||||||
->execute();
|
->execute();
|
||||||
$project_authority = mpull($project_authority, 'getPHID');
|
$project_authority = mpull($project_authority, 'getPHID');
|
||||||
|
$project_authority = array_fuse($project_authority);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, the viewer has authority over themselves.
|
// And over any packages they own.
|
||||||
return array(
|
$package_authority = array();
|
||||||
$viewer_phid => true,
|
if ($package_phids) {
|
||||||
) + array_fuse($project_authority);
|
$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() {
|
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';
|
return 'PhabricatorDifferentialApplication';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function newResultBuckets() {
|
||||||
|
return DifferentialRevisionResultBucket::getAllResultBuckets();
|
||||||
|
}
|
||||||
|
|
||||||
public function newQuery() {
|
public function newQuery() {
|
||||||
return id(new DifferentialRevisionQuery())
|
return id(new DifferentialRevisionQuery())
|
||||||
->needFlags(true)
|
->needFlags(true)
|
||||||
->needDrafts(true)
|
->needDrafts(true)
|
||||||
->needRelationships(true);
|
->needRelationships(true)
|
||||||
|
->needReviewerStatus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPageSize(PhabricatorSavedQuery $saved) {
|
protected function buildQueryFromParameters(array $map) {
|
||||||
if ($saved->getQueryKey() == 'active') {
|
$query = $this->newQuery();
|
||||||
return 0xFFFF;
|
|
||||||
}
|
if ($map['responsiblePHIDs']) {
|
||||||
return parent::getPageSize($saved);
|
$query->withResponsibleUsers($map['responsiblePHIDs']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
if ($map['authorPHIDs']) {
|
||||||
$saved = new PhabricatorSavedQuery();
|
$query->withAuthors($map['authorPHIDs']);
|
||||||
|
|
||||||
$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) {
|
if ($map['reviewerPHIDs']) {
|
||||||
$query = id(new DifferentialRevisionQuery())
|
$query->withReviewers($map['reviewerPHIDs']);
|
||||||
->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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setQueryProjects($query, $saved);
|
if ($map['repositoryPHIDs']) {
|
||||||
|
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
|
||||||
$author_phids = $saved->getParameter('authorPHIDs', array());
|
|
||||||
$author_phids = $user_datasource->evaluateTokens($author_phids);
|
|
||||||
if ($author_phids) {
|
|
||||||
$query->withAuthors($author_phids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$reviewer_phids = $saved->getParameter('reviewerPHIDs', array());
|
if ($map['status']) {
|
||||||
if ($reviewer_phids) {
|
$query->withStatus($map['status']);
|
||||||
$query->withReviewers($reviewer_phids);
|
|
||||||
}
|
|
||||||
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
|
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildSearchForm(
|
protected function buildCustomSearchFields() {
|
||||||
AphrontFormView $form,
|
return array(
|
||||||
PhabricatorSavedQuery $saved) {
|
id(new PhabricatorSearchDatasourceField())
|
||||||
|
|
||||||
$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'))
|
->setLabel(pht('Responsible Users'))
|
||||||
->setName('responsibles')
|
->setKey('responsiblePHIDs')
|
||||||
->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
|
->setAliases(array('responsiblePHID', 'responsibles', 'responsible'))
|
||||||
->setValue($responsible_phids))
|
->setDatasource(new DifferentialResponsibleDatasource())
|
||||||
->appendControl(
|
->setDescription(
|
||||||
id(new AphrontFormTokenizerControl())
|
pht('Find revisions that a given user is responsible for.')),
|
||||||
|
id(new PhabricatorUsersSearchField())
|
||||||
->setLabel(pht('Authors'))
|
->setLabel(pht('Authors'))
|
||||||
->setName('authors')
|
->setKey('authorPHIDs')
|
||||||
->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
|
->setAliases(array('author', 'authors', 'authorPHID'))
|
||||||
->setValue($author_phids))
|
->setDescription(
|
||||||
->appendControl(
|
pht('Find revisions with specific authors.')),
|
||||||
id(new AphrontFormTokenizerControl())
|
id(new PhabricatorSearchDatasourceField())
|
||||||
->setLabel(pht('Reviewers'))
|
->setLabel(pht('Reviewers'))
|
||||||
->setName('reviewers')
|
->setKey('reviewerPHIDs')
|
||||||
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
|
||||||
->setValue($reviewer_phids))
|
->setDatasource(new DiffusionAuditorFunctionDatasource())
|
||||||
->appendControl(
|
->setDescription(
|
||||||
id(new AphrontFormTokenizerControl())
|
pht('Find revisions with specific reviewers.')),
|
||||||
->setLabel(pht('Subscribers'))
|
id(new PhabricatorSearchDatasourceField())
|
||||||
->setName('subscribers')
|
|
||||||
->setDatasource(new PhabricatorMetaMTAMailableFunctionDatasource())
|
|
||||||
->setValue($subscriber_phids))
|
|
||||||
->appendControl(
|
|
||||||
id(new AphrontFormTokenizerControl())
|
|
||||||
->setLabel(pht('Repositories'))
|
->setLabel(pht('Repositories'))
|
||||||
->setName('repositories')
|
->setKey('repositoryPHIDs')
|
||||||
|
->setAliases(array('repository', 'repositories', 'repositoryPHID'))
|
||||||
->setDatasource(new DiffusionRepositoryDatasource())
|
->setDatasource(new DiffusionRepositoryDatasource())
|
||||||
->setValue($repository_phids))
|
->setDescription(
|
||||||
->appendControl(
|
pht('Find revisions from specific repositories.')),
|
||||||
id(new AphrontFormTokenizerControl())
|
id(new PhabricatorSearchSelectField())
|
||||||
->setLabel(pht('Tags'))
|
|
||||||
->setName('projects')
|
|
||||||
->setDatasource(new PhabricatorProjectLogicalDatasource())
|
|
||||||
->setValue($projects))
|
|
||||||
->appendChild(
|
|
||||||
id(new AphrontFormSelectControl())
|
|
||||||
->setLabel(pht('Status'))
|
->setLabel(pht('Status'))
|
||||||
->setName('status')
|
->setKey('status')
|
||||||
->setOptions($this->getStatusOptions())
|
->setOptions($this->getStatusOptions())
|
||||||
->setValue($saved->getParameter('status')));
|
->setDescription(
|
||||||
|
pht('Find revisions with particular statuses.')),
|
||||||
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 getURI($path) {
|
protected function getURI($path) {
|
||||||
|
@ -235,9 +112,12 @@ final class DifferentialRevisionSearchEngine
|
||||||
|
|
||||||
switch ($query_key) {
|
switch ($query_key) {
|
||||||
case 'active':
|
case 'active':
|
||||||
|
$bucket_key = DifferentialRevisionRequiredActionResultBucket::BUCKETKEY;
|
||||||
|
|
||||||
return $query
|
return $query
|
||||||
->setParameter('responsiblePHIDs', array($viewer->getPHID()))
|
->setParameter('responsiblePHIDs', array($viewer->getPHID()))
|
||||||
->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN);
|
->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN)
|
||||||
|
->setParameter('bucket', $bucket_key);
|
||||||
case 'authored':
|
case 'authored':
|
||||||
return $query
|
return $query
|
||||||
->setParameter('authorPHIDs', array($viewer->getPHID()));
|
->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(
|
protected function renderResultList(
|
||||||
array $revisions,
|
array $revisions,
|
||||||
PhabricatorSavedQuery $query,
|
PhabricatorSavedQuery $query,
|
||||||
|
@ -278,35 +151,26 @@ final class DifferentialRevisionSearchEngine
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->setNoBox($this->isPanelContext());
|
->setNoBox($this->isPanelContext());
|
||||||
|
|
||||||
|
$bucket = $this->getResultBucket($query);
|
||||||
|
|
||||||
|
$unlanded = $this->loadUnlandedDependencies($revisions);
|
||||||
|
|
||||||
$views = array();
|
$views = array();
|
||||||
if ($query->getQueryKey() == 'active') {
|
if ($bucket) {
|
||||||
$split = DifferentialRevisionQuery::splitResponsible(
|
$bucket->setViewer($viewer);
|
||||||
$revisions,
|
|
||||||
$query->getParameter('responsiblePHIDs'));
|
|
||||||
list($blocking, $active, $waiting) = $split;
|
|
||||||
|
|
||||||
$views[] = id(clone $template)
|
try {
|
||||||
->setHeader(pht('Blocking Others'))
|
$groups = $bucket->newResultGroups($query, $revisions);
|
||||||
->setNoDataString(
|
|
||||||
pht('No revisions are blocked on your action.'))
|
|
||||||
->setHighlightAge(true)
|
|
||||||
->setRevisions($blocking)
|
|
||||||
->setHandles(array());
|
|
||||||
|
|
||||||
|
foreach ($groups as $group) {
|
||||||
$views[] = id(clone $template)
|
$views[] = id(clone $template)
|
||||||
->setHeader(pht('Action Required'))
|
->setHeader($group->getName())
|
||||||
->setNoDataString(
|
->setNoDataString($group->getNoDataString())
|
||||||
pht('No revisions require your action.'))
|
->setRevisions($group->getObjects());
|
||||||
->setHighlightAge(true)
|
}
|
||||||
->setRevisions($active)
|
} catch (Exception $ex) {
|
||||||
->setHandles(array());
|
$this->addError($ex->getMessage());
|
||||||
|
}
|
||||||
$views[] = id(clone $template)
|
|
||||||
->setHeader(pht('Waiting on Others'))
|
|
||||||
->setNoDataString(
|
|
||||||
pht('You have no revisions waiting on others.'))
|
|
||||||
->setRevisions($waiting)
|
|
||||||
->setHandles(array());
|
|
||||||
} else {
|
} else {
|
||||||
$views[] = id(clone $template)
|
$views[] = id(clone $template)
|
||||||
->setRevisions($revisions)
|
->setRevisions($revisions)
|
||||||
|
@ -325,6 +189,7 @@ final class DifferentialRevisionSearchEngine
|
||||||
|
|
||||||
foreach ($views as $view) {
|
foreach ($views as $view) {
|
||||||
$view->setHandles($handles);
|
$view->setHandles($handles);
|
||||||
|
$view->setUnlandedDependencies($unlanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($views) == 1) {
|
if (count($views) == 1) {
|
||||||
|
@ -361,4 +226,56 @@ final class DifferentialRevisionSearchEngine
|
||||||
return $view;
|
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();
|
$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 = new AphrontFormView();
|
||||||
$form
|
$form
|
||||||
|
|
|
@ -12,6 +12,16 @@ final class DifferentialRevisionListView extends AphrontView {
|
||||||
private $noDataString;
|
private $noDataString;
|
||||||
private $noBox;
|
private $noBox;
|
||||||
private $background = null;
|
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) {
|
public function setNoDataString($no_data_string) {
|
||||||
$this->noDataString = $no_data_string;
|
$this->noDataString = $no_data_string;
|
||||||
|
@ -65,20 +75,6 @@ final class DifferentialRevisionListView extends AphrontView {
|
||||||
public function render() {
|
public function render() {
|
||||||
$viewer = $this->getViewer();
|
$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->initBehavior('phabricator-tooltips', array());
|
||||||
$this->requireResource('aphront-tooltip-css');
|
$this->requireResource('aphront-tooltip-css');
|
||||||
|
|
||||||
|
@ -109,18 +105,6 @@ final class DifferentialRevisionListView extends AphrontView {
|
||||||
$modified = $revision->getDateModified();
|
$modified = $revision->getDateModified();
|
||||||
|
|
||||||
$status = $revision->getStatus();
|
$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 =
|
$status_name =
|
||||||
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status);
|
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status);
|
||||||
|
|
||||||
|
@ -143,15 +127,20 @@ final class DifferentialRevisionListView extends AphrontView {
|
||||||
$item->addAttribute($draft);
|
$item->addAttribute($draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Most things 'Need Review', so accept it's the default */
|
|
||||||
if ($status != ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
|
|
||||||
$item->addAttribute($status_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author
|
// Author
|
||||||
$author_handle = $this->handles[$revision->getAuthorPHID()];
|
$author_handle = $this->handles[$revision->getAuthorPHID()];
|
||||||
$item->addByline(pht('Author: %s', $author_handle->renderLink()));
|
$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();
|
$reviewers = array();
|
||||||
// TODO: As above, this should be based on `getReviewerStatus()`.
|
// TODO: As above, this should be based on `getReviewerStatus()`.
|
||||||
foreach ($revision->getReviewers() as $reviewer) {
|
foreach ($revision->getReviewers() as $reviewer) {
|
||||||
|
@ -164,7 +153,7 @@ final class DifferentialRevisionListView extends AphrontView {
|
||||||
}
|
}
|
||||||
|
|
||||||
$item->addAttribute(pht('Reviewers: %s', $reviewers));
|
$item->addAttribute(pht('Reviewers: %s', $reviewers));
|
||||||
$item->setEpoch($revision->getDateModified(), $object_age);
|
$item->setEpoch($revision->getDateModified());
|
||||||
|
|
||||||
switch ($status) {
|
switch ($status) {
|
||||||
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
|
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
|
||||||
|
|
|
@ -455,7 +455,12 @@ final class DiffusionCommitController extends DiffusionController {
|
||||||
if ($audit_requests) {
|
if ($audit_requests) {
|
||||||
$user_requests = array();
|
$user_requests = array();
|
||||||
$other_requests = array();
|
$other_requests = array();
|
||||||
|
|
||||||
foreach ($audit_requests as $audit_request) {
|
foreach ($audit_requests as $audit_request) {
|
||||||
|
if (!$audit_request->isInteresting()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($audit_request->isUser()) {
|
if ($audit_request->isUser()) {
|
||||||
$user_requests[] = $audit_request;
|
$user_requests[] = $audit_request;
|
||||||
} else {
|
} else {
|
||||||
|
@ -471,7 +476,7 @@ final class DiffusionCommitController extends DiffusionController {
|
||||||
|
|
||||||
if ($other_requests) {
|
if ($other_requests) {
|
||||||
$view->addProperty(
|
$view->addProperty(
|
||||||
pht('Project/Package Auditors'),
|
pht('Group Auditors'),
|
||||||
$this->renderAuditStatusView($other_requests));
|
$this->renderAuditStatusView($other_requests));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,13 @@ abstract class DiffusionAuditorsHeraldAction
|
||||||
$object = $adapter->getObject();
|
$object = $adapter->getObject();
|
||||||
|
|
||||||
$auditors = $object->getAudits();
|
$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(
|
$allowed_types = array(
|
||||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||||
|
|
|
@ -205,40 +205,29 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildRevisionPanel() {
|
private function buildRevisionPanel() {
|
||||||
$user = $this->getRequest()->getUser();
|
$viewer = $this->getViewer();
|
||||||
$user_phid = $user->getPHID();
|
|
||||||
|
|
||||||
$revision_query = id(new DifferentialRevisionQuery())
|
$revisions = PhabricatorDifferentialApplication::loadNeedAttentionRevisions(
|
||||||
->setViewer($user)
|
$viewer);
|
||||||
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
|
|
||||||
->withResponsibleUsers(array($user_phid))
|
|
||||||
->needRelationships(true)
|
|
||||||
->needFlags(true)
|
|
||||||
->needDrafts(true);
|
|
||||||
|
|
||||||
$revisions = $revision_query->execute();
|
if (!$revisions) {
|
||||||
|
|
||||||
list($blocking, $active) = DifferentialRevisionQuery::splitResponsible(
|
|
||||||
$revisions,
|
|
||||||
array($user_phid));
|
|
||||||
|
|
||||||
if (!$blocking && !$active) {
|
|
||||||
return $this->renderMiniPanel(
|
return $this->renderMiniPanel(
|
||||||
pht('No Waiting Revisions'),
|
pht('No Waiting Revisions'),
|
||||||
pht('No revisions are waiting on you.'));
|
pht('No revisions are waiting on you.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$title = pht('Revisions Waiting on You');
|
$title = pht('Revisions Waiting on You');
|
||||||
$href = '/differential';
|
$href = '/differential/';
|
||||||
$panel = new PHUIObjectBoxView();
|
$panel = new PHUIObjectBoxView();
|
||||||
$panel->setHeader($this->renderSectionHeader($title, $href));
|
$panel->setHeader($this->renderSectionHeader($title, $href));
|
||||||
|
|
||||||
$revision_view = id(new DifferentialRevisionListView())
|
$revision_view = id(new DifferentialRevisionListView())
|
||||||
->setHighlightAge(true)
|
->setHighlightAge(true)
|
||||||
->setRevisions(array_merge($blocking, $active))
|
->setRevisions($revisions)
|
||||||
->setUser($user);
|
->setUser($viewer);
|
||||||
|
|
||||||
$phids = array_merge(
|
$phids = array_merge(
|
||||||
array($user_phid),
|
array($viewer->getPHID()),
|
||||||
$revision_view->getRequiredHandlePHIDs());
|
$revision_view->getRequiredHandlePHIDs());
|
||||||
$handles = $this->loadViewerHandles($phids);
|
$handles = $this->loadViewerHandles($phids);
|
||||||
|
|
||||||
|
|
|
@ -148,8 +148,7 @@ final class ManiphestTaskSearchEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildQueryFromParameters(array $map) {
|
protected function buildQueryFromParameters(array $map) {
|
||||||
$query = id(new ManiphestTaskQuery())
|
$query = $this->newQuery();
|
||||||
->needProjectPHIDs(true);
|
|
||||||
|
|
||||||
if ($map['assignedPHIDs']) {
|
if ($map['assignedPHIDs']) {
|
||||||
$query->withOwners($map['assignedPHIDs']);
|
$query->withOwners($map['assignedPHIDs']);
|
||||||
|
|
|
@ -24,6 +24,8 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute() {
|
public function execute() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$phids = array_fuse($this->phids);
|
$phids = array_fuse($this->phids);
|
||||||
$actors = array();
|
$actors = array();
|
||||||
$type_map = array();
|
$type_map = array();
|
||||||
|
@ -33,6 +35,33 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
||||||
|
|
||||||
// TODO: Generalize this somewhere else.
|
// 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();
|
$results = array();
|
||||||
foreach ($type_map as $type => $phids) {
|
foreach ($type_map as $type => $phids) {
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -40,7 +69,7 @@ final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery {
|
||||||
// NOTE: We're loading the projects here in order to respect policies.
|
// NOTE: We're loading the projects here in order to respect policies.
|
||||||
|
|
||||||
$projects = id(new PhabricatorProjectQuery())
|
$projects = id(new PhabricatorProjectQuery())
|
||||||
->setViewer($this->getViewer())
|
->setViewer($viewer)
|
||||||
->withPHIDs($phids)
|
->withPHIDs($phids)
|
||||||
->needMembers(true)
|
->needMembers(true)
|
||||||
->needWatchers(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;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,11 +107,15 @@ final class PhabricatorMailTarget extends Phobject {
|
||||||
$cc_handles = iterator_to_array($cc_handles);
|
$cc_handles = iterator_to_array($cc_handles);
|
||||||
|
|
||||||
$body = '';
|
$body = '';
|
||||||
|
|
||||||
if ($to_handles) {
|
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) {
|
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;
|
return $body;
|
||||||
|
|
|
@ -634,7 +634,7 @@ final class PhabricatorMetaMTAMail
|
||||||
}
|
}
|
||||||
$mailer->setBody($body);
|
$mailer->setBody($body);
|
||||||
|
|
||||||
$html_emails = false;
|
$html_emails = true;
|
||||||
if ($use_prefs && $prefs) {
|
if ($use_prefs && $prefs) {
|
||||||
$html_emails = $prefs->getPreference(
|
$html_emails = $prefs->getPreference(
|
||||||
PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS,
|
PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS,
|
||||||
|
|
|
@ -8,7 +8,7 @@ final class PhabricatorMetaMTAMailableDatasource
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlaceholderText() {
|
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() {
|
public function getDatasourceApplicationClass() {
|
||||||
|
@ -19,6 +19,7 @@ final class PhabricatorMetaMTAMailableDatasource
|
||||||
return array(
|
return array(
|
||||||
new PhabricatorPeopleDatasource(),
|
new PhabricatorPeopleDatasource(),
|
||||||
new PhabricatorProjectDatasource(),
|
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;
|
return self::GROUP_UTILITIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRemarkupRules() {
|
||||||
|
return array(
|
||||||
|
new PhabricatorOwnersPackageRemarkupRule(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/owners/' => array(
|
'/owners/' => array(
|
||||||
|
|
|
@ -144,7 +144,7 @@ final class PhabricatorOwnersDetailController
|
||||||
}
|
}
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
$crumbs->addTextCrumb($package->getName());
|
$crumbs->addTextCrumb($package->getMonogram());
|
||||||
$crumbs->setBorder(true);
|
$crumbs->setBorder(true);
|
||||||
|
|
||||||
$timeline = $this->buildTransactionTimeline(
|
$timeline = $this->buildTransactionTimeline(
|
||||||
|
@ -184,6 +184,19 @@ final class PhabricatorOwnersDetailController
|
||||||
}
|
}
|
||||||
$view->addProperty(pht('Owners'), $owner_list);
|
$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()) {
|
if ($package->getAuditingEnabled()) {
|
||||||
$auditing = pht('Enabled');
|
$auditing = pht('Enabled');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -64,7 +64,7 @@ final class PhabricatorOwnersPathsController
|
||||||
$editor->applyTransactions($package, $xactions);
|
$editor->applyTransactions($package, $xactions);
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())
|
return id(new AphrontRedirectResponse())
|
||||||
->setURI('/owners/package/'.$package->getID().'/');
|
->setURI($package->getURI());
|
||||||
} else {
|
} else {
|
||||||
$paths = $package->getPaths();
|
$paths = $package->getPaths();
|
||||||
$path_refs = mpull($paths, 'getRef');
|
$path_refs = mpull($paths, 'getRef');
|
||||||
|
@ -106,7 +106,7 @@ final class PhabricatorOwnersPathsController
|
||||||
|
|
||||||
require_celerity_resource('owners-path-editor-css');
|
require_celerity_resource('owners-path-editor-css');
|
||||||
|
|
||||||
$cancel_uri = '/owners/package/'.$package->getID().'/';
|
$cancel_uri = $package->getURI();
|
||||||
|
|
||||||
$form = id(new AphrontFormView())
|
$form = id(new AphrontFormView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
|
|
|
@ -51,8 +51,7 @@ final class PhabricatorOwnersPackageEditEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getObjectViewURI($object) {
|
protected function getObjectViewURI($object) {
|
||||||
$id = $object->getID();
|
return $object->getURI();
|
||||||
return "/owners/package/{$id}/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildCustomEditFields($object) {
|
protected function buildCustomEditFields($object) {
|
||||||
|
@ -85,6 +84,12 @@ applying a transaction of this type.
|
||||||
EOTEXT
|
EOTEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
|
||||||
|
$autoreview_map = ipull($autoreview_map, 'name');
|
||||||
|
|
||||||
|
$dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
|
||||||
|
$dominion_map = ipull($dominion_map, 'name');
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
id(new PhabricatorTextEditField())
|
id(new PhabricatorTextEditField())
|
||||||
->setKey('name')
|
->setKey('name')
|
||||||
|
@ -101,6 +106,28 @@ EOTEXT
|
||||||
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
->setDatasource(new PhabricatorProjectOrUserDatasource())
|
||||||
->setIsCopyable(true)
|
->setIsCopyable(true)
|
||||||
->setValue($object->getOwnerPHIDs()),
|
->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())
|
id(new PhabricatorSelectEditField())
|
||||||
->setKey('auditing')
|
->setKey('auditing')
|
||||||
->setLabel(pht('Auditing'))
|
->setLabel(pht('Auditing'))
|
||||||
|
|
|
@ -20,6 +20,8 @@ final class PhabricatorOwnersPackageTransactionEditor
|
||||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION;
|
$types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION;
|
||||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS;
|
$types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS;
|
||||||
$types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS;
|
$types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS;
|
||||||
|
$types[] = PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW;
|
||||||
|
$types[] = PhabricatorOwnersPackageTransaction::TYPE_DOMINION;
|
||||||
|
|
||||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||||
|
@ -47,6 +49,10 @@ final class PhabricatorOwnersPackageTransactionEditor
|
||||||
return mpull($paths, 'getRef');
|
return mpull($paths, 'getRef');
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||||
return $object->getStatus();
|
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_NAME:
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
|
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||||
|
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||||
|
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
|
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
|
||||||
$new = $xaction->getNewValue();
|
$new = $xaction->getNewValue();
|
||||||
|
@ -113,6 +121,12 @@ final class PhabricatorOwnersPackageTransactionEditor
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||||
$object->setStatus($xaction->getNewValue());
|
$object->setStatus($xaction->getNewValue());
|
||||||
return;
|
return;
|
||||||
|
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||||
|
$object->setAutoReview($xaction->getNewValue());
|
||||||
|
return;
|
||||||
|
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||||
|
$object->setDominion($xaction->getNewValue());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||||
|
@ -127,6 +141,8 @@ final class PhabricatorOwnersPackageTransactionEditor
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
|
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
|
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
|
||||||
|
case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
|
||||||
|
case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
|
||||||
return;
|
return;
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
|
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
|
||||||
$old = $xaction->getOldValue();
|
$old = $xaction->getOldValue();
|
||||||
|
@ -205,6 +221,61 @@ final class PhabricatorOwnersPackageTransactionEditor
|
||||||
$error->setIsMissingFieldError(true);
|
$error->setIsMissingFieldError(true);
|
||||||
$errors[] = $error;
|
$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;
|
break;
|
||||||
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
|
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
|
||||||
if (!$xactions) {
|
if (!$xactions) {
|
||||||
|
@ -331,8 +402,7 @@ final class PhabricatorOwnersPackageTransactionEditor
|
||||||
|
|
||||||
$body = parent::buildMailBody($object, $xactions);
|
$body = parent::buildMailBody($object, $xactions);
|
||||||
|
|
||||||
$detail_uri = PhabricatorEnv::getProductionURI(
|
$detail_uri = PhabricatorEnv::getProductionURI($object->getURI());
|
||||||
'/owners/package/'.$object->getID().'/');
|
|
||||||
|
|
||||||
$body->addLinkSection(
|
$body->addLinkSection(
|
||||||
pht('PACKAGE DETAIL'),
|
pht('PACKAGE DETAIL'),
|
||||||
|
|
|
@ -9,7 +9,7 @@ final class PhabricatorOwnersPackagePHIDType extends PhabricatorPHIDType {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTypeIcon() {
|
public function getTypeIcon() {
|
||||||
return 'fa-list-alt';
|
return 'fa-shopping-bag';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newObject() {
|
public function newObject() {
|
||||||
|
@ -36,12 +36,50 @@ final class PhabricatorOwnersPackagePHIDType extends PhabricatorPHIDType {
|
||||||
foreach ($handles as $phid => $handle) {
|
foreach ($handles as $phid => $handle) {
|
||||||
$package = $objects[$phid];
|
$package = $objects[$phid];
|
||||||
|
|
||||||
|
$monogram = $package->getMonogram();
|
||||||
$name = $package->getName();
|
$name = $package->getName();
|
||||||
$id = $package->getID();
|
$id = $package->getID();
|
||||||
|
$uri = $package->getURI();
|
||||||
|
|
||||||
$handle->setName($name);
|
$handle
|
||||||
$handle->setURI("/owners/package/{$id}/");
|
->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;
|
$packages = $this->controlResults;
|
||||||
|
$weak_dominion = PhabricatorOwnersPackage::DOMINION_WEAK;
|
||||||
|
|
||||||
$matches = array();
|
$matches = array();
|
||||||
foreach ($packages as $package_id => $package) {
|
foreach ($packages as $package_id => $package) {
|
||||||
|
@ -373,6 +374,7 @@ final class PhabricatorOwnersPackageQuery
|
||||||
if ($best_match && $include) {
|
if ($best_match && $include) {
|
||||||
$matches[$package_id] = array(
|
$matches[$package_id] = array(
|
||||||
'strength' => $best_match,
|
'strength' => $best_match,
|
||||||
|
'weak' => ($package->getDominion() == $weak_dominion),
|
||||||
'package' => $package,
|
'package' => $package,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -381,6 +383,18 @@ final class PhabricatorOwnersPackageQuery
|
||||||
$matches = isort($matches, 'strength');
|
$matches = isort($matches, 'strength');
|
||||||
$matches = array_reverse($matches);
|
$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'));
|
return array_values(ipull($matches, 'package'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,9 +136,9 @@ final class PhabricatorOwnersPackageSearchEngine
|
||||||
|
|
||||||
$item = id(new PHUIObjectItemView())
|
$item = id(new PHUIObjectItemView())
|
||||||
->setObject($package)
|
->setObject($package)
|
||||||
->setObjectName(pht('Package %d', $id))
|
->setObjectName($package->getMonogram())
|
||||||
->setHeader($package->getName())
|
->setHeader($package->getName())
|
||||||
->setHref('/owners/package/'.$id.'/');
|
->setHref($package->getURI());
|
||||||
|
|
||||||
if ($package->isArchived()) {
|
if ($package->isArchived()) {
|
||||||
$item->setDisabled(true);
|
$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 $name;
|
||||||
protected $originalName;
|
protected $originalName;
|
||||||
protected $auditingEnabled;
|
protected $auditingEnabled;
|
||||||
|
protected $autoReview;
|
||||||
protected $description;
|
protected $description;
|
||||||
protected $primaryOwnerPHID;
|
protected $primaryOwnerPHID;
|
||||||
protected $mailKey;
|
protected $mailKey;
|
||||||
protected $status;
|
protected $status;
|
||||||
protected $viewPolicy;
|
protected $viewPolicy;
|
||||||
protected $editPolicy;
|
protected $editPolicy;
|
||||||
|
protected $dominion;
|
||||||
|
|
||||||
private $paths = self::ATTACHABLE;
|
private $paths = self::ATTACHABLE;
|
||||||
private $owners = self::ATTACHABLE;
|
private $owners = self::ATTACHABLE;
|
||||||
|
@ -28,6 +30,14 @@ final class PhabricatorOwnersPackage
|
||||||
const STATUS_ACTIVE = 'active';
|
const STATUS_ACTIVE = 'active';
|
||||||
const STATUS_ARCHIVED = 'archived';
|
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) {
|
public static function initializeNewPackage(PhabricatorUser $actor) {
|
||||||
$app = id(new PhabricatorApplicationQuery())
|
$app = id(new PhabricatorApplicationQuery())
|
||||||
->setViewer($actor)
|
->setViewer($actor)
|
||||||
|
@ -41,6 +51,8 @@ final class PhabricatorOwnersPackage
|
||||||
|
|
||||||
return id(new PhabricatorOwnersPackage())
|
return id(new PhabricatorOwnersPackage())
|
||||||
->setAuditingEnabled(0)
|
->setAuditingEnabled(0)
|
||||||
|
->setAutoReview(self::AUTOREVIEW_NONE)
|
||||||
|
->setDominion(self::DOMINION_STRONG)
|
||||||
->setViewPolicy($view_policy)
|
->setViewPolicy($view_policy)
|
||||||
->setEditPolicy($edit_policy)
|
->setEditPolicy($edit_policy)
|
||||||
->attachPaths(array())
|
->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() {
|
protected function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
// This information is better available from the history table.
|
// This information is better available from the history table.
|
||||||
|
@ -69,6 +111,8 @@ final class PhabricatorOwnersPackage
|
||||||
'auditingEnabled' => 'bool',
|
'auditingEnabled' => 'bool',
|
||||||
'mailKey' => 'bytes20',
|
'mailKey' => 'bytes20',
|
||||||
'status' => 'text32',
|
'status' => 'text32',
|
||||||
|
'autoReview' => 'text32',
|
||||||
|
'dominion' => 'text32',
|
||||||
),
|
),
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
@ -165,7 +209,7 @@ final class PhabricatorOwnersPackage
|
||||||
foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
|
foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
|
||||||
$rows[] = queryfx_all(
|
$rows[] = queryfx_all(
|
||||||
$conn,
|
$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
|
FROM %T pkg JOIN %T p ON p.packageID = pkg.id
|
||||||
WHERE p.path IN (%Ls) %Q',
|
WHERE p.path IN (%Ls) %Q',
|
||||||
$package->getTableName(),
|
$package->getTableName(),
|
||||||
|
@ -207,35 +251,100 @@ final class PhabricatorOwnersPackage
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findLongestPathsPerPackage(array $rows, array $paths) {
|
public static function findLongestPathsPerPackage(array $rows, array $paths) {
|
||||||
$ids = array();
|
|
||||||
|
|
||||||
foreach (igroup($rows, 'id') as $id => $package_paths) {
|
// Build a map from each path to all the package paths which match it.
|
||||||
$relevant_paths = array_select_keys(
|
$path_hits = array();
|
||||||
$paths,
|
$weak = array();
|
||||||
ipull($package_paths, 'path'));
|
foreach ($rows as $row) {
|
||||||
|
$id = $row['id'];
|
||||||
|
$path = $row['path'];
|
||||||
|
$length = strlen($path);
|
||||||
|
$excluded = $row['excluded'];
|
||||||
|
|
||||||
// For every package, remove all excluded paths.
|
if ($row['dominion'] === self::DOMINION_WEAK) {
|
||||||
$remove = array();
|
$weak[$id] = true;
|
||||||
foreach ($package_paths as $package_path) {
|
}
|
||||||
if ($package_path['excluded']) {
|
|
||||||
$remove += idx($relevant_paths, $package_path['path'], array());
|
$matches = $paths[$path];
|
||||||
unset($relevant_paths[$package_path['path']]);
|
foreach ($matches as $match => $ignored) {
|
||||||
|
$path_hits[$match][] = array(
|
||||||
|
'id' => $id,
|
||||||
|
'excluded' => $excluded,
|
||||||
|
'length' => $length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($remove) {
|
// For each path, process the matching package paths to figure out which
|
||||||
foreach ($relevant_paths as $fragment => $fragment_paths) {
|
// packages actually own it.
|
||||||
$relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove);
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$relevant_paths = array_filter($relevant_paths);
|
$path_packages[$match] = $packages;
|
||||||
if ($relevant_paths) {
|
}
|
||||||
$ids[$id] = max(array_map('strlen', array_keys($relevant_paths)));
|
|
||||||
|
// 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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ids;
|
$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) {
|
public static function splitPath($path) {
|
||||||
|
@ -289,6 +398,14 @@ final class PhabricatorOwnersPackage
|
||||||
return isset($owner_phids[$phid]);
|
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 )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ final class PhabricatorOwnersPackageTransaction
|
||||||
const TYPE_DESCRIPTION = 'owners.description';
|
const TYPE_DESCRIPTION = 'owners.description';
|
||||||
const TYPE_PATHS = 'owners.paths';
|
const TYPE_PATHS = 'owners.paths';
|
||||||
const TYPE_STATUS = 'owners.status';
|
const TYPE_STATUS = 'owners.status';
|
||||||
|
const TYPE_AUTOREVIEW = 'owners.autoreview';
|
||||||
|
const TYPE_DOMINION = 'owners.dominion';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
return 'owners';
|
return 'owners';
|
||||||
|
@ -143,6 +145,30 @@ final class PhabricatorOwnersPackageTransaction
|
||||||
'%s archived this package.',
|
'%s archived this package.',
|
||||||
$this->renderHandleLink($author_phid));
|
$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();
|
return parent::getTitle();
|
||||||
|
|
|
@ -4,9 +4,24 @@ final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
public function testFindLongestPathsPerPackage() {
|
public function testFindLongestPathsPerPackage() {
|
||||||
$rows = array(
|
$rows = array(
|
||||||
array('id' => 1, 'excluded' => 0, 'path' => 'src/'),
|
array(
|
||||||
array('id' => 1, 'excluded' => 1, 'path' => 'src/releeph/'),
|
'id' => 1,
|
||||||
array('id' => 2, 'excluded' => 0, 'path' => 'src/releeph/'),
|
'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(
|
$paths = array(
|
||||||
|
@ -29,6 +44,62 @@ final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase {
|
||||||
2 => strlen('src/releeph/'),
|
2 => strlen('src/releeph/'),
|
||||||
),
|
),
|
||||||
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
|
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);
|
$packages = $this->executeQuery($query);
|
||||||
foreach ($packages as $package) {
|
foreach ($packages as $package) {
|
||||||
|
$name = $package->getName();
|
||||||
|
$monogram = $package->getMonogram();
|
||||||
|
|
||||||
$results[] = id(new PhabricatorTypeaheadResult())
|
$results[] = id(new PhabricatorTypeaheadResult())
|
||||||
->setName($package->getName())
|
->setName("{$monogram}: {$name}")
|
||||||
->setURI('/owners/package/'.$package->getID().'/')
|
->setURI($package->getURI())
|
||||||
->setPHID($package->getPHID());
|
->setPHID($package->getPHID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,12 @@ final class PassphraseQueryConduitAPIMethod
|
||||||
|
|
||||||
$material = array();
|
$material = array();
|
||||||
|
|
||||||
|
$is_locked = $credential->getIsLocked();
|
||||||
|
$allow_api = ($credential->getAllowConduit() && !$is_locked);
|
||||||
|
|
||||||
$secret = null;
|
$secret = null;
|
||||||
if ($request->getValue('needSecrets')) {
|
if ($request->getValue('needSecrets')) {
|
||||||
if ($credential->getAllowConduit()) {
|
if ($allow_api) {
|
||||||
$secret = $credential->getSecret();
|
$secret = $credential->getSecret();
|
||||||
if ($secret) {
|
if ($secret) {
|
||||||
$secret = $secret->openEnvelope();
|
$secret = $secret->openEnvelope();
|
||||||
|
@ -102,7 +105,7 @@ final class PassphraseQueryConduitAPIMethod
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$credential->getAllowConduit()) {
|
if (!$allow_api) {
|
||||||
$material['noAPIAccess'] = pht(
|
$material['noAPIAccess'] = pht(
|
||||||
'This private material for this credential is not accessible via '.
|
'This private material for this credential is not accessible via '.
|
||||||
'API calls.');
|
'API calls.');
|
||||||
|
|
|
@ -33,8 +33,22 @@ final class PassphraseCredentialConduitController
|
||||||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
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()) {
|
if ($request->isFormPost()) {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT)
|
->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT)
|
||||||
->setNewValue(!$credential->getAllowConduit());
|
->setNewValue(!$credential->getAllowConduit());
|
||||||
|
|
|
@ -270,8 +270,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type->shouldRequireUsername()) {
|
if ($type->shouldRequireUsername()) {
|
||||||
$form
|
$form->appendChild(
|
||||||
->appendChild(
|
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setName('username')
|
->setName('username')
|
||||||
->setLabel(pht('Login/Username'))
|
->setLabel(pht('Login/Username'))
|
||||||
|
@ -279,8 +278,8 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
||||||
->setDisabled($credential_is_locked)
|
->setDisabled($credential_is_locked)
|
||||||
->setError($e_username));
|
->setError($e_username));
|
||||||
}
|
}
|
||||||
$form
|
|
||||||
->appendChild(
|
$form->appendChild(
|
||||||
$secret_control
|
$secret_control
|
||||||
->setName('secret')
|
->setName('secret')
|
||||||
->setLabel($type->getSecretLabel())
|
->setLabel($type->getSecretLabel())
|
||||||
|
|
|
@ -32,15 +32,17 @@ final class PassphraseCredentialLockController
|
||||||
return $this->newDialog()
|
return $this->newDialog()
|
||||||
->setTitle(pht('Credential Already Locked'))
|
->setTitle(pht('Credential Already Locked'))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
pht(
|
pht('This credential is already locked.'))
|
||||||
'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.'))
|
|
||||||
->addCancelButton($view_uri, pht('Close'));
|
->addCancelButton($view_uri, pht('Close'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||||
|
->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT)
|
||||||
|
->setNewValue(0);
|
||||||
|
|
||||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK)
|
->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK)
|
||||||
->setNewValue(1);
|
->setNewValue(1);
|
||||||
|
@ -48,6 +50,7 @@ final class PassphraseCredentialLockController
|
||||||
$editor = id(new PassphraseCredentialTransactionEditor())
|
$editor = id(new PassphraseCredentialTransactionEditor())
|
||||||
->setActor($viewer)
|
->setActor($viewer)
|
||||||
->setContinueOnMissingFields(true)
|
->setContinueOnMissingFields(true)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
->setContentSourceFromRequest($request)
|
->setContentSourceFromRequest($request)
|
||||||
->applyTransactions($credential, $xactions);
|
->applyTransactions($credential, $xactions);
|
||||||
|
|
||||||
|
@ -55,12 +58,13 @@ final class PassphraseCredentialLockController
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->newDialog()
|
return $this->newDialog()
|
||||||
->setTitle(pht('Really lock credential?'))
|
->setTitle(pht('Lock Credential'))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
pht(
|
pht(
|
||||||
'This credential will be locked and the secret will be '.
|
'This credential will be locked and the secret will be hidden '.
|
||||||
'hidden forever. Anything relying on this credential will '.
|
'forever. If Conduit access is enabled, it will be revoked. '.
|
||||||
'still function. This operation can not be undone.'))
|
'Anything relying on this credential will still function. This '.
|
||||||
|
'operation can not be undone.'))
|
||||||
->addSubmitButton(pht('Lock Credential'))
|
->addSubmitButton(pht('Lock Credential'))
|
||||||
->addCancelButton($view_uri);
|
->addCancelButton($view_uri);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,8 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
||||||
$credential,
|
$credential,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$can_conduit = ($can_edit && !$is_locked);
|
||||||
|
|
||||||
$curtain = $this->newCurtainView($credential);
|
$curtain = $this->newCurtainView($credential);
|
||||||
|
|
||||||
$curtain->addAction(
|
$curtain->addAction(
|
||||||
|
@ -161,7 +163,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
||||||
->setName($credential_conduit_text)
|
->setName($credential_conduit_text)
|
||||||
->setIcon($credential_conduit_icon)
|
->setIcon($credential_conduit_icon)
|
||||||
->setHref($this->getApplicationURI("conduit/{$id}/"))
|
->setHref($this->getApplicationURI("conduit/{$id}/"))
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_conduit)
|
||||||
->setWorkflow(true));
|
->setWorkflow(true));
|
||||||
|
|
||||||
$curtain->addAction(
|
$curtain->addAction(
|
||||||
|
@ -202,16 +204,6 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
||||||
$credential->getUsername());
|
$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();
|
$description = $credential->getDescription();
|
||||||
if (strlen($description)) {
|
if (strlen($description)) {
|
||||||
$properties->addSectionHeader(
|
$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();
|
$profile->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
|
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
'objectPHID = %s',
|
->setViewer($engine->getViewer())
|
||||||
$this->getPHID());
|
->withObjectPHIDs(array($this->getPHID()))
|
||||||
|
->execute();
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
$key->delete();
|
$engine->destroyObject($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
||||||
|
@ -1341,6 +1342,12 @@ final class PhabricatorUser
|
||||||
return 'id_rsa_phabricator';
|
return 'id_rsa_phabricator';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSSHKeyNotifyPHIDs() {
|
||||||
|
return array(
|
||||||
|
$this->getPHID(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ final class PhabricatorObjectHandle
|
||||||
private $policyFiltered;
|
private $policyFiltered;
|
||||||
private $subtitle;
|
private $subtitle;
|
||||||
private $tokenIcon;
|
private $tokenIcon;
|
||||||
|
private $commandLineObjectName;
|
||||||
|
|
||||||
public function setIcon($icon) {
|
public function setIcon($icon) {
|
||||||
$this->icon = $icon;
|
$this->icon = $icon;
|
||||||
|
@ -196,6 +197,19 @@ final class PhabricatorObjectHandle
|
||||||
return $this->getName();
|
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) {
|
public function setTitle($title) {
|
||||||
$this->title = $title;
|
$this->title = $title;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -6,6 +6,7 @@ final class PhabricatorObjectListQuery extends Phobject {
|
||||||
private $objectList;
|
private $objectList;
|
||||||
private $allowedTypes = array();
|
private $allowedTypes = array();
|
||||||
private $allowPartialResults;
|
private $allowPartialResults;
|
||||||
|
private $suffixes = array();
|
||||||
|
|
||||||
public function setAllowPartialResults($allow_partial_results) {
|
public function setAllowPartialResults($allow_partial_results) {
|
||||||
$this->allowPartialResults = $allow_partial_results;
|
$this->allowPartialResults = $allow_partial_results;
|
||||||
|
@ -16,6 +17,15 @@ final class PhabricatorObjectListQuery extends Phobject {
|
||||||
return $this->allowPartialResults;
|
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) {
|
public function setAllowedTypes(array $allowed_types) {
|
||||||
$this->allowedTypes = $allowed_types;
|
$this->allowedTypes = $allowed_types;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -45,9 +55,80 @@ final class PhabricatorObjectListQuery extends Phobject {
|
||||||
|
|
||||||
public function execute() {
|
public function execute() {
|
||||||
$names = $this->getObjectList();
|
$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();
|
$types = array();
|
||||||
foreach ($objects as $name => $object) {
|
foreach ($objects as $name => $object) {
|
||||||
|
@ -66,8 +147,8 @@ final class PhabricatorObjectListQuery extends Phobject {
|
||||||
$invalid = array_mergev($invalid);
|
$invalid = array_mergev($invalid);
|
||||||
|
|
||||||
$missing = array();
|
$missing = array();
|
||||||
foreach ($names as $name) {
|
foreach ($name_map as $key => $name) {
|
||||||
if (empty($objects[$name])) {
|
if (empty($objects[$key])) {
|
||||||
$missing[] = $name;
|
$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) {
|
private function loadObjects($names) {
|
||||||
|
@ -146,5 +238,16 @@ final class PhabricatorObjectListQuery extends Phobject {
|
||||||
return $results;
|
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();
|
$name = $user->getUsername();
|
||||||
$phid = $user->getPHID();
|
$phid = $user->getPHID();
|
||||||
|
|
||||||
|
|
||||||
$result = $this->parseObjectList("@{$name}");
|
$result = $this->parseObjectList("@{$name}");
|
||||||
$this->assertEqual(array($phid), $result);
|
$this->assertEqual(array($phid), $result);
|
||||||
|
|
||||||
|
@ -29,6 +28,47 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
||||||
$result = $this->parseObjectList('');
|
$result = $this->parseObjectList('');
|
||||||
$this->assertEqual(array(), $result);
|
$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".
|
// Expect failure when loading a user if objects must be of type "DUCK".
|
||||||
$caught = null;
|
$caught = null;
|
||||||
try {
|
try {
|
||||||
|
@ -67,7 +107,8 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
||||||
private function parseObjectList(
|
private function parseObjectList(
|
||||||
$list,
|
$list,
|
||||||
array $types = array(),
|
array $types = array(),
|
||||||
$allow_partial = false) {
|
$allow_partial = false,
|
||||||
|
$suffixes = array()) {
|
||||||
|
|
||||||
$query = id(new PhabricatorObjectListQuery())
|
$query = id(new PhabricatorObjectListQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
@ -81,6 +122,10 @@ final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
|
||||||
$query->setAllowPartialResults(true);
|
$query->setAllowPartialResults(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($suffixes) {
|
||||||
|
$query->setSuffixes($suffixes);
|
||||||
|
}
|
||||||
|
|
||||||
return $query->execute();
|
return $query->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,19 @@ final class PholioUploadedImageView extends AphrontView {
|
||||||
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
|
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
|
||||||
$thumbnail_uri = $file->getURIForTransform($xform);
|
$thumbnail_uri = $file->getURIForTransform($xform);
|
||||||
|
|
||||||
|
$thumb_img = phutil_tag(
|
||||||
|
'img',
|
||||||
|
array(
|
||||||
|
'class' => 'pholio-thumb-img',
|
||||||
|
'src' => $thumbnail_uri,
|
||||||
|
));
|
||||||
|
|
||||||
$thumb_frame = phutil_tag(
|
$thumb_frame = phutil_tag(
|
||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
'class' => 'pholio-thumb-frame',
|
'class' => 'pholio-thumb-frame',
|
||||||
'style' => 'background-image: url('.$thumbnail_uri.');',
|
),
|
||||||
));
|
$thumb_img);
|
||||||
|
|
||||||
$handle = javelin_tag(
|
$handle = javelin_tag(
|
||||||
'div',
|
'div',
|
||||||
|
|
|
@ -100,9 +100,10 @@ final class ProjectBoardTaskCard extends Phobject {
|
||||||
if ($points !== null) {
|
if ($points !== null) {
|
||||||
$points_tag = id(new PHUITagView())
|
$points_tag = id(new PHUITagView())
|
||||||
->setType(PHUITagView::TYPE_SHADE)
|
->setType(PHUITagView::TYPE_SHADE)
|
||||||
->setShade(PHUITagView::COLOR_BLUE)
|
->setShade(PHUITagView::COLOR_GREY)
|
||||||
->setSlimShady(true)
|
->setSlimShady(true)
|
||||||
->setName($points);
|
->setName($points)
|
||||||
|
->addClass('phui-workcard-points');
|
||||||
$card->addAttribute($points_tag);
|
$card->addAttribute($points_tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,16 @@ final class PhabricatorRepositoryAuditRequest
|
||||||
return $this->assertAttached($this->commit);
|
return $this->assertAttached($this->commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isInteresting() {
|
||||||
|
switch ($this->getAuditStatus()) {
|
||||||
|
case PhabricatorAuditStatusConstants::NONE:
|
||||||
|
case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -33,17 +33,39 @@ final class PhabricatorRepositoryCommitOwnersWorker
|
||||||
$repository,
|
$repository,
|
||||||
$commit,
|
$commit,
|
||||||
PhabricatorUser::getOmnipotentUser());
|
PhabricatorUser::getOmnipotentUser());
|
||||||
|
|
||||||
$affected_packages = PhabricatorOwnersPackage::loadAffectedPackages(
|
$affected_packages = PhabricatorOwnersPackage::loadAffectedPackages(
|
||||||
$repository,
|
$repository,
|
||||||
$affected_paths);
|
$affected_paths);
|
||||||
|
|
||||||
if ($affected_packages) {
|
if (!$affected_packages) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
|
||||||
|
'commitID = %d',
|
||||||
|
$commit->getID());
|
||||||
|
$commit->attachCommitData($data);
|
||||||
|
|
||||||
|
$author_phid = $data->getCommitDetail('authorPHID');
|
||||||
|
$revision_id = $data->getCommitDetail('differential.revisionID');
|
||||||
|
if ($revision_id) {
|
||||||
|
$revision = id(new DifferentialRevisionQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withIDs(array($revision_id))
|
||||||
|
->needReviewerStatus(true)
|
||||||
|
->executeOne();
|
||||||
|
} else {
|
||||||
|
$revision = null;
|
||||||
|
}
|
||||||
|
|
||||||
$requests = id(new PhabricatorRepositoryAuditRequest())
|
$requests = id(new PhabricatorRepositoryAuditRequest())
|
||||||
->loadAllWhere(
|
->loadAllWhere(
|
||||||
'commitPHID = %s',
|
'commitPHID = %s',
|
||||||
$commit->getPHID());
|
$commit->getPHID());
|
||||||
$requests = mpull($requests, null, 'getAuditorPHID');
|
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||||
|
|
||||||
|
|
||||||
foreach ($affected_packages as $package) {
|
foreach ($affected_packages as $package) {
|
||||||
$request = idx($requests, $package->getPHID());
|
$request = idx($requests, $package->getPHID());
|
||||||
if ($request) {
|
if ($request) {
|
||||||
|
@ -57,13 +79,16 @@ final class PhabricatorRepositoryCommitOwnersWorker
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($package->getAuditingEnabled()) {
|
if ($package->getAuditingEnabled()) {
|
||||||
$reasons = $this->checkAuditReasons($commit, $package);
|
$reasons = $this->checkAuditReasons(
|
||||||
|
$commit,
|
||||||
|
$package,
|
||||||
|
$author_phid,
|
||||||
|
$revision);
|
||||||
|
|
||||||
if ($reasons) {
|
if ($reasons) {
|
||||||
$audit_status =
|
$audit_status = PhabricatorAuditStatusConstants::AUDIT_REQUIRED;
|
||||||
PhabricatorAuditStatusConstants::AUDIT_REQUIRED;
|
|
||||||
} else {
|
} else {
|
||||||
$audit_status =
|
$audit_status = PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED;
|
||||||
PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$reasons = array();
|
$reasons = array();
|
||||||
|
@ -84,57 +109,56 @@ final class PhabricatorRepositoryCommitOwnersWorker
|
||||||
$commit->updateAuditStatus($requests);
|
$commit->updateAuditStatus($requests);
|
||||||
$commit->save();
|
$commit->save();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private function checkAuditReasons(
|
private function checkAuditReasons(
|
||||||
PhabricatorRepositoryCommit $commit,
|
PhabricatorRepositoryCommit $commit,
|
||||||
PhabricatorOwnersPackage $package) {
|
PhabricatorOwnersPackage $package,
|
||||||
|
$author_phid,
|
||||||
|
$revision) {
|
||||||
|
|
||||||
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
|
$owner_phids = PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
|
||||||
'commitID = %d',
|
array(
|
||||||
$commit->getID());
|
$package->getID(),
|
||||||
|
));
|
||||||
|
$owner_phids = array_fuse($owner_phids);
|
||||||
|
|
||||||
$reasons = array();
|
$reasons = array();
|
||||||
|
|
||||||
if ($data->getCommitDetail('vsDiff')) {
|
if (!$author_phid) {
|
||||||
$reasons[] = pht('Changed After Revision Was Accepted');
|
|
||||||
}
|
|
||||||
|
|
||||||
$commit_author_phid = $data->getCommitDetail('authorPHID');
|
|
||||||
if (!$commit_author_phid) {
|
|
||||||
$reasons[] = pht('Commit Author Not Recognized');
|
$reasons[] = pht('Commit Author Not Recognized');
|
||||||
|
} else if (isset($owner_phids[$author_phid])) {
|
||||||
|
return $reasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
$revision_id = $data->getCommitDetail('differential.revisionID');
|
if (!$revision) {
|
||||||
|
|
||||||
$revision_author_phid = null;
|
|
||||||
$commit_reviewedby_phid = null;
|
|
||||||
|
|
||||||
if ($revision_id) {
|
|
||||||
$revision = id(new DifferentialRevisionQuery())
|
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
||||||
->withIDs(array($revision_id))
|
|
||||||
->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');
|
$reasons[] = pht('No Revision Specified');
|
||||||
|
return $reasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
$owners_phids = PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
|
$accepted_statuses = array(
|
||||||
array($package->getID()));
|
DifferentialReviewerStatus::STATUS_ACCEPTED,
|
||||||
|
DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER,
|
||||||
|
);
|
||||||
|
$accepted_statuses = array_fuse($accepted_statuses);
|
||||||
|
|
||||||
if (!($commit_author_phid && in_array($commit_author_phid, $owners_phids) ||
|
$found_accept = false;
|
||||||
$commit_reviewedby_phid && in_array($commit_reviewedby_phid,
|
foreach ($revision->getReviewerStatus() as $reviewer) {
|
||||||
$owners_phids))) {
|
$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');
|
$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