diff --git a/bin/herald b/bin/herald new file mode 120000 index 0000000000..53f039ddf1 --- /dev/null +++ b/bin/herald @@ -0,0 +1 @@ +../scripts/setup/manage_herald.php \ No newline at end of file diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 528f1bc886..00f6a0bab7 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,10 +9,10 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '5efaf405', - 'core.pkg.js' => 'b5a949ca', + 'core.pkg.css' => '7489ba0d', + 'core.pkg.js' => '4bde473b', 'differential.pkg.css' => '06dc617c', - 'differential.pkg.js' => 'c1cfa143', + 'differential.pkg.js' => 'ef0b989b', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'maniphest.pkg.css' => '4845691a', @@ -32,7 +32,7 @@ return array( 'rsrc/css/aphront/phabricator-nav-view.css' => '694d7723', 'rsrc/css/aphront/table-view.css' => '8c9bbafe', 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', - 'rsrc/css/aphront/tooltip.css' => '173b9431', + 'rsrc/css/aphront/tooltip.css' => 'cb1397a4', 'rsrc/css/aphront/typeahead-browse.css' => 'f2818435', 'rsrc/css/aphront/typeahead.css' => 'a4a21016', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', @@ -155,7 +155,7 @@ return array( 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '1ba8b707', - 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', + 'rsrc/css/phui/phui-hovercard.css' => '4a484541', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => 'cf24ceec', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', @@ -209,13 +209,13 @@ return array( 'rsrc/externals/font/lato/lato-regular.ttf' => 'e270165b', 'rsrc/externals/font/lato/lato-regular.woff' => '13d39fe2', 'rsrc/externals/font/lato/lato-regular.woff2' => '57a9f742', - 'rsrc/externals/javelin/core/Event.js' => '2ee659ce', + 'rsrc/externals/javelin/core/Event.js' => 'ef7e057f', 'rsrc/externals/javelin/core/Stratcom.js' => '327f418a', 'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '717554e4', 'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85', 'rsrc/externals/javelin/core/__tests__/stratcom.js' => '88bf7313', 'rsrc/externals/javelin/core/__tests__/util.js' => 'e251703d', - 'rsrc/externals/javelin/core/init.js' => '638a4e2b', + 'rsrc/externals/javelin/core/init.js' => '8d83d2a1', 'rsrc/externals/javelin/core/init_node.js' => 'c234aded', 'rsrc/externals/javelin/core/install.js' => '05270951', 'rsrc/externals/javelin/core/util.js' => '93cc50d6', @@ -375,7 +375,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'b49b59d6', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'e0b984b5', + 'rsrc/js/application/diff/DiffChangesetList.js' => '0a84bcc1', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -424,8 +424,7 @@ return array( 'rsrc/js/application/repository/repository-crossreference.js' => '9a860428', 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', - 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', - 'rsrc/js/application/transactions/behavior-comment-actions.js' => '038bf27f', + 'rsrc/js/application/transactions/behavior-comment-actions.js' => '59e27e74', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8f29b364', @@ -524,7 +523,7 @@ return array( 'aphront-panel-view-css' => '8427b78d', 'aphront-table-view-css' => '8c9bbafe', 'aphront-tokenizer-control-css' => '15d5ff71', - 'aphront-tooltip-css' => '173b9431', + 'aphront-tooltip-css' => 'cb1397a4', 'aphront-typeahead-control-css' => 'a4a21016', 'application-search-view-css' => '787f5b76', 'auth-css' => '0877ed6e', @@ -577,7 +576,7 @@ return array( 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-calendar-month-view' => 'fe33e256', 'javelin-behavior-choose-control' => '327a00d1', - 'javelin-behavior-comment-actions' => '038bf27f', + 'javelin-behavior-comment-actions' => '59e27e74', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-menu' => '4047cd35', 'javelin-behavior-conpherence-participant-pane' => 'd057e45a', @@ -676,7 +675,6 @@ return array( 'javelin-behavior-select-content' => 'bf5374ef', 'javelin-behavior-select-on-click' => '4e3e79a6', 'javelin-behavior-setup-check-https' => '491416b3', - 'javelin-behavior-slowvote-embed' => '887ad43f', 'javelin-behavior-stripe-payment-form' => 'a6b98425', 'javelin-behavior-test-payment-form' => 'fc91ab6c', 'javelin-behavior-time-typeahead' => '522431f7', @@ -692,13 +690,13 @@ return array( 'javelin-diffusion-locate-file-source' => '00676f00', 'javelin-dom' => '4976858c', 'javelin-dynval' => 'f6555212', - 'javelin-event' => '2ee659ce', + 'javelin-event' => 'ef7e057f', 'javelin-fx' => '54b612ba', 'javelin-history' => 'd4505101', 'javelin-install' => '05270951', 'javelin-json' => '69adf288', 'javelin-leader' => '7f243deb', - 'javelin-magical-init' => '638a4e2b', + 'javelin-magical-init' => '8d83d2a1', 'javelin-mask' => '8a41885b', 'javelin-quicksand' => '6b8ef10b', 'javelin-reactor' => '2b8de964', @@ -754,7 +752,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'b49b59d6', - 'phabricator-diff-changeset-list' => 'e0b984b5', + 'phabricator-diff-changeset-list' => '0a84bcc1', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -826,7 +824,7 @@ return array( 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => '1ba8b707', 'phui-hovercard' => '1bd28176', - 'phui-hovercard-view-css' => 'f0592bcf', + 'phui-hovercard-view-css' => '4a484541', 'phui-icon-set-selector-css' => '87db8fee', 'phui-icon-view-css' => 'cf24ceec', 'phui-image-mask-css' => 'a8498f9c', @@ -908,15 +906,6 @@ return array( 'javelin-behavior', 'javelin-uri', ), - '038bf27f' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-dom', - 'phuix-form-control-view', - 'phuix-icon-view', - 'javelin-behavior-phabricator-gesture', - ), '040fce04' => array( 'javelin-behavior', 'javelin-request', @@ -958,6 +947,10 @@ return array( 'javelin-dom', 'javelin-router', ), + '0a84bcc1' => array( + 'javelin-install', + 'phuix-button-view', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1060,9 +1053,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2ee659ce' => array( - 'javelin-install', - ), '31420f77' => array( 'javelin-behavior', ), @@ -1321,6 +1311,15 @@ return array( 'javelin-vector', 'javelin-dom', ), + '59e27e74' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'phuix-form-control-view', + 'phuix-icon-view', + 'javelin-behavior-phabricator-gesture', + ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1553,12 +1552,6 @@ return array( 'phabricator-keyboard-shortcut', 'javelin-stratcom', ), - '887ad43f' => array( - 'javelin-behavior', - 'javelin-request', - 'javelin-stratcom', - 'javelin-dom', - ), '8935deef' => array( 'javelin-install', 'javelin-dom', @@ -2020,10 +2013,6 @@ return array( 'phuix-icon-view', 'phabricator-prefab', ), - 'e0b984b5' => array( - 'javelin-install', - 'phuix-button-view', - ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2105,6 +2094,9 @@ return array( 'javelin-behavior', 'javelin-uri', ), + 'ef7e057f' => array( + 'javelin-install', + ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/resources/sql/autopatches/20140805.boardcol.2.php b/resources/sql/autopatches/20140805.boardcol.2.php index 317de4e370..40d6c46ec2 100644 --- a/resources/sql/autopatches/20140805.boardcol.2.php +++ b/resources/sql/autopatches/20140805.boardcol.2.php @@ -45,7 +45,7 @@ foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn_w, 'INSERT INTO %T (boardPHID, columnPHID, objectPHID, sequence) - VALUES %Q', + VALUES %LQ', id(new PhabricatorProjectColumnPosition())->getTableName(), $chunk); } diff --git a/resources/sql/autopatches/20180910.audit.03.status.php b/resources/sql/autopatches/20180910.audit.03.status.php index b7a0bf9f58..ab42d196f0 100644 --- a/resources/sql/autopatches/20180910.audit.03.status.php +++ b/resources/sql/autopatches/20180910.audit.03.status.php @@ -12,17 +12,11 @@ $status_map = array( 5 => 'needs-verification', ); -foreach (new LiskMigrationIterator($table) as $commit) { - $status = $commit->getAuditStatus(); - - if (!isset($status_map[$status])) { - continue; - } - +foreach ($status_map as $old_status => $new_status) { queryfx( $conn, - 'UPDATE %T SET auditStatus = %s WHERE id = %d', - $table->getTableName(), - $status_map[$status], - $commit->getID()); + 'UPDATE %R SET auditStatus = %s WHERE auditStatus = %s', + $table, + $new_status, + $old_status); } diff --git a/resources/sql/autopatches/20180914.audit.01.mailkey.php b/resources/sql/autopatches/20180914.audit.01.mailkey.php index de8419a3c4..60926857ee 100644 --- a/resources/sql/autopatches/20180914.audit.01.mailkey.php +++ b/resources/sql/autopatches/20180914.audit.01.mailkey.php @@ -8,19 +8,27 @@ $properties_table = new PhabricatorMetaMTAMailProperties(); $conn = $properties_table->establishConnection('w'); $iterator = new LiskRawMigrationIterator($commit_conn, $commit_name); -foreach ($iterator as $commit) { +$chunks = new PhutilChunkedIterator($iterator, 100); +foreach ($chunks as $chunk) { + $sql = array(); + foreach ($chunk as $commit) { + $sql[] = qsprintf( + $conn, + '(%s, %s, %d, %d)', + $commit['phid'], + phutil_json_encode( + array( + 'mailKey' => $commit['mailKey'], + )), + PhabricatorTime::getNow(), + PhabricatorTime::getNow()); + } + queryfx( $conn, - 'INSERT IGNORE INTO %T + 'INSERT IGNORE INTO %R (objectPHID, mailProperties, dateCreated, dateModified) - VALUES - (%s, %s, %d, %d)', - $properties_table->getTableName(), - $commit['phid'], - phutil_json_encode( - array( - 'mailKey' => $commit['mailKey'], - )), - PhabricatorTime::getNow(), - PhabricatorTime::getNow()); + VALUES %LQ', + $properties_table, + $sql); } diff --git a/resources/sql/autopatches/20181024.drydock.01.commandprops.sql b/resources/sql/autopatches/20181024.drydock.01.commandprops.sql new file mode 100644 index 0000000000..e808146b02 --- /dev/null +++ b/resources/sql/autopatches/20181024.drydock.01.commandprops.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_command + ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20181024.drydock.02.commanddefaults.sql b/resources/sql/autopatches/20181024.drydock.02.commanddefaults.sql new file mode 100644 index 0000000000..2c336dc40e --- /dev/null +++ b/resources/sql/autopatches/20181024.drydock.02.commanddefaults.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_drydock.drydock_command + SET properties = '{}' WHERE properties = ''; diff --git a/resources/sql/autopatches/20181031.board.01.queryreset.php b/resources/sql/autopatches/20181031.board.01.queryreset.php new file mode 100644 index 0000000000..781cf456ce --- /dev/null +++ b/resources/sql/autopatches/20181031.board.01.queryreset.php @@ -0,0 +1,50 @@ +establishConnection('w'); + +$iterator = new LiskMigrationIterator($table); +$search_engine = id(new ManiphestTaskSearchEngine()) + ->setViewer($viewer); + +foreach ($iterator as $project) { + $default_filter = $project->getDefaultWorkboardFilter(); + if (!strlen($default_filter)) { + continue; + } + + if ($search_engine->isBuiltinQuery($default_filter)) { + continue; + } + + $saved = id(new PhabricatorSavedQueryQuery()) + ->setViewer($viewer) + ->withQueryKeys(array($default_filter)) + ->executeOne(); + if ($saved) { + continue; + } + + $properties = $project->getProperties(); + unset($properties['workboard.filter.default']); + + queryfx( + $conn, + 'UPDATE %T SET properties = %s WHERE id = %d', + $table->getTableName(), + phutil_json_encode($properties), + $project->getID()); + + echo tsprintf( + "%s\n", + pht( + 'Project ("%s") had an invalid query saved as a default workboard '. + 'query. The query has been reset. See T13208.', + $project->getDisplayName())); +} diff --git a/resources/sql/autopatches/20181106.repo.01.sync.sql b/resources/sql/autopatches/20181106.repo.01.sync.sql new file mode 100644 index 0000000000..3302ad8ff1 --- /dev/null +++ b/resources/sql/autopatches/20181106.repo.01.sync.sql @@ -0,0 +1,14 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_syncevent ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + repositoryPHID VARBINARY(64) NOT NULL, + epoch INT UNSIGNED NOT NULL, + devicePHID VARBINARY(64) NOT NULL, + fromDevicePHID VARBINARY(64) NOT NULL, + deviceVersion INT UNSIGNED, + fromDeviceVersion INT UNSIGNED, + resultType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + resultCode INT UNSIGNED NOT NULL, + syncWait BIGINT UNSIGNED NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20181106.repo.02.hook.sql b/resources/sql/autopatches/20181106.repo.02.hook.sql new file mode 100644 index 0000000000..be06923044 --- /dev/null +++ b/resources/sql/autopatches/20181106.repo.02.hook.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_pushevent + ADD hookWait BIGINT UNSIGNED; diff --git a/resources/sql/patches/20130820.file-mailkey-populate.php b/resources/sql/patches/20130820.file-mailkey-populate.php index ba4d6d1606..0db10bef58 100644 --- a/resources/sql/patches/20130820.file-mailkey-populate.php +++ b/resources/sql/patches/20130820.file-mailkey-populate.php @@ -22,12 +22,12 @@ foreach (new LiskRawMigrationIterator($conn_w, 'file') as $row) { } if ($sql) { - foreach (PhabricatorLiskDAO::chunkSQL($sql, ', ') as $chunk) { + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn_w, 'INSERT INTO %T (id, mailKey, phid, byteSize, storageEngine, storageFormat, - storageHandle, dateCreated, dateModified, metadata) VALUES %Q '. + storageHandle, dateCreated, dateModified, metadata) VALUES %LQ '. 'ON DUPLICATE KEY UPDATE mailKey = VALUES(mailKey)', $table_name, $chunk); diff --git a/resources/sql/patches/20131106.diffphid.2.mig.php b/resources/sql/patches/20131106.diffphid.2.mig.php index 67fd14aad0..7976c910eb 100644 --- a/resources/sql/patches/20131106.diffphid.2.mig.php +++ b/resources/sql/patches/20131106.diffphid.2.mig.php @@ -34,10 +34,10 @@ foreach ($chunk_iter as $chunk) { continue; } - foreach (PhabricatorLiskDAO::chunkSQL($sql, ', ') as $sql_chunk) { + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $sql_chunk) { queryfx( $conn_w, - 'INSERT IGNORE INTO %T (id, phid) VALUES %Q + 'INSERT IGNORE INTO %T (id, phid) VALUES %LQ ON DUPLICATE KEY UPDATE phid = VALUES(phid)', $diff_table->getTableName(), $sql_chunk); diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php index 07d6d7cfa2..df49aa7b00 100755 --- a/scripts/repository/commit_hook.php +++ b/scripts/repository/commit_hook.php @@ -17,6 +17,8 @@ // subclasses of PhabricatorConfigSiteSource to read it and build an instance // environment. +$hook_start = microtime(true); + if ($argc > 1) { $context = $argv[1]; $context = explode(':', $context, 2); @@ -35,7 +37,8 @@ if ($argc < 2) { throw new Exception(pht('usage: commit-hook ')); } -$engine = new DiffusionCommitHookEngine(); +$engine = id(new DiffusionCommitHookEngine()) + ->setStartTime($hook_start); $repository = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -204,23 +207,23 @@ try { +---------------------------------------------------------------+ | * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * | +---------------------------------------------------------------+ - \ - \ ^ /^ - \ / \ // \ - \ |\___/| / \// .\ - \ /V V \__ / // | \ \ *----* - / / \/_/ // | \ \ \ | - @___@` \/_ // | \ \ \/\ \ - 0/0/| \/_ // | \ \ \ \ - 0/0/0/0/| \/// | \ \ | | - 0/0/0/0/0/_|_ / ( // | \ _\ | / - 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / / - ,-} _ *-.|.-~-. .~ ~ - \ \__/ `/\ / ~-. _ .-~ / - \____(Oo) *. } { / - ( (--) .----~-.\ \-` .~ - //__\\\\ \ DENIED! ///.----..< \ _ -~ - // \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~ + \ + \ ^ /^ + \ / \ // \ + \ |\___/| / \// .\ + \ /V V \__ / // | \ \ *----* + / / \/_/ // | \ \ \ | + @___@` \/_ // | \ \ \/\ \ + 0/0/| \/_ // | \ \ \ \ + 0/0/0/0/| \/// | \ \ | | + 0/0/0/0/0/_|_ / ( // | \ _\ | / + 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / / + ,-} _ *-.|.-~-. .~ ~ + * \__/ `/\ / ~-. _ .-~ / + \____(Oo) *. } { / + ( (..) .----~-.\ \-` .~ + //___\\\\ \ DENIED! ///.----..< \ _ -~ + // \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~ EOTXT ); diff --git a/scripts/setup/manage_herald.php b/scripts/setup/manage_herald.php new file mode 100755 index 0000000000..4ebd94f820 --- /dev/null +++ b/scripts/setup/manage_herald.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline(pht('manage Herald')); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilClassMapQuery()) + ->setAncestorClass('HeraldManagementWorkflow') + ->execute(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php index 0f2275cda8..eea2db4712 100755 --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -307,7 +307,7 @@ try { $ssh_log->setData( array( 'c' => $err, - 'T' => (int)(1000000 * (microtime(true) - $ssh_start_time)), + 'T' => phutil_microseconds_since($ssh_start_time), )); exit($err); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a9c1c35f89..ba68c741f1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -839,6 +839,7 @@ phutil_register_library_map(array( 'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php', 'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php', 'DiffusionLowLevelCommitQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php', + 'DiffusionLowLevelFilesizeQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelFilesizeQuery.php', 'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php', 'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php', 'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php', @@ -949,10 +950,16 @@ phutil_register_library_map(array( 'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php', 'DiffusionRepositoryIdentityEditor' => 'applications/diffusion/editor/DiffusionRepositoryIdentityEditor.php', 'DiffusionRepositoryIdentitySearchEngine' => 'applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php', + 'DiffusionRepositoryLimitsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryLimitsManagementPanel.php', 'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php', 'DiffusionRepositoryManageController' => 'applications/diffusion/controller/DiffusionRepositoryManageController.php', 'DiffusionRepositoryManagePanelsController' => 'applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php', + 'DiffusionRepositoryManagementBuildsPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementBuildsPanelGroup.php', + 'DiffusionRepositoryManagementIntegrationsPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementIntegrationsPanelGroup.php', + 'DiffusionRepositoryManagementMainPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementMainPanelGroup.php', + 'DiffusionRepositoryManagementOtherPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementOtherPanelGroup.php', 'DiffusionRepositoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryManagementPanel.php', + 'DiffusionRepositoryManagementPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementPanelGroup.php', 'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php', 'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php', 'DiffusionRepositoryProfilePictureController' => 'applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php', @@ -992,6 +999,9 @@ phutil_register_library_map(array( 'DiffusionSymbolController' => 'applications/diffusion/controller/DiffusionSymbolController.php', 'DiffusionSymbolDatasource' => 'applications/diffusion/typeahead/DiffusionSymbolDatasource.php', 'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php', + 'DiffusionSyncLogListController' => 'applications/diffusion/controller/DiffusionSyncLogListController.php', + 'DiffusionSyncLogListView' => 'applications/diffusion/view/DiffusionSyncLogListView.php', + 'DiffusionSyncLogSearchEngine' => 'applications/diffusion/query/DiffusionSyncLogSearchEngine.php', 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', 'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php', @@ -1146,6 +1156,7 @@ phutil_register_library_map(array( 'DrydockLeaseReclaimLogType' => 'applications/drydock/logtype/DrydockLeaseReclaimLogType.php', 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php', + 'DrydockLeaseSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockLeaseSearchConduitAPIMethod.php', 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', @@ -1325,6 +1336,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php', 'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php', 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', + 'HarbormasterBuildPlanSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php', 'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php', 'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php', 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', @@ -1348,6 +1360,7 @@ phutil_register_library_map(array( 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 'HarbormasterBuildTargetPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildTargetPHIDType.php', 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', + 'HarbormasterBuildTargetSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildTargetSearchEngine.php', 'HarbormasterBuildTransaction' => 'applications/harbormaster/storage/HarbormasterBuildTransaction.php', 'HarbormasterBuildTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php', 'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php', @@ -1362,6 +1375,7 @@ phutil_register_library_map(array( 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', + 'HarbormasterBuildableSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildableSearchAPIMethod.php', 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', 'HarbormasterBuildableStatus' => 'applications/harbormaster/constants/HarbormasterBuildableStatus.php', 'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php', @@ -1427,6 +1441,7 @@ phutil_register_library_map(array( 'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php', 'HarbormasterStepViewController' => 'applications/harbormaster/controller/HarbormasterStepViewController.php', 'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php', + 'HarbormasterTargetSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterTargetSearchAPIMethod.php', 'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php', 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', @@ -1483,6 +1498,7 @@ phutil_register_library_map(array( 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', 'HeraldMailableState' => 'applications/herald/state/HeraldMailableState.php', 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', + 'HeraldManagementWorkflow' => 'applications/herald/management/HeraldManagementWorkflow.php', 'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php', 'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php', 'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php', @@ -1532,6 +1548,7 @@ phutil_register_library_map(array( 'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php', 'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php', 'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php', + 'HeraldTestManagementWorkflow' => 'applications/herald/management/HeraldTestManagementWorkflow.php', 'HeraldTextFieldValue' => 'applications/herald/value/HeraldTextFieldValue.php', 'HeraldTokenizerFieldValue' => 'applications/herald/value/HeraldTokenizerFieldValue.php', 'HeraldTransactionQuery' => 'applications/herald/query/HeraldTransactionQuery.php', @@ -4073,8 +4090,13 @@ phutil_register_library_map(array( 'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php', 'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php', 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', + 'PhabricatorRepositoryActivateTransaction' => 'applications/repository/xaction/PhabricatorRepositoryActivateTransaction.php', 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php', + 'PhabricatorRepositoryAutocloseOnlyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php', + 'PhabricatorRepositoryAutocloseTransaction' => 'applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php', + 'PhabricatorRepositoryBlueprintsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryBlueprintsTransaction.php', 'PhabricatorRepositoryBranch' => 'applications/repository/storage/PhabricatorRepositoryBranch.php', + 'PhabricatorRepositoryCallsignTransaction' => 'applications/repository/xaction/PhabricatorRepositoryCallsignTransaction.php', 'PhabricatorRepositoryCommit' => 'applications/repository/storage/PhabricatorRepositoryCommit.php', 'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php', 'PhabricatorRepositoryCommitData' => 'applications/repository/storage/PhabricatorRepositoryCommitData.php', @@ -4087,12 +4109,19 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitRef' => 'applications/repository/engine/PhabricatorRepositoryCommitRef.php', 'PhabricatorRepositoryCommitTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryCommitTestCase.php', 'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php', + 'PhabricatorRepositoryCopyTimeLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryCopyTimeLimitTransaction.php', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php', + 'PhabricatorRepositoryDangerousTransaction' => 'applications/repository/xaction/PhabricatorRepositoryDangerousTransaction.php', + 'PhabricatorRepositoryDefaultBranchTransaction' => 'applications/repository/xaction/PhabricatorRepositoryDefaultBranchTransaction.php', + 'PhabricatorRepositoryDescriptionTransaction' => 'applications/repository/xaction/PhabricatorRepositoryDescriptionTransaction.php', 'PhabricatorRepositoryDestructibleCodex' => 'applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php', 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', 'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php', + 'PhabricatorRepositoryEncodingTransaction' => 'applications/repository/xaction/PhabricatorRepositoryEncodingTransaction.php', 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', + 'PhabricatorRepositoryEnormousTransaction' => 'applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php', 'PhabricatorRepositoryFerretEngine' => 'applications/repository/search/PhabricatorRepositoryFerretEngine.php', + 'PhabricatorRepositoryFilesizeLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php', 'PhabricatorRepositoryFulltextEngine' => 'applications/repository/search/PhabricatorRepositoryFulltextEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', @@ -4135,6 +4164,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php', 'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php', 'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php', + 'PhabricatorRepositoryNameTransaction' => 'applications/repository/xaction/PhabricatorRepositoryNameTransaction.php', + 'PhabricatorRepositoryNotifyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php', 'PhabricatorRepositoryOldRef' => 'applications/repository/storage/PhabricatorRepositoryOldRef.php', 'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php', 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', @@ -4151,6 +4182,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryPushLogQuery' => 'applications/repository/query/PhabricatorRepositoryPushLogQuery.php', 'PhabricatorRepositoryPushLogSearchEngine' => 'applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php', 'PhabricatorRepositoryPushMailWorker' => 'applications/repository/worker/PhabricatorRepositoryPushMailWorker.php', + 'PhabricatorRepositoryPushPolicyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryPushPolicyTransaction.php', 'PhabricatorRepositoryPushReplyHandler' => 'applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php', 'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php', 'PhabricatorRepositoryRefCursor' => 'applications/repository/storage/PhabricatorRepositoryRefCursor.php', @@ -4159,15 +4191,27 @@ phutil_register_library_map(array( 'PhabricatorRepositoryRefEngine' => 'applications/repository/engine/PhabricatorRepositoryRefEngine.php', 'PhabricatorRepositoryRefPosition' => 'applications/repository/storage/PhabricatorRepositoryRefPosition.php', 'PhabricatorRepositoryRepositoryPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php', + 'PhabricatorRepositorySVNSubpathTransaction' => 'applications/repository/xaction/PhabricatorRepositorySVNSubpathTransaction.php', 'PhabricatorRepositorySchemaSpec' => 'applications/repository/storage/PhabricatorRepositorySchemaSpec.php', 'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php', + 'PhabricatorRepositoryServiceTransaction' => 'applications/repository/xaction/PhabricatorRepositoryServiceTransaction.php', + 'PhabricatorRepositorySlugTransaction' => 'applications/repository/xaction/PhabricatorRepositorySlugTransaction.php', + 'PhabricatorRepositoryStagingURITransaction' => 'applications/repository/xaction/PhabricatorRepositoryStagingURITransaction.php', 'PhabricatorRepositoryStatusMessage' => 'applications/repository/storage/PhabricatorRepositoryStatusMessage.php', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositorySvnCommitMessageParserWorker.php', 'PhabricatorRepositorySymbol' => 'applications/repository/storage/PhabricatorRepositorySymbol.php', + 'PhabricatorRepositorySymbolLanguagesTransaction' => 'applications/repository/xaction/PhabricatorRepositorySymbolLanguagesTransaction.php', + 'PhabricatorRepositorySymbolSourcesTransaction' => 'applications/repository/xaction/PhabricatorRepositorySymbolSourcesTransaction.php', + 'PhabricatorRepositorySyncEvent' => 'applications/repository/storage/PhabricatorRepositorySyncEvent.php', + 'PhabricatorRepositorySyncEventPHIDType' => 'applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php', + 'PhabricatorRepositorySyncEventQuery' => 'applications/repository/query/PhabricatorRepositorySyncEventQuery.php', 'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php', + 'PhabricatorRepositoryTouchLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php', + 'PhabricatorRepositoryTrackOnlyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryTrackOnlyTransaction.php', 'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php', 'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php', + 'PhabricatorRepositoryTransactionType' => 'applications/repository/xaction/PhabricatorRepositoryTransactionType.php', 'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php', 'PhabricatorRepositoryURI' => 'applications/repository/storage/PhabricatorRepositoryURI.php', 'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php', @@ -4178,6 +4222,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', 'PhabricatorRepositoryURITransaction' => 'applications/repository/storage/PhabricatorRepositoryURITransaction.php', 'PhabricatorRepositoryURITransactionQuery' => 'applications/repository/query/PhabricatorRepositoryURITransactionQuery.php', + 'PhabricatorRepositoryVCSTransaction' => 'applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php', 'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php', 'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php', 'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php', @@ -6217,6 +6262,7 @@ phutil_register_library_map(array( 'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelCommitQuery' => 'DiffusionLowLevelQuery', + 'DiffusionLowLevelFilesizeQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery', @@ -6326,10 +6372,16 @@ phutil_register_library_map(array( 'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryIdentityEditor' => 'PhabricatorApplicationTransactionEditor', 'DiffusionRepositoryIdentitySearchEngine' => 'PhabricatorApplicationSearchEngine', + 'DiffusionRepositoryLimitsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryListController' => 'DiffusionController', 'DiffusionRepositoryManageController' => 'DiffusionController', 'DiffusionRepositoryManagePanelsController' => 'DiffusionRepositoryManageController', + 'DiffusionRepositoryManagementBuildsPanelGroup' => 'DiffusionRepositoryManagementPanelGroup', + 'DiffusionRepositoryManagementIntegrationsPanelGroup' => 'DiffusionRepositoryManagementPanelGroup', + 'DiffusionRepositoryManagementMainPanelGroup' => 'DiffusionRepositoryManagementPanelGroup', + 'DiffusionRepositoryManagementOtherPanelGroup' => 'DiffusionRepositoryManagementPanelGroup', 'DiffusionRepositoryManagementPanel' => 'Phobject', + 'DiffusionRepositoryManagementPanelGroup' => 'Phobject', 'DiffusionRepositoryPath' => 'Phobject', 'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryProfilePictureController' => 'DiffusionController', @@ -6369,6 +6421,9 @@ phutil_register_library_map(array( 'DiffusionSymbolController' => 'DiffusionController', 'DiffusionSymbolDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery', + 'DiffusionSyncLogListController' => 'DiffusionLogController', + 'DiffusionSyncLogListView' => 'AphrontView', + 'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', 'DiffusionTagTableView' => 'DiffusionView', @@ -6539,6 +6594,7 @@ phutil_register_library_map(array( 'DrydockLease' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', + 'PhabricatorConduitResultInterface', ), 'DrydockLeaseAcquiredLogType' => 'DrydockLogType', 'DrydockLeaseActivatedLogType' => 'DrydockLogType', @@ -6559,6 +6615,7 @@ phutil_register_library_map(array( 'DrydockLeaseReclaimLogType' => 'DrydockLogType', 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseReleasedLogType' => 'DrydockLogType', + 'DrydockLeaseSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseStatus' => 'PhabricatorObjectStatus', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', @@ -6779,6 +6836,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorNgramsInterface', + 'PhabricatorConduitResultInterface', 'PhabricatorProjectInterface', ), 'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource', @@ -6789,6 +6847,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams', 'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildPlanSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -6821,9 +6880,11 @@ phutil_register_library_map(array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'HarbormasterBuildTargetPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildTargetSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -6835,6 +6896,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'HarbormasterBuildableInterface', + 'PhabricatorConduitResultInterface', 'PhabricatorDestructibleInterface', ), 'HarbormasterBuildableActionController' => 'HarbormasterController', @@ -6842,6 +6904,7 @@ phutil_register_library_map(array( 'HarbormasterBuildableListController' => 'HarbormasterController', 'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildableSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildableStatus' => 'Phobject', 'HarbormasterBuildableTransaction' => 'PhabricatorApplicationTransaction', @@ -6905,6 +6968,7 @@ phutil_register_library_map(array( 'HarbormasterStepEditController' => 'HarbormasterPlanController', 'HarbormasterStepViewController' => 'HarbormasterPlanController', 'HarbormasterTargetEngine' => 'Phobject', + 'HarbormasterTargetSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'HarbormasterTargetWorker' => 'HarbormasterWorker', 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', @@ -6967,6 +7031,7 @@ phutil_register_library_map(array( 'HeraldInvalidConditionException' => 'Exception', 'HeraldMailableState' => 'HeraldState', 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', + 'HeraldManagementWorkflow' => 'PhabricatorManagementWorkflow', 'HeraldManiphestTaskAdapter' => 'HeraldAdapter', 'HeraldNewController' => 'HeraldController', 'HeraldNewObjectField' => 'HeraldField', @@ -7023,6 +7088,7 @@ phutil_register_library_map(array( 'HeraldSupportActionGroup' => 'HeraldActionGroup', 'HeraldSupportFieldGroup' => 'HeraldFieldGroup', 'HeraldTestConsoleController' => 'HeraldController', + 'HeraldTestManagementWorkflow' => 'HeraldManagementWorkflow', 'HeraldTextFieldValue' => 'HeraldFieldValue', 'HeraldTokenizerFieldValue' => 'HeraldFieldValue', 'HeraldTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -7132,7 +7198,10 @@ phutil_register_library_map(array( 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView', 'LiskChunkTestCase' => 'PhabricatorTestCase', - 'LiskDAO' => 'Phobject', + 'LiskDAO' => array( + 'Phobject', + 'AphrontDatabaseTableRefInterface', + ), 'LiskDAOSet' => 'Phobject', 'LiskDAOTestCase' => 'PhabricatorTestCase', 'LiskEphemeralObjectException' => 'Exception', @@ -9975,11 +10044,16 @@ phutil_register_library_map(array( 'PhabricatorFulltextInterface', 'PhabricatorFerretInterface', ), + 'PhabricatorRepositoryActivateTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryAuditRequest' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), + 'PhabricatorRepositoryAutocloseOnlyTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryAutocloseTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryBlueprintsTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryBranch' => 'PhabricatorRepositoryDAO', + 'PhabricatorRepositoryCallsignTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryCommit' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', @@ -10012,12 +10086,19 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitRef' => 'Phobject', 'PhabricatorRepositoryCommitTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorRepositoryCopyTimeLimitTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', + 'PhabricatorRepositoryDangerousTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryDefaultBranchTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryDescriptionTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryDestructibleCodex' => 'PhabricatorDestructibleCodex', 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorRepositoryEncodingTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryEngine' => 'Phobject', + 'PhabricatorRepositoryEnormousTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorRepositoryFilesizeLimitTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', @@ -10068,6 +10149,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryMirror' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine', + 'PhabricatorRepositoryNameTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryNotifyTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryOldRef' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', @@ -10096,6 +10179,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryPushLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryPushLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorRepositoryPushMailWorker' => 'PhabricatorWorker', + 'PhabricatorRepositoryPushPolicyTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryPushReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorRepositoryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryRefCursor' => array( @@ -10107,15 +10191,30 @@ phutil_register_library_map(array( 'PhabricatorRepositoryRefEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryRefPosition' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryRepositoryPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorRepositorySVNSubpathTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositorySchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorRepositoryServiceTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositorySlugTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryStagingURITransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryStatusMessage' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositorySymbol' => 'PhabricatorRepositoryDAO', + 'PhabricatorRepositorySymbolLanguagesTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositorySymbolSourcesTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositorySyncEvent' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorRepositorySyncEventPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorRepositorySyncEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase', - 'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorRepositoryTouchLimitTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryTrackOnlyTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryTransaction' => 'PhabricatorModularTransaction', 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorRepositoryTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorRepositoryType' => 'Phobject', 'PhabricatorRepositoryURI' => array( 'PhabricatorRepositoryDAO', @@ -10132,6 +10231,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURITransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryURITransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorRepositoryVCSTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO', 'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler', 'PhabricatorResourceSite' => 'PhabricatorSite', diff --git a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php index cb1dea3533..442cb08f66 100644 --- a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php @@ -83,6 +83,18 @@ final class PhabricatorPolicyRequestExceptionHandler $dialog->appendList($list); } + // If the install is in developer mode, include a stack trace for the + // exception. When debugging things, it isn't always obvious where a + // policy exception came from and this can make it easier to hunt down + // bugs or improve ambiguous/confusing messaging. + + $is_developer = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); + if ($is_developer) { + $dialog->appendChild( + id(new AphrontStackTraceView()) + ->setTrace($throwable->getTrace())); + } + if ($request->isAjax()) { $dialog->addCancelButton('/', pht('Close')); } else { diff --git a/src/applications/almanac/editor/AlmanacBindingEditEngine.php b/src/applications/almanac/editor/AlmanacBindingEditEngine.php index 5146578fff..66db7fcbab 100644 --- a/src/applications/almanac/editor/AlmanacBindingEditEngine.php +++ b/src/applications/almanac/editor/AlmanacBindingEditEngine.php @@ -136,7 +136,7 @@ final class AlmanacBindingEditEngine id(new PhabricatorTextEditField()) ->setKey('service') ->setLabel(pht('Service')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( AlmanacBindingServiceTransaction::TRANSACTIONTYPE) ->setDescription(pht('Service to create a binding for.')) @@ -146,7 +146,7 @@ final class AlmanacBindingEditEngine id(new PhabricatorTextEditField()) ->setKey('interface') ->setLabel(pht('Interface')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( AlmanacBindingInterfaceTransaction::TRANSACTIONTYPE) ->setDescription(pht('Interface to bind the service to.')) @@ -156,7 +156,7 @@ final class AlmanacBindingEditEngine id(new PhabricatorBoolEditField()) ->setKey('disabled') ->setLabel(pht('Disabled')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( AlmanacBindingDisableTransaction::TRANSACTIONTYPE) ->setDescription(pht('Disable or enable the binding.')) diff --git a/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php b/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php index 30c371d6f9..ca57113bf2 100644 --- a/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php @@ -150,7 +150,7 @@ final class AlmanacInterfaceEditEngine id(new PhabricatorTextEditField()) ->setKey('device') ->setLabel(pht('Device')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( AlmanacInterfaceDeviceTransaction::TRANSACTIONTYPE) ->setDescription(pht('When creating an interface, set the device.')) diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php index 00e54962b3..b63075543f 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -136,7 +136,7 @@ final class AlmanacServiceEditEngine id(new PhabricatorTextEditField()) ->setKey('type') ->setLabel(pht('Type')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( AlmanacServiceTypeTransaction::TRANSACTIONTYPE) ->setDescription(pht('When creating a service, set the type.')) diff --git a/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php b/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php index 965c193f40..425ee04b3c 100644 --- a/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php +++ b/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php @@ -30,14 +30,14 @@ final class AlmanacPropertiesEditEngineExtension ->setConduitDescription( pht('Pass a map of values to set one or more properties.')) ->setConduitTypeDescription(pht('Map of property names to values.')) - ->setIsConduitOnly(true), + ->setIsFormField(false), id(new AlmanacDeletePropertyEditField()) ->setKey('property.delete') ->setTransactionType($object->getAlmanacPropertyDeleteTransactionType()) ->setConduitDescription( pht('Pass a list of property names to delete properties.')) ->setConduitTypeDescription(pht('List of property names.')) - ->setIsConduitOnly(true), + ->setIsFormField(false), ); } diff --git a/src/applications/almanac/query/AlmanacInterfaceQuery.php b/src/applications/almanac/query/AlmanacInterfaceQuery.php index bb6fc2f9d9..d5886761c1 100644 --- a/src/applications/almanac/query/AlmanacInterfaceQuery.php +++ b/src/applications/almanac/query/AlmanacInterfaceQuery.php @@ -121,7 +121,7 @@ final class AlmanacInterfaceQuery $address->getAddress(), $address->getPort()); } - $where[] = implode(' OR ', $parts); + $where[] = qsprintf($conn, '%LO', $parts); } return $where; diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index ee5105f193..e286573e93 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -58,6 +58,10 @@ final class PhabricatorCommitSearchEngine $query->withAncestorsOf($map['ancestorsOf']); } + if ($map['identifiers']) { + $query->withIdentifiers($map['identifiers']); + } + return $query; } @@ -130,6 +134,15 @@ final class PhabricatorCommitSearchEngine pht( 'Find commits which are ancestors of a particular ref, '. 'like "master".')), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Identifiers')) + ->setKey('identifiers') + ->setDescription( + pht( + 'Find commits with particular identifiers (usually, hashes). '. + 'Supports full or partial identifiers (like "abcd12340987..." or '. + '"abcd1234") and qualified or unqualified identifiers (like '. + '"rXabcd1234" or "abcd1234").')), ); } diff --git a/src/applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php b/src/applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php index 6ac616b2a1..794b0b5e22 100644 --- a/src/applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php +++ b/src/applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php @@ -99,6 +99,82 @@ final class PhabricatorAuthPasswordTestCase extends PhabricatorTestCase { $this->assertTrue($account_engine->isUniquePassword($password2)); } + public function testPasswordBlocklisting() { + $user = $this->generateNewTestUser(); + + $user + ->setUsername('iasimov') + ->setRealName('Isaac Asimov') + ->save(); + + $test_type = PhabricatorAuthPassword::PASSWORD_TYPE_TEST; + $content_source = $this->newContentSource(); + + $engine = id(new PhabricatorAuthPasswordEngine()) + ->setViewer($user) + ->setContentSource($content_source) + ->setPasswordType($test_type) + ->setObject($user); + + $env = PhabricatorEnv::beginScopedEnv(); + $env->overrideEnvConfig('account.minimum-password-length', 4); + + $passwords = array( + 'a23li432m9mdf' => true, + + // Empty. + '' => false, + + // Password length tests. + 'xh3' => false, + 'xh32' => true, + + // In common password blocklist. + 'password1' => false, + + // Tests for the account identifier blocklist. + 'isaac' => false, + 'iasimov' => false, + 'iasimov1' => false, + 'asimov' => false, + 'iSaAc' => false, + '32IASIMOV' => false, + 'i-am-iasimov-this-is-my-long-strong-password' => false, + 'iasimo' => false, + + // These are okay: although they're visually similar, they aren't mutual + // substrings of any identifier. + 'iasimo1' => true, + 'isa1mov' => true, + ); + + foreach ($passwords as $password => $expect) { + $this->assertBlocklistedPassword($engine, $password, $expect); + } + } + + private function assertBlocklistedPassword( + PhabricatorAuthPasswordEngine $engine, + $raw_password, + $expect_valid) { + + $envelope_1 = new PhutilOpaqueEnvelope($raw_password); + $envelope_2 = new PhutilOpaqueEnvelope($raw_password); + + $caught = null; + try { + $engine->checkNewPassword($envelope_1, $envelope_2); + } catch (PhabricatorAuthPasswordException $exception) { + $caught = $exception; + } + + $this->assertEqual( + $expect_valid, + !($caught instanceof PhabricatorAuthPasswordException), + pht('Validity of password "%s".', $raw_password)); + } + + public function testPasswordUpgrade() { $weak_hasher = new PhabricatorIteratedMD5PasswordHasher(); diff --git a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php index 067cea30e7..f763b0987f 100644 --- a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php @@ -115,7 +115,9 @@ final class PhabricatorAuthPasswordEngine // revoked passwords or colliding passwords either, so we can skip these // checks. - if ($this->getObject()->getPHID()) { + $object = $this->getObject(); + + if ($object->getPHID()) { if ($this->isRevokedPassword($password)) { throw new PhabricatorAuthPasswordException( pht( @@ -132,6 +134,66 @@ final class PhabricatorAuthPasswordEngine pht('Not Unique')); } } + + // Prevent use of passwords which are similar to any object identifier. + // For example, if your username is "alincoln", your password may not be + // "alincoln", "lincoln", or "alincoln1". + $viewer = $this->getViewer(); + $blocklist = $object->newPasswordBlocklist($viewer, $this); + + // Smallest number of overlapping characters that we'll consider to be + // too similar. + $minimum_similarity = 4; + + // Add the domain name to the blocklist. + $base_uri = PhabricatorEnv::getAnyBaseURI(); + $base_uri = new PhutilURI($base_uri); + $blocklist[] = $base_uri->getDomain(); + + // Generate additional subterms by splitting the raw blocklist on + // characters like "@", " " (space), and "." to break up email addresses, + // readable names, and domain names into components. + $terms_map = array(); + foreach ($blocklist as $term) { + $terms_map[$term] = $term; + foreach (preg_split('/[ @.]/', $term) as $subterm) { + $terms_map[$subterm] = $term; + } + } + + // Skip very short terms: it's okay if your password has the substring + // "com" in it somewhere even if the install is on "mycompany.com". + foreach ($terms_map as $term => $source) { + if (strlen($term) < $minimum_similarity) { + unset($terms_map[$term]); + } + } + + // Normalize terms for comparison. + $normal_map = array(); + foreach ($terms_map as $term => $source) { + $term = phutil_utf8_strtolower($term); + $normal_map[$term] = $source; + } + + // Finally, make sure that none of the terms appear in the password, + // and that the password does not appear in any of the terms. + $normal_password = phutil_utf8_strtolower($raw_password); + if (strlen($normal_password) >= $minimum_similarity) { + foreach ($normal_map as $term => $source) { + if (strpos($term, $normal_password) === false && + strpos($normal_password, $term) === false) { + continue; + } + + throw new PhabricatorAuthPasswordException( + pht( + 'The password you entered is very similar to a nonsecret account '. + 'identifier (like a username or email address). Choose a more '. + 'distinct password.'), + pht('Not Distinct')); + } + } } public function isValidPassword(PhutilOpaqueEnvelope $envelope) { diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 6e40cdde98..4ce86e8f97 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -127,12 +127,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { u.* %Q FROM %T u JOIN %T s ON u.phid = s.userPHID - AND s.type = %s AND s.sessionKey = %s %Q', + AND s.type = %s AND s.sessionKey = %P %Q', $cache_selects, $user_table->getTableName(), $session_table->getTableName(), $session_type, - $session_key, + new PhutilOpaqueEnvelope($session_key), $cache_joins); if (!$info) { @@ -345,6 +345,33 @@ final class PhabricatorAuthSessionEngine extends Phobject { /* -( High Security )------------------------------------------------------ */ + /** + * Require the user respond to a high security (MFA) check. + * + * This method differs from @{method:requireHighSecuritySession} in that it + * does not upgrade the user's session as a side effect. This method is + * appropriate for one-time checks. + * + * @param PhabricatorUser User whose session needs to be in high security. + * @param AphrontReqeust Current request. + * @param string URI to return the user to if they cancel. + * @return PhabricatorAuthHighSecurityToken Security token. + * @task hisec + */ + public function requireHighSecurityToken( + PhabricatorUser $viewer, + AphrontRequest $request, + $cancel_uri) { + + return $this->newHighSecurityToken( + $viewer, + $request, + $cancel_uri, + false, + false); + } + + /** * Require high security, or prompt the user to enter high security. * @@ -352,6 +379,11 @@ final class PhabricatorAuthSessionEngine extends Phobject { * token. Otherwise, it will throw an exception which will eventually * be converted into a multi-factor authentication workflow. * + * This method upgrades the user's session to high security for a short + * period of time, and is appropriate if you anticipate they may need to + * take multiple high security actions. To perform a one-time check instead, + * use @{method:requireHighSecurityToken}. + * * @param PhabricatorUser User whose session needs to be in high security. * @param AphrontReqeust Current request. * @param string URI to return the user to if they cancel. @@ -367,11 +399,30 @@ final class PhabricatorAuthSessionEngine extends Phobject { $cancel_uri, $jump_into_hisec = false) { + return $this->newHighSecurityToken( + $viewer, + $request, + $cancel_uri, + false, + true); + } + + private function newHighSecurityToken( + PhabricatorUser $viewer, + AphrontRequest $request, + $cancel_uri, + $jump_into_hisec, + $upgrade_session) { + if (!$viewer->hasSession()) { throw new Exception( pht('Requiring a high-security session from a user with no session!')); } + // TODO: If a user answers a "requireHighSecurityToken()" prompt and hits + // a "requireHighSecuritySession()" prompt a short time later, the one-shot + // token should be good enough to upgrade the session. + $session = $viewer->getSession(); // Check if the session is already in high security mode. @@ -441,6 +492,11 @@ final class PhabricatorAuthSessionEngine extends Phobject { return $this->issueHighSecurityToken($session, true); } + // If we aren't upgrading the session itself, just issue a token. + if (!$upgrade_session) { + return $this->issueHighSecurityToken($session, true); + } + $until = time() + phutil_units('15 minutes in seconds'); $session->setHighSecurityUntil($until); @@ -809,15 +865,15 @@ final class PhabricatorAuthSessionEngine extends Phobject { } if ($cache_selects) { - $cache_selects = ', '.implode(', ', $cache_selects); + $cache_selects = qsprintf($conn, ', %LQ', $cache_selects); } else { - $cache_selects = ''; + $cache_selects = qsprintf($conn, ''); } if ($cache_joins) { - $cache_joins = implode(' ', $cache_joins); + $cache_joins = qsprintf($conn, '%LJ', $cache_joins); } else { - $cache_joins = ''; + $cache_joins = qsprintf($conn, ''); } return array($cache_selects, $cache_joins, $cache_map, $types_map); diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php index 373adfbb54..7d7aec921f 100644 --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -185,7 +185,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { public static function generateNewTOTPKey() { - return strtoupper(Filesystem::readRandomCharacters(16)); + return strtoupper(Filesystem::readRandomCharacters(32)); } public static function verifyTOTPCode( diff --git a/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php b/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php index 36a296b209..af20db3ed4 100644 --- a/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php +++ b/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php @@ -6,4 +6,22 @@ interface PhabricatorAuthPasswordHashInterface { PhutilOpaqueEnvelope $envelope, PhabricatorAuthPassword $password); + /** + * Return a list of strings which passwords associated with this object may + * not be similar to. + * + * This method allows you to prevent users from selecting their username + * as their password or picking other passwords which are trivially similar + * to an account or object identifier. + * + * @param PhabricatorUser The user selecting the password. + * @param PhabricatorAuthPasswordEngine The password engine updating a + * password. + * @return list Blocklist of nonsecret identifiers which the password + * should not be similar to. + */ + public function newPasswordBlocklist( + PhabricatorUser $viewer, + PhabricatorAuthPasswordEngine $engine); + } diff --git a/src/applications/auth/query/PhabricatorAuthInviteQuery.php b/src/applications/auth/query/PhabricatorAuthInviteQuery.php index 1ae617db65..55b325d603 100644 --- a/src/applications/auth/query/PhabricatorAuthInviteQuery.php +++ b/src/applications/auth/query/PhabricatorAuthInviteQuery.php @@ -59,26 +59,26 @@ final class PhabricatorAuthInviteQuery return $invites; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->emailAddresses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'emailAddress IN (%Ls)', $this->emailAddresses); } @@ -90,21 +90,21 @@ final class PhabricatorAuthInviteQuery } $where[] = qsprintf( - $conn_r, + $conn, 'verificationHash IN (%Ls)', $hashes); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'authorPHID IN (%Ls)', $this->authorPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php b/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php index 44e5913290..626c80348f 100644 --- a/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php +++ b/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php @@ -54,26 +54,26 @@ final class PhabricatorAuthProviderConfigQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->providerClasses) { + if ($this->providerClasses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'providerClass IN (%Ls)', $this->providerClasses); } @@ -84,16 +84,16 @@ final class PhabricatorAuthProviderConfigQuery break; case self::STATUS_ENABLED: $where[] = qsprintf( - $conn_r, + $conn, 'isEnabled = 1'); break; default: throw new Exception(pht("Unknown status '%s'!", $status)); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index d8474085b2..3a310ed173 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -111,7 +111,7 @@ final class PhabricatorAuthSSHKeyQuery $key->getType(), $key->getHash()); } - $where[] = implode(' OR ', $sql); + $where[] = qsprintf($conn, '%LO', $sql); } if ($this->isActive !== null) { diff --git a/src/applications/auth/query/PhabricatorAuthSessionQuery.php b/src/applications/auth/query/PhabricatorAuthSessionQuery.php index dea95dd450..25928e72c1 100644 --- a/src/applications/auth/query/PhabricatorAuthSessionQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSessionQuery.php @@ -65,44 +65,44 @@ final class PhabricatorAuthSessionQuery return $sessions; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->identityPHIDs) { + if ($this->identityPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'userPHID IN (%Ls)', $this->identityPHIDs); } - if ($this->sessionKeys) { + if ($this->sessionKeys !== null) { $hashes = array(); foreach ($this->sessionKeys as $session_key) { $hashes[] = PhabricatorHash::weakDigest($session_key); } $where[] = qsprintf( - $conn_r, + $conn, 'sessionKey IN (%Ls)', $hashes); } - if ($this->sessionTypes) { + if ($this->sessionTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'type IN (%Ls)', $this->sessionTypes); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php index e7a441343c..721184852c 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php @@ -126,7 +126,7 @@ final class PhabricatorBadgesEditEngine ->setValue($object->getDescription()), id(new PhabricatorUsersEditField()) ->setKey('award') - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setDescription(pht('New badge award recipients.')) ->setConduitTypeDescription(pht('New badge award recipients.')) ->setTransactionType( @@ -134,7 +134,7 @@ final class PhabricatorBadgesEditEngine ->setLabel(pht('Award Recipients')), id(new PhabricatorUsersEditField()) ->setKey('revoke') - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setDescription(pht('Revoke badge award recipients.')) ->setConduitTypeDescription(pht('Revoke badge award recipients.')) ->setTransactionType( diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index e276e035e4..1cabeb0709 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -545,7 +545,7 @@ abstract class PhabricatorApplication case PhabricatorPolicyCapability::CAN_VIEW: return $this->canUninstall(); case PhabricatorPolicyCapability::CAN_EDIT: - return false; + return true; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'edit', true); diff --git a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php index c6a52024fe..0b4609074a 100644 --- a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php +++ b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php @@ -38,7 +38,7 @@ final class PhabricatorKeyValueDatabaseCache $conn_w, 'INSERT INTO %T (cacheKeyHash, cacheKey, cacheFormat, cacheData, - cacheCreated, cacheExpires) VALUES %Q + cacheCreated, cacheExpires) VALUES %LQ ON DUPLICATE KEY UPDATE cacheKey = VALUES(cacheKey), cacheFormat = VALUES(cacheFormat), diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index a9eab9376a..1b23b13fbf 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -148,7 +148,7 @@ final class PhabricatorCalendarEventEditEngine ->setDescription(pht('Cancel the event.')) ->setTransactionType( PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setConduitDescription(pht('Cancel or restore the event.')) ->setConduitTypeDescription(pht('True to cancel the event.')) ->setValue($object->getIsCancelled()), @@ -161,7 +161,7 @@ final class PhabricatorCalendarEventEditEngine ->setDescription(pht('Host of the event.')) ->setTransactionType( PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly($this->getIsCreate()) + ->setIsFormField(!$this->getIsCreate()) ->setConduitDescription(pht('Change the host of the event.')) ->setConduitTypeDescription(pht('New event host.')) ->setSingleValue($object->getHostPHID()), diff --git a/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php index 8cd35e7323..bc8fc360c6 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php @@ -99,7 +99,7 @@ final class PhabricatorCalendarExportEditEngine ->setDescription(pht('Disable the export.')) ->setTransactionType( PhabricatorCalendarExportDisableTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setConduitDescription(pht('Disable or restore the export.')) ->setConduitTypeDescription(pht('True to cancel the export.')) ->setValue($object->getIsDisabled()), diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php index 90b4962ca9..7be3969671 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php @@ -101,7 +101,7 @@ final class PhabricatorCalendarImportEditEngine ->setDescription(pht('Disable the import.')) ->setTransactionType( PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setConduitDescription(pht('Disable or restore the import.')) ->setConduitTypeDescription(pht('True to cancel the import.')) ->setValue($object->getIsDisabled()), @@ -111,7 +111,7 @@ final class PhabricatorCalendarImportEditEngine ->setDescription(pht('Delete all events from this source.')) ->setTransactionType( PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setConduitDescription(pht('Disable or restore the import.')) ->setConduitTypeDescription(pht('True to delete imported events.')) ->setValue(false), @@ -121,7 +121,7 @@ final class PhabricatorCalendarImportEditEngine ->setDescription(pht('Reload events imported from this source.')) ->setTransactionType( PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setConduitDescription(pht('Disable or restore the import.')) ->setConduitTypeDescription(pht('True to reload the import.')) ->setValue(false), diff --git a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php index 59d8476c87..cf7fc30554 100644 --- a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php +++ b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php @@ -245,7 +245,7 @@ final class PhabricatorCalendarNotificationEngine $conn, 'INSERT IGNORE INTO %T (eventPHID, targetPHID, utcInitialEpoch, didNotifyEpoch) - VALUES %Q', + VALUES %LQ', $table->getTableName(), $chunk); } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php index 5ffa2a8aab..683d6cd918 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php @@ -49,47 +49,47 @@ final class PhabricatorCalendarEventInviteeQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->eventPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'eventPHID IN (%Ls)', $this->eventPHIDs); } if ($this->inviteePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'inviteePHID IN (%Ls)', $this->inviteePHIDs); } if ($this->inviterPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'inviterPHID IN (%Ls)', $this->inviterPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'status = %d', $this->statuses); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 9b7189cfdf..fc1399fdb3 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -444,8 +444,8 @@ final class PhabricatorCalendarEventQuery $where[] = qsprintf( $conn, - '%Q', - implode(' OR ', $sql)); + '%LO', + $sql); } if ($this->isStub !== null) { @@ -509,15 +509,10 @@ final class PhabricatorCalendarEventQuery return parent::shouldGroupQueryResultRows(); } - protected function getApplicationSearchObjectPHIDColumn() { - return 'event.phid'; - } - public function getQueryApplicationClass() { return 'PhabricatorCalendarApplication'; } - protected function willFilterPage(array $events) { $instance_of_event_phids = array(); $recurring_events = array(); diff --git a/src/applications/chatlog/query/PhabricatorChatLogChannelQuery.php b/src/applications/chatlog/query/PhabricatorChatLogChannelQuery.php index 2aded5c11b..a13514eec7 100644 --- a/src/applications/chatlog/query/PhabricatorChatLogChannelQuery.php +++ b/src/applications/chatlog/query/PhabricatorChatLogChannelQuery.php @@ -33,14 +33,14 @@ final class PhabricatorChatLogChannelQuery return $logs; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); if ($this->channelIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->channelIDs); @@ -48,12 +48,12 @@ final class PhabricatorChatLogChannelQuery if ($this->channels) { $where[] = qsprintf( - $conn_r, + $conn, 'channelName IN (%Ls)', $this->channels); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/chatlog/query/PhabricatorChatLogQuery.php b/src/applications/chatlog/query/PhabricatorChatLogQuery.php index d174fdac90..88cf6da7e3 100644 --- a/src/applications/chatlog/query/PhabricatorChatLogQuery.php +++ b/src/applications/chatlog/query/PhabricatorChatLogQuery.php @@ -55,26 +55,26 @@ final class PhabricatorChatLogQuery return $events; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - if ($this->maximumEpoch) { + if ($this->maximumEpoch !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'epoch <= %d', $this->maximumEpoch); } - if ($this->channelIDs) { + if ($this->channelIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'channelID IN (%Ld)', $this->channelIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index aea3fc0908..f217adf154 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -109,15 +109,13 @@ final class PhabricatorConduitAPIController $error_info = $ex->getMessage(); } - $time_end = microtime(true); - $log ->setCallerPHID( isset($conduit_user) ? $conduit_user->getPHID() : null) ->setError((string)$error_code) - ->setDuration(1000000 * ($time_end - $time_start)); + ->setDuration(phutil_microseconds_since($time_start)); if (!PhabricatorEnv::isReadOnly()) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); diff --git a/src/applications/conduit/ssh/ConduitSSHWorkflow.php b/src/applications/conduit/ssh/ConduitSSHWorkflow.php index 603a479ea0..0b4bd3cdef 100644 --- a/src/applications/conduit/ssh/ConduitSSHWorkflow.php +++ b/src/applications/conduit/ssh/ConduitSSHWorkflow.php @@ -73,15 +73,13 @@ final class ConduitSSHWorkflow extends PhabricatorSSHWorkflow { // if the response is large and the receiver is slow to read it. $this->getIOChannel()->flush(); - $time_end = microtime(true); - $connection_id = idx($metadata, 'connectionID'); $log = id(new PhabricatorConduitMethodCallLog()) ->setCallerPHID($this->getSSHUser()->getPHID()) ->setConnectionID($connection_id) ->setMethod($method) ->setError((string)$error_code) - ->setDuration(1000000 * ($time_end - $time_start)) + ->setDuration(phutil_microseconds_since($time_start)) ->save(); } } diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php index f776c3ec0c..deccf1ef5c 100644 --- a/src/applications/config/editor/PhabricatorConfigEditor.php +++ b/src/applications/config/editor/PhabricatorConfigEditor.php @@ -118,7 +118,8 @@ final class PhabricatorConfigEditor PhabricatorUser $user, PhabricatorConfigEntry $config_entry, $value, - PhabricatorContentSource $source) { + PhabricatorContentSource $source, + $acting_as_phid = null) { $xaction = id(new PhabricatorConfigTransaction()) ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) @@ -133,6 +134,10 @@ final class PhabricatorConfigEditor ->setContinueOnNoEffect(true) ->setContentSource($source); + if ($acting_as_phid) { + $editor->setActingAsPHID($acting_as_phid); + } + $editor->applyTransactions($config_entry, array($xaction)); } diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index 8a236a883b..ad8a62d184 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -81,14 +81,6 @@ of each approach are: - Required if private reply-to addresses are configured. - Mail messages are sent in the language of user preference. -EODOC -)); - - $herald_hints_description = $this->deformat(pht(<<setLocked(true) ->setDescription(pht('Domain used for reply email addresses.')) ->addExample('phabricator.example.com', ''), - $this->newOption('metamta.herald.show-hints', 'bool', true) - ->setBoolOptions( - array( - pht('Show Herald Hints'), - pht('No Herald Hints'), - )) - ->setSummary(pht('Show hints about Herald rules in email.')) - ->setDescription($herald_hints_description), $this->newOption('metamta.recipients.show-hints', 'bool', true) ->setBoolOptions( array( diff --git a/src/applications/config/query/PhabricatorConfigEntryQuery.php b/src/applications/config/query/PhabricatorConfigEntryQuery.php index 56bf15a267..f46fdb7d1e 100644 --- a/src/applications/config/query/PhabricatorConfigEntryQuery.php +++ b/src/applications/config/query/PhabricatorConfigEntryQuery.php @@ -31,26 +31,26 @@ final class PhabricatorConfigEntryQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php index 2cb7763110..4a93396671 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -115,8 +115,8 @@ final class PhabricatorConfigSchemaQuery extends Phobject { 'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE, IS_NULLABLE, EXTRA FROM INFORMATION_SCHEMA.COLUMNS - WHERE (%Q)', - '('.implode(') OR (', $sql).')'); + WHERE %LO', + $sql); $column_info = igroup($column_info, 'TABLE_SCHEMA'); } else { $column_info = array(); diff --git a/src/applications/conpherence/editor/ConpherenceEditEngine.php b/src/applications/conpherence/editor/ConpherenceEditEngine.php index 91cd8fd081..f5f850e637 100644 --- a/src/applications/conpherence/editor/ConpherenceEditEngine.php +++ b/src/applications/conpherence/editor/ConpherenceEditEngine.php @@ -76,8 +76,8 @@ final class ConpherenceEditEngine $initial_phids = $participant_phids; } - // Only show participants on create or conduit, not edit - $conduit_only = !$this->getIsCreate(); + // Only show participants on create or conduit, not edit. + $show_participants = (bool)$this->getIsCreate(); return array( id(new PhabricatorTextEditField()) @@ -103,7 +103,7 @@ final class ConpherenceEditEngine ->setKey('participants') ->setValue($participant_phids) ->setInitialValue($initial_phids) - ->setIsConduitOnly($conduit_only) + ->setIsFormField($show_participants) ->setAliases(array('users', 'members', 'participants', 'userPHID')) ->setDescription(pht('Room participants.')) ->setUseEdgeTransactions(true) diff --git a/src/applications/conpherence/query/ConpherenceFulltextQuery.php b/src/applications/conpherence/query/ConpherenceFulltextQuery.php index ba734049f8..99ee4f559d 100644 --- a/src/applications/conpherence/query/ConpherenceFulltextQuery.php +++ b/src/applications/conpherence/query/ConpherenceFulltextQuery.php @@ -38,19 +38,19 @@ final class ConpherenceFulltextQuery return $rows; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->threadPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'i.threadPHID IN (%Ls)', $this->threadPHIDs); } if ($this->previousTransactionPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'i.previousTransactionPHID IN (%Ls)', $this->previousTransactionPHIDs); } @@ -61,12 +61,12 @@ final class ConpherenceFulltextQuery $compiled_query = $compiler->compileQuery($tokens); $where[] = qsprintf( - $conn_r, + $conn, 'MATCH(i.corpus) AGAINST (%s IN BOOLEAN MODE)', $compiled_query); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function buildOrderByClause(AphrontDatabaseConnection $conn_r) { diff --git a/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php b/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php index 268af0ccf1..c9ac9f76c1 100644 --- a/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php +++ b/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php @@ -57,7 +57,7 @@ final class ConpherenceParticipantCountQuery } } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function buildGroupByClause(AphrontDatabaseConnection $conn) { diff --git a/src/applications/conpherence/query/ConpherenceParticipantQuery.php b/src/applications/conpherence/query/ConpherenceParticipantQuery.php index fb4c3eff0f..0316d5e2c2 100644 --- a/src/applications/conpherence/query/ConpherenceParticipantQuery.php +++ b/src/applications/conpherence/query/ConpherenceParticipantQuery.php @@ -38,7 +38,7 @@ final class ConpherenceParticipantQuery extends PhabricatorOffsetPagedQuery { $this->participantPHIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function buildOrderClause(AphrontDatabaseConnection $conn) { diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php index 394b4735a5..8fca624b44 100644 --- a/src/applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php @@ -40,15 +40,20 @@ final class PhabricatorDaemonManagementLogWorkflow $query->withIDs($ids); } $daemons = $query->execute(); + $daemons = mpull($daemons, null, 'getID'); - if (!$daemons) { - if ($ids) { - throw new PhutilArgumentUsageException( - pht('No daemon(s) with id(s) "%s" exist!', implode(', ', $ids))); - } else { - throw new PhutilArgumentUsageException( - pht('No daemons are running.')); + if ($ids) { + foreach ($ids as $id) { + if (!isset($daemons[$id])) { + throw new PhutilArgumentUsageException( + pht( + 'No log record exists for a daemon with ID "%s".', + $id)); + } } + } else if (!$daemons) { + throw new PhutilArgumentUsageException( + pht('No log records exist for any daemons.')); } $console = PhutilConsole::getConsole(); diff --git a/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php b/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php index 5602bda309..8bf9be9423 100644 --- a/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php +++ b/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php @@ -107,9 +107,9 @@ final class PhabricatorLockLogManagementWorkflow } if (!$parts) { - $constraint = '1 = 1'; + $constraint = qsprintf($conn, '1 = 1'); } else { - $constraint = '('.implode(') AND (', $parts).')'; + $constraint = qsprintf($conn, '%LA', $parts); } $logs = $table->loadAllWhere( diff --git a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php index 961c1cfc61..2c5b6baa3b 100644 --- a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php +++ b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php @@ -124,46 +124,47 @@ final class PhabricatorDaemonLogQuery return $daemons; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->notIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id NOT IN (%Ld)', $this->notIDs); } if ($this->getStatusConstants()) { $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ls)', $this->getStatusConstants()); } if ($this->daemonClasses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'daemon IN (%Ls)', $this->daemonClasses); } if ($this->daemonIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'daemonID IN (%Ls)', $this->daemonIDs); } - $where[] = $this->buildPagingClause($conn_r); - return $this->formatWhereClause($where); + $where[] = $this->buildPagingClause($conn); + + return $this->formatWhereClause($conn, $where); } private function getStatusConstants() { diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index f835854e2f..31b175b09d 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -72,20 +72,35 @@ final class DifferentialReviewersField return array(); } + $all_resigned = true; + $all_disabled = true; + $any_reviewers = false; + foreach ($this->getValue() as $reviewer) { - if (!$handles[$reviewer->getReviewerPHID()]->isDisabled()) { - return array(); + $reviewer_phid = $reviewer->getReviewerPHID(); + + $any_reviewers = true; + + if (!$handles[$reviewer_phid]->isDisabled()) { + $all_disabled = false; + } + + if (!$reviewer->isResigned()) { + $all_resigned = false; } } $warnings = array(); - if ($this->getValue()) { + if (!$any_reviewers) { + $warnings[] = pht( + 'This revision needs review, but there are no reviewers specified.'); + } else if ($all_disabled) { $warnings[] = pht( 'This revision needs review, but all specified reviewers are '. 'disabled or inactive.'); - } else { + } else if ($all_resigned) { $warnings[] = pht( - 'This revision needs review, but there are no reviewers specified.'); + 'This revision needs review, but all reviewers have resigned.'); } return $warnings; diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php index 0404bd6201..ffae7fab16 100644 --- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php +++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php @@ -138,7 +138,7 @@ final class DifferentialRevisionEditEngine DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDListHTTPParameterType()) ->setSingleValue($diff_phid) - ->setIsConduitOnly(!$diff) + ->setIsFormField((bool)$diff) ->setIsReorderable(false) ->setIsDefaultable(false) ->setIsInvisible(true) @@ -225,7 +225,7 @@ final class DifferentialRevisionEditEngine $fields[] = id(new PhabricatorHandlesEditField()) ->setKey('tasks') ->setUseEdgeTransactions(true) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue( 'edge:type', @@ -245,7 +245,7 @@ final class DifferentialRevisionEditEngine $fields[] = id(new PhabricatorBoolEditField()) ->setKey('draft') ->setLabel(pht('Hold as Draft')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setOptions( pht('Autosubmit Once Builds Finish'), pht('Hold as Draft')) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 5bc33dabe1..7fa4cd61e2 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -451,6 +451,11 @@ final class DifferentialTransactionEditor // conditions for acceptance. This usually happens after an accepting // reviewer resigns or is removed. $new_status = DifferentialRevisionStatus::NEEDS_REVIEW; + } else if ($was_revision) { + // This revision was "Needs Revision", but no longer has any rejecting + // reviewers. This usually happens after the last rejecting reviewer + // resigns or is removed. Put the revision back in "Needs Review". + $new_status = DifferentialRevisionStatus::NEEDS_REVIEW; } if ($new_status === null) { diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php index 0bc914914b..c1430320e3 100644 --- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php +++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php @@ -361,7 +361,7 @@ final class DifferentialInlineCommentMailView return $parser->render( $start - $context, - $length + 1 + (2 * $context), + $length + (2 * $context), array()); } diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index 596ea0e426..e214aa16a4 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -874,9 +874,16 @@ final class DifferentialChangesetParser extends Phobject { $offset_map = $this->old; } + // NOTE: Inline comments use zero-based lengths. For example, a comment + // that starts and ends on line 123 has length 0. Rendering considers + // this range to have length 1. Probably both should agree, but that + // ship likely sailed long ago. Tweak things here to get the two systems + // to agree. See PHI985, where this affected mail rendering of inline + // comments left on the final line of a file. + $range_end = $this->getOffset($offset_map, $range_start + $range_len); $range_start = $this->getOffset($offset_map, $range_start); - $range_len = ($range_end - $range_start); + $range_len = ($range_end - $range_start) + 1; } $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset); diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php index 3f8ea62e14..38549cd933 100644 --- a/src/applications/differential/query/DifferentialInlineCommentQuery.php +++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php @@ -107,31 +107,31 @@ final class DifferentialInlineCommentQuery return head($this->execute()); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); // Only find inline comments. $where[] = qsprintf( - $conn_r, + $conn, 'changesetID IS NOT NULL'); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->revisionPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'revisionPHID IN (%Ls)', $this->revisionPHIDs); } @@ -139,28 +139,28 @@ final class DifferentialInlineCommentQuery if ($this->drafts === null) { if ($this->deletedDrafts) { $where[] = qsprintf( - $conn_r, + $conn, '(authorPHID = %s) OR (transactionPHID IS NOT NULL)', $this->getViewer()->getPHID()); } else { $where[] = qsprintf( - $conn_r, + $conn, '(authorPHID = %s AND isDeleted = 0) OR (transactionPHID IS NOT NULL)', $this->getViewer()->getPHID()); } } else if ($this->drafts) { $where[] = qsprintf( - $conn_r, + $conn, '(authorPHID = %s AND isDeleted = 0) AND (transactionPHID IS NULL)', $this->getViewer()->getPHID()); } else { $where[] = qsprintf( - $conn_r, + $conn, 'transactionPHID IS NOT NULL'); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function adjustInlinesForChangesets( diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index bc93de2db2..fdd4904bee 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -453,7 +453,7 @@ final class DifferentialRevisionQuery private function loadData() { $table = $this->newResultObject(); - $conn_r = $table->establishConnection('r'); + $conn = $table->establishConnection('r'); $selects = array(); @@ -469,13 +469,13 @@ final class DifferentialRevisionQuery $this->authors = array_merge($basic_authors, $this->responsibles); $this->reviewers = $basic_reviewers; - $selects[] = $this->buildSelectStatement($conn_r); + $selects[] = $this->buildSelectStatement($conn); // Build the query where the responsible users are reviewers, or // projects they are members of are reviewers. $this->authors = $basic_authors; $this->reviewers = array_merge($basic_reviewers, $this->responsibles); - $selects[] = $this->buildSelectStatement($conn_r); + $selects[] = $this->buildSelectStatement($conn); // Put everything back like it was. $this->authors = $basic_authors; @@ -486,21 +486,35 @@ final class DifferentialRevisionQuery throw $ex; } } else { - $selects[] = $this->buildSelectStatement($conn_r); + $selects[] = $this->buildSelectStatement($conn); } if (count($selects) > 1) { + $unions = null; + foreach ($selects as $select) { + if (!$unions) { + $unions = $select; + continue; + } + + $unions = qsprintf( + $conn, + '%Q UNION DISTINCT %Q', + $unions, + $select); + } + $query = qsprintf( - $conn_r, + $conn, '%Q %Q %Q', - implode(' UNION DISTINCT ', $selects), - $this->buildOrderClause($conn_r, true), - $this->buildLimitClause($conn_r)); + $unions, + $this->buildOrderClause($conn, true), + $this->buildLimitClause($conn)); } else { $query = head($selects); } - return queryfx_all($conn_r, '%Q', $query); + return queryfx_all($conn, '%Q', $query); } private function buildSelectStatement(AphrontDatabaseConnection $conn_r) { @@ -542,26 +556,26 @@ final class DifferentialRevisionQuery /** * @task internal */ - private function buildJoinsClause($conn_r) { + private function buildJoinsClause(AphrontDatabaseConnection $conn) { $joins = array(); if ($this->pathIDs) { $path_table = new DifferentialAffectedPath(); $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName()); } if ($this->commitHashes) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME); } if ($this->ccs) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T e_ccs ON e_ccs.src = r.phid '. 'AND e_ccs.type = %s '. 'AND e_ccs.dst in (%Ls)', @@ -572,7 +586,7 @@ final class DifferentialRevisionQuery if ($this->reviewers) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T reviewer ON reviewer.revisionPHID = r.phid AND reviewer.reviewerStatus != %s AND reviewer.reviewerPHID in (%Ls)', @@ -583,7 +597,7 @@ final class DifferentialRevisionQuery if ($this->draftAuthors) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T has_draft ON has_draft.srcPHID = r.phid AND has_draft.type = %s AND has_draft.dstPHID IN (%Ls)', @@ -594,21 +608,21 @@ final class DifferentialRevisionQuery if ($this->commitPHIDs) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T commits ON commits.revisionID = r.id', DifferentialRevision::TABLE_COMMIT); } - $joins[] = $this->buildJoinClauseParts($conn_r); + $joins[] = $this->buildJoinClauseParts($conn); - return $this->formatJoinClause($joins); + return $this->formatJoinClause($conn, $joins); } /** * @task internal */ - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->pathIDs) { @@ -616,32 +630,32 @@ final class DifferentialRevisionQuery $repo_info = igroup($this->pathIDs, 'repositoryID'); foreach ($repo_info as $repository_id => $paths) { $path_clauses[] = qsprintf( - $conn_r, + $conn, '(p.repositoryID = %d AND p.pathID IN (%Ld))', $repository_id, ipull($paths, 'pathID')); } - $path_clauses = '('.implode(' OR ', $path_clauses).')'; + $path_clauses = qsprintf($conn, '%LO', $path_clauses); $where[] = $path_clauses; } if ($this->authors) { $where[] = qsprintf( - $conn_r, + $conn, 'r.authorPHID IN (%Ls)', $this->authors); } if ($this->revIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'r.id IN (%Ld)', $this->revIDs); } if ($this->repositoryPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'r.repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } @@ -651,67 +665,67 @@ final class DifferentialRevisionQuery foreach ($this->commitHashes as $info) { list($type, $hash) = $info; $hash_clauses[] = qsprintf( - $conn_r, + $conn, '(hash_rel.type = %s AND hash_rel.hash = %s)', $type, $hash); } - $hash_clauses = '('.implode(' OR ', $hash_clauses).')'; + $hash_clauses = qsprintf($conn, '%LO', $hash_clauses); $where[] = $hash_clauses; } if ($this->commitPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'commits.commitPHID IN (%Ls)', $this->commitPHIDs); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'r.phid IN (%Ls)', $this->phids); } if ($this->branches) { $where[] = qsprintf( - $conn_r, + $conn, 'r.branchName in (%Ls)', $this->branches); } if ($this->updatedEpochMin !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.dateModified >= %d', $this->updatedEpochMin); } if ($this->updatedEpochMax !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.dateModified <= %d', $this->updatedEpochMax); } if ($this->createdEpochMin !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.dateCreated >= %d', $this->createdEpochMin); } if ($this->createdEpochMax !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.dateCreated <= %d', $this->createdEpochMax); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'r.status in (%Ls)', $this->statuses); } @@ -725,13 +739,14 @@ final class DifferentialRevisionQuery DifferentialLegacyQuery::STATUS_CLOSED); } $where[] = qsprintf( - $conn_r, + $conn, 'r.status in (%Ls)', $statuses); } - $where[] = $this->buildWhereClauseParts($conn_r); - return $this->formatWhereClause($where); + $where[] = $this->buildWhereClauseParts($conn); + + return $this->formatWhereClause($conn, $where); } diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index cf2b557764..99dc8bebc6 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -57,6 +57,11 @@ abstract class DifferentialRevisionActionTransaction return null; } + protected function getRevisionActionSubmitButtonText( + DifferentialRevision $revision) { + return null; + } + public static function loadAllActions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) @@ -110,6 +115,9 @@ abstract class DifferentialRevisionActionTransaction $group_key = $this->getRevisionActionGroupKey(); $field->setCommentActionGroupKey($group_key); + $button_text = $this->getRevisionActionSubmitButtonText($revision); + $field->setActionSubmitButtonText($button_text); + // Currently, every revision action conflicts with every other // revision action: for example, you can not simultaneously Accept and // Reject a revision. diff --git a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php index 5aafa536f3..a296597bc7 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php @@ -59,11 +59,21 @@ final class DifferentialRevisionCommandeerTransaction } protected function validateAction($object, PhabricatorUser $viewer) { - if ($object->isClosed()) { + // If a revision has already landed, we generally want to discourage + // reopening and reusing it since this tends to create a big mess (users + // should create a new revision instead). Thus, we stop you from + // commandeering closed revisions. + + // See PHI985. If the revision was abandoned, there's no peril in allowing + // the commandeer since the change (likely) never actually landed. So + // it's okay to commandeer abandoned revisions. + + if ($object->isClosed() && !$object->isAbandoned()) { throw new Exception( pht( 'You can not commandeer this revision because it has already '. - 'been closed. You can only commandeer open revisions.')); + 'been closed. You can only commandeer open or abandoned '. + 'revisions.')); } if ($this->isViewerRevisionAuthor($object, $viewer)) { diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index a3d2699c74..169e41dec5 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -19,6 +19,19 @@ final class DifferentialRevisionRequestReviewTransaction } } + protected function getRevisionActionSubmitButtonText( + DifferentialRevision $revision) { + + // See PHI975. When the action stack will promote the revision out of + // draft, change the button text from "Submit Quietly". + if ($revision->isDraft()) { + return pht('Publish Revision'); + } + + return null; + } + + public function getColor() { return 'sky'; } diff --git a/src/applications/differential/xaction/DifferentialRevisionUpdateTransaction.php b/src/applications/differential/xaction/DifferentialRevisionUpdateTransaction.php index bf1e6de870..33bbaceb7e 100644 --- a/src/applications/differential/xaction/DifferentialRevisionUpdateTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionUpdateTransaction.php @@ -57,6 +57,12 @@ final class DifferentialRevisionUpdateTransaction // Harbormaster. See discussion in T8650. $diff->setRevisionID($object->getID()); $diff->save(); + } + + public function didCommitTransaction($object, $value) { + $editor = $this->getEditor(); + $diff = $editor->requireDiff($value); + $omnipotent = PhabricatorUser::getOmnipotentUser(); // If there are any outstanding buildables for this diff, tell // Harbormaster that their containers need to be updated. This is @@ -64,7 +70,7 @@ final class DifferentialRevisionUpdateTransaction // and unit results. $buildables = id(new HarbormasterBuildableQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($omnipotent) ->withManualBuildables(false) ->withBuildablePHIDs(array($diff->getPHID())) ->execute(); diff --git a/src/applications/diffusion/DiffusionLintSaveRunner.php b/src/applications/diffusion/DiffusionLintSaveRunner.php index 980234f95d..a1a2fe7d32 100644 --- a/src/applications/diffusion/DiffusionLintSaveRunner.php +++ b/src/applications/diffusion/DiffusionLintSaveRunner.php @@ -229,9 +229,9 @@ final class DiffusionLintSaveRunner extends Phobject { $this->conn, 'INSERT INTO %T (branchID, path, line, code, severity, name, description) - VALUES %Q', + VALUES %LQ', PhabricatorRepository::TABLE_LINTMESSAGE, - implode(', ', $values)); + $values); } $this->conn->saveTransaction(); @@ -295,10 +295,10 @@ final class DiffusionLintSaveRunner extends Phobject { } queryfx( $this->conn, - 'UPDATE %T SET authorPHID = %s WHERE %Q', + 'UPDATE %T SET authorPHID = %s WHERE %LO', PhabricatorRepository::TABLE_LINTMESSAGE, $author, - implode(' OR ', $where)); + $where); } $this->conn->saveTransaction(); diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index a7baea1968..d635f5d559 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -118,6 +118,9 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { $this->getQueryRoutePattern() => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', ), + 'synclog/' => array( + $this->getQueryRoutePattern() => 'DiffusionSyncLogListController', + ), 'pulllog/' => array( $this->getQueryRoutePattern() => 'DiffusionPullLogListController', ), diff --git a/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php index 62878c0291..ac2c223c1e 100644 --- a/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php @@ -21,6 +21,7 @@ final class DiffusionBranchQueryConduitAPIMethod 'limit' => 'optional int', 'offset' => 'optional int', 'contains' => 'optional string', + 'patterns' => 'optional list', ); } @@ -31,15 +32,17 @@ final class DiffusionBranchQueryConduitAPIMethod $contains = $request->getValue('contains'); if (strlen($contains)) { - // See PHI720. If the standard "branch" field is provided, use it - // as the "pattern" argument to "git branch ..." to let callers test - // for reachability from a particular branch head. - $pattern = $request->getValue('branch'); - if (strlen($pattern)) { - $pattern_argv = array($pattern); - } else { - $pattern_argv = array(); - } + // See PHI958 (and, earlier, PHI720). If "patterns" are provided, pass + // them to "git branch ..." to let callers test for reachability from + // particular branch heads. + $patterns_argv = $request->getValue('patterns', array()); + PhutilTypeSpec::checkMap( + array( + 'patterns' => $patterns_argv, + ), + array( + 'patterns' => 'list', + )); // NOTE: We can't use DiffusionLowLevelGitRefQuery here because // `git for-each-ref` does not support `--contains`. @@ -47,14 +50,14 @@ final class DiffusionBranchQueryConduitAPIMethod list($stdout) = $repository->execxLocalCommand( 'branch --verbose --no-abbrev --contains %s -- %Ls', $contains, - $pattern_argv); + $patterns_argv); $ref_map = DiffusionGitBranch::parseLocalBranchOutput( $stdout); } else { list($stdout) = $repository->execxLocalCommand( 'branch -r --verbose --no-abbrev --contains %s -- %Ls', $contains, - $pattern_argv); + $patterns_argv); $ref_map = DiffusionGitBranch::parseRemoteBranchOutput( $stdout, DiffusionGitBranch::DEFAULT_GIT_REMOTE); diff --git a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php index 2e272d69a7..fe99471b0c 100644 --- a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php @@ -451,13 +451,13 @@ final class DiffusionBrowseQueryConduitAPIMethod WHERE repositoryID = %d AND parentID = %d AND existed = 1 - AND (%Q) + AND (%LO) ORDER BY pathName', PhabricatorRepository::TABLE_FILESYSTEM, PhabricatorRepository::TABLE_PATH, $repository->getID(), $path_id, - implode(' OR ', $sql)); + $sql); $loadable_commits = array(); foreach ($browse as $key => $file) { diff --git a/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php index be0d2c4faa..256d023345 100644 --- a/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php @@ -106,8 +106,8 @@ final class DiffusionUpdateCoverageConduitAPIMethod foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn, - 'INSERT INTO %T (branchID, pathID, commitID, coverage) VALUES %Q'. - ' ON DUPLICATE KEY UPDATE coverage=VALUES(coverage)', + 'INSERT INTO %T (branchID, pathID, commitID, coverage) VALUES %LQ'. + ' ON DUPLICATE KEY UPDATE coverage = VALUES(coverage)', $table_name, $chunk); } diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index af3ed571ce..704435882d 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -276,13 +276,13 @@ final class DiffusionLintController extends DiffusionController { array_keys($branch), $path->getPath()); if ($path->getExcluded()) { - $where[] = 'NOT '.$condition; + $where[] = qsprintf($conn, 'NOT %Q', $condition); } else { $or[] = $condition; } } } - $where[] = '('.implode(' OR ', $or).')'; + $where[] = qsprintf($conn, '%LO', $or); } return queryfx_all( @@ -296,11 +296,11 @@ final class DiffusionLintController extends DiffusionController { COUNT(DISTINCT path) AS files, COUNT(*) AS n FROM %T - WHERE %Q + WHERE %LA GROUP BY branchID, code ORDER BY n DESC', PhabricatorRepository::TABLE_LINTMESSAGE, - implode(' AND ', $where)); + $where); } protected function buildActionView(DiffusionRequest $drequest) { @@ -526,10 +526,10 @@ final class DiffusionLintController extends DiffusionController { $conn, 'SELECT * FROM %T - WHERE %Q + WHERE %LA ORDER BY path, code, line LIMIT %d OFFSET %d', PhabricatorRepository::TABLE_LINTMESSAGE, - implode(' AND ', $where), + $where, $limit, $offset); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 89c677c081..268eedbcc7 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -370,8 +370,17 @@ final class DiffusionRepositoryController extends DiffusionController { $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('View Push Logs')) - ->setIcon('fa-list-alt') + ->setIcon('fa-upload') ->setHref($push_uri)); + + $pull_uri = $this->getApplicationURI( + 'synclog/?repositories='.$repository->getPHID()); + + $action_view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Sync Logs')) + ->setIcon('fa-exchange') + ->setHref($pull_uri)); } $pull_uri = $this->getApplicationURI( @@ -380,7 +389,7 @@ final class DiffusionRepositoryController extends DiffusionController { $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('View Pull Logs')) - ->setIcon('fa-list-alt') + ->setIcon('fa-download') ->setHref($pull_uri)); return $action_view; diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php index b383333fbc..7e2d06982d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php @@ -25,7 +25,8 @@ final class DiffusionRepositoryEditActivateController } $xaction = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ACTIVATE) + ->setTransactionType( + PhabricatorRepositoryActivateTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); $editor = id(new PhabricatorRepositoryEditor()) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php index 1088733cfc..9503a9e386 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php @@ -30,7 +30,8 @@ final class DiffusionRepositoryEditDangerousController if ($request->isFormPost()) { $xaction = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_DANGEROUS) + ->setTransactionType( + PhabricatorRepositoryDangerousTransaction::TRANSACTIONTYPE) ->setNewValue(!$repository->shouldAllowDangerousChanges()); $editor = id(new PhabricatorRepositoryEditor()) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php index d4eeb118d7..11a3ee3736 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php @@ -30,7 +30,8 @@ final class DiffusionRepositoryEditEnormousController if ($request->isFormPost()) { $xaction = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENORMOUS) + ->setTransactionType( + PhabricatorRepositoryEnormousTransaction::TRANSACTIONTYPE) ->setNewValue(!$repository->shouldAllowEnormousChanges()); $editor = id(new PhabricatorRepositoryEditor()) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php index 7871c5192a..dfb84cc3f0 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php @@ -68,13 +68,17 @@ final class DiffusionRepositoryManagePanelsController $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) ->setMainColumn($content); + $curtain = $panel->buildManagementPanelCurtain(); + if ($curtain) { + $view->setCurtain($curtain); + } + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($view); } @@ -89,20 +93,45 @@ final class DiffusionRepositoryManagePanelsController $nav = id(new AphrontSideNavFilterView()) ->setBaseURI($base_uri); - foreach ($panels as $panel) { - $key = $panel->getManagementPanelKey(); - $label = $panel->getManagementPanelLabel(); - $icon = $panel->getManagementPanelIcon(); - $href = $panel->getPanelNavigationURI(); + $groups = DiffusionRepositoryManagementPanelGroup::getAllPanelGroups(); + $panel_groups = mgroup($panels, 'getManagementPanelGroupKey'); + $other_key = DiffusionRepositoryManagementOtherPanelGroup::PANELGROUPKEY; - $item = id(new PHUIListItemView()) - ->setKey($key) - ->setName($label) - ->setType(PHUIListItemView::TYPE_LINK) - ->setHref($href) - ->setIcon($icon); + foreach ($groups as $group_key => $group) { + // If this is the "Other" group, include everything else that isn't in + // some actual group. + if ($group_key === $other_key) { + $group_panels = array_mergev($panel_groups); + $panel_groups = array(); + } else { + $group_panels = idx($panel_groups, $group_key); + unset($panel_groups[$group_key]); + } - $nav->addMenuItem($item); + if (!$group_panels) { + continue; + } + + $label = $group->getManagementPanelGroupLabel(); + if ($label) { + $nav->addLabel($label); + } + + foreach ($group_panels as $panel) { + $key = $panel->getManagementPanelKey(); + $label = $panel->getManagementPanelLabel(); + $icon = $panel->getManagementPanelIcon(); + $href = $panel->getPanelNavigationURI(); + + $item = id(new PHUIListItemView()) + ->setKey($key) + ->setName($label) + ->setType(PHUIListItemView::TYPE_LINK) + ->setHref($href) + ->setIcon($icon); + + $nav->addMenuItem($item); + } } $nav->selectFilter($selected); diff --git a/src/applications/diffusion/controller/DiffusionSyncLogListController.php b/src/applications/diffusion/controller/DiffusionSyncLogListController.php new file mode 100644 index 0000000000..a6281f9bf0 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionSyncLogListController.php @@ -0,0 +1,17 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + return parent::buildApplicationCrumbs() + ->addTextCrumb(pht('Sync Logs'), $this->getApplicationURI('synclog/')); + } + +} diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index dcb79a6c86..29dc588b45 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -243,8 +243,9 @@ final class DiffusionRepositoryEditEngine id(new PhabricatorSelectEditField()) ->setKey('vcs') ->setLabel(pht('Version Control System')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_VCS) - ->setIsConduitOnly(true) + ->setTransactionType( + PhabricatorRepositoryVCSTransaction::TRANSACTIONTYPE) + ->setIsFormField(false) ->setIsCopyable(true) ->setOptions(PhabricatorRepositoryType::getAllRepositoryTypes()) ->setDescription(pht('Underlying repository version control system.')) @@ -258,7 +259,8 @@ final class DiffusionRepositoryEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setIsRequired(true) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_NAME) + ->setTransactionType( + PhabricatorRepositoryNameTransaction::TRANSACTIONTYPE) ->setDescription(pht('The repository name.')) ->setConduitDescription(pht('Rename the repository.')) ->setConduitTypeDescription(pht('New repository name.')) @@ -266,7 +268,8 @@ final class DiffusionRepositoryEditEngine id(new PhabricatorTextEditField()) ->setKey('callsign') ->setLabel(pht('Callsign')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_CALLSIGN) + ->setTransactionType( + PhabricatorRepositoryCallsignTransaction::TRANSACTIONTYPE) ->setDescription(pht('The repository callsign.')) ->setConduitDescription(pht('Change the repository callsign.')) ->setConduitTypeDescription(pht('New repository callsign.')) @@ -274,7 +277,8 @@ final class DiffusionRepositoryEditEngine id(new PhabricatorTextEditField()) ->setKey('shortName') ->setLabel(pht('Short Name')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_SLUG) + ->setTransactionType( + PhabricatorRepositorySlugTransaction::TRANSACTIONTYPE) ->setDescription(pht('Short, unique repository name.')) ->setConduitDescription(pht('Change the repository short name.')) ->setConduitTypeDescription(pht('New short name for the repository.')) @@ -282,7 +286,8 @@ final class DiffusionRepositoryEditEngine id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhabricatorRepositoryDescriptionTransaction::TRANSACTIONTYPE) ->setDescription(pht('Repository description.')) ->setConduitDescription(pht('Change the repository description.')) ->setConduitTypeDescription(pht('New repository description.')) @@ -291,7 +296,8 @@ final class DiffusionRepositoryEditEngine ->setKey('encoding') ->setLabel(pht('Text Encoding')) ->setIsCopyable(true) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENCODING) + ->setTransactionType( + PhabricatorRepositoryEncodingTransaction::TRANSACTIONTYPE) ->setDescription(pht('Default text encoding.')) ->setConduitDescription(pht('Change the default text encoding.')) ->setConduitTypeDescription(pht('New text encoding.')) @@ -300,11 +306,12 @@ final class DiffusionRepositoryEditEngine ->setKey('allowDangerousChanges') ->setLabel(pht('Allow Dangerous Changes')) ->setIsCopyable(true) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setOptions( pht('Prevent Dangerous Changes'), pht('Allow Dangerous Changes')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_DANGEROUS) + ->setTransactionType( + PhabricatorRepositoryDangerousTransaction::TRANSACTIONTYPE) ->setDescription(pht('Permit dangerous changes to be made.')) ->setConduitDescription(pht('Allow or prevent dangerous changes.')) ->setConduitTypeDescription(pht('New protection setting.')) @@ -313,11 +320,12 @@ final class DiffusionRepositoryEditEngine ->setKey('allowEnormousChanges') ->setLabel(pht('Allow Enormous Changes')) ->setIsCopyable(true) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setOptions( pht('Prevent Enormous Changes'), pht('Allow Enormous Changes')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENORMOUS) + ->setTransactionType( + PhabricatorRepositoryEnormousTransaction::TRANSACTIONTYPE) ->setDescription(pht('Permit enormous changes to be made.')) ->setConduitDescription(pht('Allow or prevent enormous changes.')) ->setConduitTypeDescription(pht('New protection setting.')) @@ -325,8 +333,9 @@ final class DiffusionRepositoryEditEngine id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ACTIVATE) - ->setIsConduitOnly(true) + ->setTransactionType( + PhabricatorRepositoryActivateTransaction::TRANSACTIONTYPE) + ->setIsFormField(false) ->setOptions(PhabricatorRepository::getStatusNameMap()) ->setDescription(pht('Active or inactive status.')) ->setConduitDescription(pht('Active or deactivate the repository.')) @@ -336,7 +345,7 @@ final class DiffusionRepositoryEditEngine ->setKey('defaultBranch') ->setLabel(pht('Default Branch')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH) + PhabricatorRepositoryDefaultBranchTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDescription(pht('Default branch name.')) ->setConduitDescription(pht('Set the default branch name.')) @@ -347,7 +356,7 @@ final class DiffusionRepositoryEditEngine ->setKey('trackOnly') ->setLabel(pht('Track Only')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY) + PhabricatorRepositoryTrackOnlyTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDescription(pht('Track only these branches.')) ->setConduitDescription(pht('Set the tracked branches.')) @@ -358,7 +367,7 @@ final class DiffusionRepositoryEditEngine ->setKey('autocloseOnly') ->setLabel(pht('Autoclose Only')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY) + PhabricatorRepositoryAutocloseOnlyTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDescription(pht('Autoclose commits on only these branches.')) ->setConduitDescription(pht('Set the autoclose branches.')) @@ -368,7 +377,7 @@ final class DiffusionRepositoryEditEngine ->setKey('importOnly') ->setLabel(pht('Import Only')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH) + PhabricatorRepositorySVNSubpathTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDescription(pht('Subpath to selectively import.')) ->setConduitDescription(pht('Set the subpath to import.')) @@ -379,7 +388,7 @@ final class DiffusionRepositoryEditEngine ->setKey('stagingAreaURI') ->setLabel(pht('Staging Area URI')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_STAGING_URI) + PhabricatorRepositoryStagingURITransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDescription(pht('Staging area URI.')) ->setConduitDescription(pht('Set the staging area URI.')) @@ -390,7 +399,7 @@ final class DiffusionRepositoryEditEngine ->setKey('automationBlueprintPHIDs') ->setLabel(pht('Use Blueprints')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS) + PhabricatorRepositoryBlueprintsTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDatasource(new DrydockBlueprintDatasource()) ->setDescription(pht('Automation blueprints.')) @@ -402,7 +411,7 @@ final class DiffusionRepositoryEditEngine ->setKey('symbolLanguages') ->setLabel(pht('Languages')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE) + PhabricatorRepositorySymbolLanguagesTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDescription( pht('Languages which define symbols in this repository.')) @@ -415,7 +424,7 @@ final class DiffusionRepositoryEditEngine ->setKey('symbolRepositoryPHIDs') ->setLabel(pht('Uses Symbols From')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES) + PhabricatorRepositorySymbolSourcesTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setDatasource(new DiffusionRepositoryDatasource()) ->setDescription(pht('Repositories to link symbols from.')) @@ -426,7 +435,7 @@ final class DiffusionRepositoryEditEngine ->setKey('publish') ->setLabel(pht('Publish/Notify')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_NOTIFY) + PhabricatorRepositoryNotifyTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setOptions( pht('Disable Notifications, Feed, and Herald'), @@ -439,7 +448,7 @@ final class DiffusionRepositoryEditEngine ->setKey('autoclose') ->setLabel(pht('Autoclose')) ->setTransactionType( - PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE) + PhabricatorRepositoryAutocloseTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setOptions( pht('Disable Autoclose'), @@ -455,13 +464,42 @@ final class DiffusionRepositoryEditEngine ->setIsCopyable(true) ->setCapability(DiffusionPushCapability::CAPABILITY) ->setPolicies($policies) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY) + ->setTransactionType( + PhabricatorRepositoryPushPolicyTransaction::TRANSACTIONTYPE) ->setDescription( pht('Controls who can push changes to the repository.')) ->setConduitDescription( pht('Change the push policy of the repository.')) ->setConduitTypeDescription(pht('New policy PHID or constant.')) ->setValue($object->getPolicy(DiffusionPushCapability::CAPABILITY)), + id(new PhabricatorTextEditField()) + ->setKey('filesizeLimit') + ->setLabel(pht('Filesize Limit')) + ->setTransactionType( + PhabricatorRepositoryFilesizeLimitTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Maximum permitted file size.')) + ->setConduitDescription(pht('Change the filesize limit.')) + ->setConduitTypeDescription(pht('New repository filesize limit.')) + ->setValue($object->getFilesizeLimit()), + id(new PhabricatorTextEditField()) + ->setKey('copyTimeLimit') + ->setLabel(pht('Clone/Fetch Timeout')) + ->setTransactionType( + PhabricatorRepositoryCopyTimeLimitTransaction::TRANSACTIONTYPE) + ->setDescription( + pht('Maximum permitted duration of internal clone/fetch.')) + ->setConduitDescription(pht('Change the copy time limit.')) + ->setConduitTypeDescription(pht('New repository copy time limit.')) + ->setValue($object->getCopyTimeLimit()), + id(new PhabricatorTextEditField()) + ->setKey('touchLimit') + ->setLabel(pht('Touched Paths Limit')) + ->setTransactionType( + PhabricatorRepositoryTouchLimitTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Maximum permitted paths touched per commit.')) + ->setConduitDescription(pht('Change the touch limit.')) + ->setConduitTypeDescription(pht('New repository touch limit.')) + ->setValue($object->getTouchLimit()), ); } diff --git a/src/applications/diffusion/editor/DiffusionURIEditEngine.php b/src/applications/diffusion/editor/DiffusionURIEditEngine.php index fdc91cff5f..dbc1238153 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionURIEditEngine.php @@ -150,7 +150,7 @@ final class DiffusionURIEditEngine ->setAliases(array('repositoryPHID')) ->setLabel(pht('Repository')) ->setIsRequired(true) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( PhabricatorRepositoryURITransaction::TYPE_REPOSITORY) ->setDescription(pht('The repository this URI is associated with.')) @@ -195,7 +195,7 @@ final class DiffusionURIEditEngine ->setKey('credential') ->setAliases(array('credentialPHID')) ->setLabel(pht('Credential')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL) ->setDescription( @@ -206,7 +206,7 @@ final class DiffusionURIEditEngine id(new PhabricatorBoolEditField()) ->setKey('disable') ->setLabel(pht('Disabled')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_DISABLE) ->setDescription(pht('Active status of the URI.')) ->setConduitDescription(pht('Disable or activate the URI.')) diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 59fb4b5e12..6856b8676d 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -31,6 +31,7 @@ final class DiffusionCommitHookEngine extends Phobject { private $mercurialHook; private $mercurialCommits = array(); private $gitCommits = array(); + private $startTime; private $heraldViewerProjects; private $rejectCode = PhabricatorRepositoryPushLog::REJECT_BROKEN; @@ -38,6 +39,7 @@ final class DiffusionCommitHookEngine extends Phobject { private $emailPHIDs = array(); private $changesets = array(); private $changesetsSize = 0; + private $filesizeCache = array(); /* -( Config )------------------------------------------------------------- */ @@ -70,6 +72,15 @@ final class DiffusionCommitHookEngine extends Phobject { return $this->requestIdentifier; } + public function setStartTime($start_time) { + $this->startTime = $start_time; + return $this; + } + + public function getStartTime() { + return $this->startTime; + } + public function setSubversionTransactionInfo($transaction, $repository) { $this->subversionTransaction = $transaction; $this->subversionRepository = $repository; @@ -154,6 +165,25 @@ final class DiffusionCommitHookEngine extends Phobject { $this->applyHeraldRefRules($ref_updates); } + try { + if (!$is_initial_import) { + $this->rejectOversizedFiles($content_updates); + } + } catch (DiffusionCommitHookRejectException $ex) { + // If we're rejecting oversized files, flag everything. + $this->rejectCode = PhabricatorRepositoryPushLog::REJECT_OVERSIZED; + throw $ex; + } + + try { + if (!$is_initial_import) { + $this->rejectCommitsAffectingTooManyPaths($content_updates); + } + } catch (DiffusionCommitHookRejectException $ex) { + $this->rejectCode = PhabricatorRepositoryPushLog::REJECT_TOUCHES; + throw $ex; + } + try { if (!$is_initial_import) { $this->rejectEnormousChanges($content_updates); @@ -1102,11 +1132,14 @@ final class DiffusionCommitHookEngine extends Phobject { private function newPushEvent() { $viewer = $this->getViewer(); + $hook_start = $this->getStartTime(); + $event = PhabricatorRepositoryPushEvent::initializeNewEvent($viewer) ->setRepositoryPHID($this->getRepository()->getPHID()) ->setRemoteAddress($this->getRemoteAddress()) ->setRemoteProtocol($this->getRemoteProtocol()) - ->setEpoch(PhabricatorTime::getNow()); + ->setEpoch(PhabricatorTime::getNow()) + ->setHookWait(phutil_microseconds_since($hook_start)); $identifier = $this->getRequestIdentifier(); if (strlen($identifier)) { @@ -1242,6 +1275,92 @@ final class DiffusionCommitHookEngine extends Phobject { return $changesets; } + private function rejectOversizedFiles(array $content_updates) { + $repository = $this->getRepository(); + + $limit = $repository->getFilesizeLimit(); + if (!$limit) { + return; + } + + foreach ($content_updates as $update) { + $identifier = $update->getRefNew(); + + $sizes = $this->getFileSizesForCommit($identifier); + + foreach ($sizes as $path => $size) { + if ($size <= $limit) { + continue; + } + + $message = pht( + 'OVERSIZED FILE'. + "\n". + 'This repository ("%s") is configured with a maximum individual '. + 'file size limit, but you are pushing a change ("%s") which causes '. + 'the size of a file ("%s") to exceed the limit. The commit makes '. + 'the file %s bytes long, but the limit for this repository is '. + '%s bytes.', + $repository->getDisplayName(), + $identifier, + $path, + new PhutilNumber($size), + new PhutilNumber($limit)); + + throw new DiffusionCommitHookRejectException($message); + } + } + } + + private function rejectCommitsAffectingTooManyPaths(array $content_updates) { + $repository = $this->getRepository(); + + $limit = $repository->getTouchLimit(); + if (!$limit) { + return; + } + + foreach ($content_updates as $update) { + $identifier = $update->getRefNew(); + + $sizes = $this->getFileSizesForCommit($identifier); + if (count($sizes) > $limit) { + $message = pht( + 'COMMIT AFFECTS TOO MANY PATHS'. + "\n". + 'This repository ("%s") is configured with a touched files limit '. + 'that caps the maximum number of paths any single commit may '. + 'affect. You are pushing a change ("%s") which exceeds this '. + 'limit: it affects %s paths, but the largest number of paths any '. + 'commit may affect is %s paths.', + $repository->getDisplayName(), + $identifier, + phutil_count($sizes), + new PhutilNumber($limit)); + + throw new DiffusionCommitHookRejectException($message); + } + } + } + + public function getFileSizesForCommit($identifier) { + if (!isset($this->filesizeCache[$identifier])) { + $file_sizes = $this->loadFileSizesForCommit($identifier); + $this->filesizeCache[$identifier] = $file_sizes; + } + + return $this->filesizeCache[$identifier]; + } + + private function loadFileSizesForCommit($identifier) { + $repository = $this->getRepository(); + + return id(new DiffusionLowLevelFilesizeQuery()) + ->setRepository($repository) + ->withIdentifier($identifier) + ->execute(); + } + public function loadCommitRefForCommit($identifier) { $repository = $this->getRepository(); $vcs = $repository->getVersionControlSystem(); diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php index a5805fd0cc..eb1b98dba7 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php @@ -14,7 +14,20 @@ final class DiffusionRepositoryActionsManagementPanel } public function getManagementPanelIcon() { - return 'fa-flash'; + $repository = $this->getRepository(); + + $has_any = + $repository->getDetail('herald-disabled') || + $repository->getDetail('disable-autoclose'); + + // NOTE: Any value here really means something is disabled, so try to + // hint that a little bit with the icon. + + if ($has_any) { + return 'fa-flash'; + } else { + return 'fa-flash grey'; + } } protected function getEditEngineFieldKeys() { @@ -24,6 +37,30 @@ final class DiffusionRepositoryActionsManagementPanel ); } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Actions')) + ->setHref($actions_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -43,22 +80,7 @@ final class DiffusionRepositoryActionsManagementPanel $autoclose = phutil_tag('em', array(), $autoclose); $view->addProperty(pht('Autoclose'), $autoclose); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions_uri = $this->getEditPageURI(); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($actions_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - return $this->newBox(pht('Actions'), $view, array($button)); + return $this->newBox(pht('Actions'), $view); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php index 22afd5a53b..ac0c1251bb 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php @@ -13,6 +13,10 @@ final class DiffusionRepositoryAutomationManagementPanel return 800; } + public function getManagementPanelGroupKey() { + return DiffusionRepositoryManagementBuildsPanelGroup::PANELGROUPKEY; + } + public function shouldEnableForRepository( PhabricatorRepository $repository) { return $repository->isGit(); @@ -27,18 +31,60 @@ final class DiffusionRepositoryAutomationManagementPanel public function getManagementPanelIcon() { $repository = $this->getRepository(); + if (!$repository->canPerformAutomation()) { + return 'fa-truck grey'; + } + $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); + if (!$blueprint_phids) { + return 'fa-truck grey'; + } $is_authorized = DrydockAuthorizationQuery::isFullyAuthorized( $repository->getPHID(), $blueprint_phids); if (!$is_authorized) { - return 'fa-exclamation-triangle'; + return 'fa-exclamation-triangle yellow'; } return 'fa-truck'; } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $can_test = $can_edit && $repository->canPerformAutomation(); + + $automation_uri = $this->getEditPageURI(); + $test_uri = $repository->getPathURI('edit/testautomation/'); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Automation')) + ->setHref($automation_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-gamepad') + ->setName(pht('Test Configuration')) + ->setWorkflow(true) + ->setDisabled(!$can_test) + ->setHref($test_uri)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -58,33 +104,7 @@ final class DiffusionRepositoryAutomationManagementPanel $view->addProperty(pht('Automation'), $blueprint_view); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $can_test = $can_edit && $repository->canPerformAutomation(); - - $automation_uri = $this->getEditPageURI(); - $test_uri = $repository->getPathURI('edit/testautomation/'); - - $edit = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($automation_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $test = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-gamepad') - ->setText(pht('Test Config')) - ->setWorkflow(true) - ->setDisabled(!$can_test) - ->setHref($test_uri); - - return $this->newBox(pht('Automation'), $view, array($edit, $test)); + return $this->newBox(pht('Automation'), $view); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index af5e9b2881..bcf3ad6d74 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -27,9 +27,10 @@ final class DiffusionRepositoryBasicsManagementPanel ); } - private function buildActionMenu() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = id(new PhabricatorActionListView()) ->setViewer($viewer); @@ -44,27 +45,34 @@ final class DiffusionRepositoryBasicsManagementPanel $encoding_uri = $this->getEditPageURI('encoding'); $dangerous_uri = $repository->getPathURI('edit/dangerous/'); $enormous_uri = $repository->getPathURI('edit/enormous/'); + $update_uri = $repository->getPathURI('edit/update/'); if ($repository->isTracked()) { + $activate_icon = 'fa-ban'; $activate_label = pht('Deactivate Repository'); } else { + $activate_icon = 'fa-check'; $activate_label = pht('Activate Repository'); } $should_dangerous = $repository->shouldAllowDangerousChanges(); if ($should_dangerous) { + $dangerous_icon = 'fa-shield'; $dangerous_name = pht('Prevent Dangerous Changes'); $can_dangerous = $can_edit; } else { + $dangerous_icon = 'fa-exclamation-triangle'; $dangerous_name = pht('Allow Dangerous Changes'); $can_dangerous = ($can_edit && $repository->canAllowDangerousChanges()); } $should_enormous = $repository->shouldAllowEnormousChanges(); if ($should_enormous) { + $enormous_icon = 'fa-shield'; $enormous_name = pht('Prevent Enormous Changes'); $can_enormous = $can_edit; } else { + $enormous_icon = 'fa-exclamation-triangle'; $enormous_name = pht('Allow Enormous Changes'); $can_enormous = ($can_edit && $repository->canAllowEnormousChanges()); } @@ -73,12 +81,14 @@ final class DiffusionRepositoryBasicsManagementPanel id(new PhabricatorActionView()) ->setName(pht('Edit Basic Information')) ->setHref($edit_uri) + ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Text Encoding')) + ->setIcon('fa-text-width') ->setHref($encoding_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); @@ -87,6 +97,7 @@ final class DiffusionRepositoryBasicsManagementPanel id(new PhabricatorActionView()) ->setName($dangerous_name) ->setHref($dangerous_uri) + ->setIcon($dangerous_icon) ->setDisabled(!$can_dangerous) ->setWorkflow(true)); @@ -94,16 +105,26 @@ final class DiffusionRepositoryBasicsManagementPanel id(new PhabricatorActionView()) ->setName($enormous_name) ->setHref($enormous_uri) + ->setIcon($enormous_icon) ->setDisabled(!$can_enormous) ->setWorkflow(true)); $action_list->addAction( id(new PhabricatorActionView()) - ->setHref($activate_uri) ->setName($activate_label) + ->setHref($activate_uri) + ->setIcon($activate_icon) ->setDisabled(!$can_edit) ->setWorkflow(true)); + $action_list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Update Now')) + ->setHref($update_uri) + ->setIcon('fa-refresh') + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + $action_list->addAction( id(new PhabricatorActionView()) ->setType(PhabricatorActionView::TYPE_DIVIDER)); @@ -112,25 +133,18 @@ final class DiffusionRepositoryBasicsManagementPanel id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setHref($delete_uri) + ->setIcon('fa-times') ->setColor(PhabricatorActionView::RED) ->setDisabled(true) ->setWorkflow(true)); - return $action_list; + return $this->newCurtainView() + ->setActionList($action_list); } public function buildManagementPanelContent() { - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Actions')) - ->setHref('#') - ->setIcon('fa-bars') - ->addClass('phui-mobile-menu') - ->setDropdownMenu($this->buildActionMenu()); - $basics = $this->buildBasics(); - $basics = $this->newBox(pht('Properties'), $basics, array($button)); + $basics = $this->newBox(pht('Properties'), $basics); $repository = $this->getRepository(); $is_new = $repository->isNewlyInitialized(); @@ -254,7 +268,6 @@ final class DiffusionRepositoryBasicsManagementPanel private function buildStatus() { $repository = $this->getRepository(); $viewer = $this->getViewer(); - $update_uri = $repository->getPathURI('edit/update/'); $view = id(new PHUIPropertyListView()) ->setViewer($viewer); @@ -274,20 +287,7 @@ final class DiffusionRepositoryBasicsManagementPanel $view->addTextContent($raw_error); } - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-refresh') - ->setText(pht('Update Now')) - ->setWorkflow(true) - ->setDisabled(!$can_edit) - ->setHref($update_uri); - - return $this->newBox(pht('Status'), $view, array($button)); + return $this->newBox(pht('Status'), $view); } private function buildRepositoryUpdateInterval( diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index 7c7f4bcf63..e3ace3a5a5 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -19,7 +19,18 @@ final class DiffusionRepositoryBranchesManagementPanel } public function getManagementPanelIcon() { - return 'fa-code-fork'; + $repository = $this->getRepository(); + + $has_any = + $repository->getDetail('default-branch') || + $repository->getDetail('branch-filter') || + $repository->getDetail('close-commits-filter'); + + if ($has_any) { + return 'fa-code-fork'; + } else { + return 'fa-code-fork grey'; + } } protected function getEditEngineFieldKeys() { @@ -30,6 +41,30 @@ final class DiffusionRepositoryBranchesManagementPanel ); } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $branches_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Branches')) + ->setHref($branches_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -61,22 +96,7 @@ final class DiffusionRepositoryBranchesManagementPanel $view->addProperty(pht('Autoclose Only'), $autoclose_only); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $branches_uri = $this->getEditPageURI(); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($branches_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $content[] = $this->newBox(pht('Branches'), $view, array($button)); + $content[] = $this->newBox(pht('Branches'), $view); // Branch Autoclose Table if (!$repository->isImporting()) { diff --git a/src/applications/diffusion/management/DiffusionRepositoryLimitsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryLimitsManagementPanel.php new file mode 100644 index 0000000000..b57ed5cf56 --- /dev/null +++ b/src/applications/diffusion/management/DiffusionRepositoryLimitsManagementPanel.php @@ -0,0 +1,112 @@ +isGit(); + } + + public function getManagementPanelIcon() { + $repository = $this->getRepository(); + + $any_limit = false; + + if ($repository->getFilesizeLimit()) { + $any_limit = true; + } + + if ($any_limit) { + return 'fa-signal'; + } else { + return 'fa-signal grey'; + } + } + + protected function getEditEngineFieldKeys() { + return array( + 'filesizeLimit', + 'copyTimeLimit', + 'touchLimit', + ); + } + + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $limits_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Limits')) + ->setHref($limits_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + + public function buildManagementPanelContent() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + $byte_limit = $repository->getFilesizeLimit(); + if ($byte_limit) { + $filesize_display = pht('%s Bytes', new PhutilNumber($byte_limit)); + } else { + $filesize_display = pht('Unlimited'); + $filesize_display = phutil_tag('em', array(), $filesize_display); + } + + $view->addProperty(pht('Filesize Limit'), $filesize_display); + + $copy_limit = $repository->getCopyTimeLimit(); + if ($copy_limit) { + $copy_display = pht('%s Seconds', new PhutilNumber($copy_limit)); + } else { + $copy_default = $repository->getDefaultCopyTimeLimit(); + $copy_display = pht( + 'Default (%s Seconds)', + new PhutilNumber($copy_default)); + $copy_display = phutil_tag('em', array(), $copy_display); + } + + $view->addProperty(pht('Clone/Fetch Timeout'), $copy_display); + + $touch_limit = $repository->getTouchLimit(); + if ($touch_limit) { + $touch_display = pht('%s Paths', new PhutilNumber($touch_limit)); + } else { + $touch_display = pht('Unlimited'); + $touch_display = phutil_tag('em', array(), $touch_display); + } + + $view->addProperty(pht('Touched Paths Limit'), $touch_display); + + return $this->newBox(pht('Limits'), $view); + } + +} diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementBuildsPanelGroup.php b/src/applications/diffusion/management/DiffusionRepositoryManagementBuildsPanelGroup.php new file mode 100644 index 0000000000..e80901b494 --- /dev/null +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementBuildsPanelGroup.php @@ -0,0 +1,16 @@ +execute(); } - final protected function newBox($header_text, $body, $button = array()) { - $header = id(new PHUIHeaderView()) - ->setHeader($header_text); - - foreach ($button as $link) { - $header->addActionLink($link); - } - - $view = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) - ->appendChild($body); - - return $view; - } - final protected function newTimeline() { return $this->controller->newTimeline($this->getRepository()); } @@ -124,4 +109,36 @@ abstract class DiffusionRepositoryManagementPanel return $this->getPanelURI(); } + final protected function newActionList() { + $viewer = $this->getViewer(); + $action_id = celerity_generate_unique_node_id(); + + return id(new PhabricatorActionListView()) + ->setViewer($viewer) + ->setID($action_id); + } + + final protected function newCurtainView() { + $viewer = $this->getViewer(); + + return id(new PHUICurtainView()) + ->setViewer($viewer); + } + + final protected function newBox($header_text, $body) { + $viewer = $this->getViewer(); + + $header = id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($header_text); + + $view = id(new PHUIObjectBoxView()) + ->setViewer($viewer) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($body); + + return $view; + } + } diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanelGroup.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanelGroup.php new file mode 100644 index 0000000000..61e3f19c3b --- /dev/null +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanelGroup.php @@ -0,0 +1,21 @@ +getPhobjectClassConstant('PANELGROUPKEY'); + } + + abstract public function getManagementPanelGroupOrder(); + abstract public function getManagementPanelGroupLabel(); + + public static function getAllPanelGroups() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getManagementPanelGroupKey') + ->setSortMethod('getManagementPanelGroupOrder') + ->execute(); + } + +} diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php index 3a8e054ce0..0c7f5bb4e4 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php @@ -14,7 +14,35 @@ final class DiffusionRepositoryPoliciesManagementPanel } public function getManagementPanelIcon() { - return 'fa-lock'; + $viewer = $this->getViewer(); + $repository = $this->getRepository(); + + $can_view = PhabricatorPolicyCapability::CAN_VIEW; + $can_edit = PhabricatorPolicyCapability::CAN_EDIT; + $can_push = DiffusionPushCapability::CAPABILITY; + + $actual_values = array( + 'spacePHID' => $repository->getSpacePHID(), + 'view' => $repository->getPolicy($can_view), + 'edit' => $repository->getPolicy($can_edit), + 'push' => $repository->getPolicy($can_push), + ); + + $default = PhabricatorRepository::initializeNewRepository( + $viewer); + + $default_values = array( + 'spacePHID' => $default->getSpacePHID(), + 'view' => $default->getPolicy($can_view), + 'edit' => $default->getPolicy($can_edit), + 'push' => $default->getPolicy($can_push), + ); + + if ($actual_values === $default_values) { + return 'fa-lock grey'; + } else { + return 'fa-lock'; + } } protected function getEditEngineFieldKeys() { @@ -26,6 +54,31 @@ final class DiffusionRepositoryPoliciesManagementPanel ); } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Policies')) + ->setHref($edit_uri) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -56,22 +109,7 @@ final class DiffusionRepositoryPoliciesManagementPanel $pushable = $descriptions[DiffusionPushCapability::CAPABILITY]; $view->addProperty(pht('Pushable By'), $pushable); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $edit_uri = $this->getEditPageURI(); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - return $this->newBox(pht('Policies'), $view, array($button)); + return $this->newBox(pht('Policies'), $view); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php index ebd970f032..2d92e08dfa 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php @@ -13,14 +13,25 @@ final class DiffusionRepositoryStagingManagementPanel return 700; } + public function getManagementPanelGroupKey() { + return DiffusionRepositoryManagementBuildsPanelGroup::PANELGROUPKEY; + } + public function shouldEnableForRepository( PhabricatorRepository $repository) { return $repository->isGit(); } - public function getManagementPanelIcon() { - return 'fa-upload'; + $repository = $this->getRepository(); + + $staging_uri = $repository->getStagingURI(); + + if ($staging_uri) { + return 'fa-upload'; + } else { + return 'fa-upload grey'; + } } protected function getEditEngineFieldKeys() { @@ -29,6 +40,30 @@ final class DiffusionRepositoryStagingManagementPanel ); } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $staging_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Staging')) + ->setHref($staging_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -43,22 +78,7 @@ final class DiffusionRepositoryStagingManagementPanel $view->addProperty(pht('Staging Area URI'), $staging_uri); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $staging_uri = $this->getEditPageURI(); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($staging_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - return $this->newBox(pht('Staging Area'), $view, array($button)); + return $this->newBox(pht('Staging Area'), $view); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php index a39dfaa632..0f09b3dc22 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php @@ -14,7 +14,32 @@ final class DiffusionRepositoryStorageManagementPanel } public function getManagementPanelIcon() { - return 'fa-database'; + $repository = $this->getRepository(); + + if ($repository->getAlmanacServicePHID()) { + return 'fa-sitemap'; + } else if ($repository->isHosted()) { + return 'fa-database'; + } else { + return 'fa-download'; + } + } + + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setName(pht('Cluster Documentation'))); + + return $this->newCurtainView() + ->setActionList($action_list); } public function buildManagementPanelContent() { @@ -47,15 +72,7 @@ final class DiffusionRepositoryStorageManagementPanel $view->addProperty(pht('Storage Path'), $storage_path); $view->addProperty(pht('Storage Cluster'), $storage_service); - $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-book') - ->setHref($doc_href) - ->setText(pht('Help')); - - return $this->newBox(pht('Storage'), $view, array($button)); + return $this->newBox(pht('Storage'), $view); } private function buildClusterStatusPanel() { @@ -99,15 +116,20 @@ final class DiffusionRepositoryStorageManagementPanel $versions = mpull($versions, null, 'getDevicePHID'); - foreach ($bindings as $binding_group) { - $all_disabled = true; - foreach ($binding_group as $binding) { - if (!$binding->getIsDisabled()) { - $all_disabled = false; - break; - } - } + // List enabled devices first, then sort devices in each group by name. + $sort = array(); + foreach ($bindings as $key => $binding_group) { + $all_disabled = $this->isDisabledGroup($binding_group); + $sort[$key] = id(new PhutilSortVector()) + ->addInt($all_disabled ? 1 : 0) + ->addString(head($binding_group)->getDevice()->getName()); + } + $sort = msortv($sort, 'getSelf'); + $bindings = array_select_keys($bindings, array_keys($sort)) + $bindings; + + foreach ($bindings as $binding_group) { + $all_disabled = $this->isDisabledGroup($binding_group); $any_binding = head($binding_group); if ($all_disabled) { @@ -228,4 +250,16 @@ final class DiffusionRepositoryStorageManagementPanel return $this->newBox(pht('Cluster Status'), $table); } + private function isDisabledGroup(array $binding_group) { + assert_instances_of($binding_group, 'AlmanacBinding'); + + foreach ($binding_group as $binding) { + if (!$binding->getIsDisabled()) { + return false; + } + } + + return true; + } + } diff --git a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php index cd6e2c5be0..f87a5d9b85 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php @@ -19,7 +19,15 @@ final class DiffusionRepositorySubversionManagementPanel } public function getManagementPanelIcon() { - return 'fa-folder'; + $repository = $this->getRepository(); + + $has_any = (bool)$repository->getDetail('svn-subpath'); + + if ($has_any) { + return 'fa-folder'; + } else { + return 'fa-folder grey'; + } } protected function getEditEngineFieldKeys() { @@ -28,6 +36,30 @@ final class DiffusionRepositorySubversionManagementPanel ); } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $subversion_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Properties')) + ->setHref($subversion_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView($action_list) + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -40,22 +72,7 @@ final class DiffusionRepositorySubversionManagementPanel phutil_tag('em', array(), pht('Import Entire Repository'))); $view->addProperty(pht('Import Only'), $default_branch); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $subversion_uri = $this->getEditPageURI(); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($subversion_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - return $this->newBox(pht('Subversion'), $view, array($button)); + return $this->newBox(pht('Subversion'), $view); } } diff --git a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php index f64fbf8d6e..63a02dcf61 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php @@ -13,8 +13,22 @@ final class DiffusionRepositorySymbolsManagementPanel return 900; } + public function getManagementPanelGroupKey() { + return DiffusionRepositoryManagementIntegrationsPanelGroup::PANELGROUPKEY; + } + public function getManagementPanelIcon() { - return 'fa-bullseye'; + $repository = $this->getRepository(); + + $has_any = + $repository->getSymbolLanguages() || + $repository->getSymbolSources(); + + if ($has_any) { + return 'fa-link'; + } else { + return 'fa-link grey'; + } } protected function getEditEngineFieldKeys() { @@ -24,6 +38,30 @@ final class DiffusionRepositorySymbolsManagementPanel ); } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $symbols_uri = $this->getEditPageURI(); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Symbols')) + ->setHref($symbols_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $this->newCurtainView() + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -47,22 +85,7 @@ final class DiffusionRepositorySymbolsManagementPanel } $view->addProperty(pht('Uses Symbols From'), $sources); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $symbols_uri = $this->getEditPageURI(); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-pencil') - ->setText(pht('Edit')) - ->setHref($symbols_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - return $this->newBox(pht('Symbols'), $view, array($button)); + return $this->newBox(pht('Symbols'), $view); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php index 220bf4e2c1..a795c955a7 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php @@ -17,6 +17,36 @@ final class DiffusionRepositoryURIsManagementPanel return 400; } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->newActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs'); + $add_href = $repository->getPathURI('uri/edit/'); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-plus') + ->setHref($add_href) + ->setDisabled(!$can_edit) + ->setName(pht('Add New URI'))); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setName(pht('URI Documentation'))); + + return $this->newCurtainView() + ->setActionList($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -122,30 +152,9 @@ final class DiffusionRepositoryURIsManagementPanel ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors($messages); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); + $box = $this->newBox(pht('Repository URIs'), $table); - $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs'); - $add_href = $repository->getPathURI('uri/edit/'); - - $add = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-plus') - ->setHref($add_href) - ->setDisabled(!$can_edit) - ->setText(pht('New URI')); - - $help = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-book') - ->setHref($doc_href) - ->setText(pht('Help')); - - $box = $this->newBox(pht('Repository URIs'), $table, array($add, $help)); - - return array($box, $info_view); + return array($info_view, $box); } } diff --git a/src/applications/diffusion/protocol/DiffusionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionCommandEngine.php index d7da62b11d..a0fe568168 100644 --- a/src/applications/diffusion/protocol/DiffusionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionCommandEngine.php @@ -138,7 +138,8 @@ abstract class DiffusionCommandEngine extends Phobject { // See T13108. By default, don't let any cluster command run indefinitely // to try to avoid cases where `git fetch` hangs for some reason and we're // left sitting with a held lock forever. - $future->setTimeout(phutil_units('15 minutes in seconds')); + $repository = $this->getRepository(); + $future->setTimeout($repository->getEffectiveCopyTimeLimit()); return $future; } diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index 2e5d4215db..c72021f0c1 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -206,7 +206,10 @@ final class DiffusionRepositoryClusterEngine extends Phobject { } } - $this->synchronizeWorkingCopyFromDevices($fetchable); + $this->synchronizeWorkingCopyFromDevices( + $fetchable, + $this_version, + $max_version); } else { $this->synchronizeWorkingCopyFromRemote(); } @@ -407,8 +410,8 @@ final class DiffusionRepositoryClusterEngine extends Phobject { $log = $this->logger; if ($log) { - $log->writeClusterEngineLogProperty('readWait', $read_wait); $log->writeClusterEngineLogProperty('writeWait', $write_wait); + $log->writeClusterEngineLogProperty('readWait', $read_wait); } } @@ -653,7 +656,11 @@ final class DiffusionRepositoryClusterEngine extends Phobject { /** * @task internal */ - private function synchronizeWorkingCopyFromDevices(array $device_phids) { + private function synchronizeWorkingCopyFromDevices( + array $device_phids, + $local_version, + $remote_version) { + $repository = $this->getRepository(); $service = $repository->loadAlmanacService(); @@ -694,7 +701,10 @@ final class DiffusionRepositoryClusterEngine extends Phobject { $caught = null; foreach ($fetchable as $binding) { try { - $this->synchronizeWorkingCopyFromBinding($binding); + $this->synchronizeWorkingCopyFromBinding( + $binding, + $local_version, + $remote_version); $caught = null; break; } catch (Exception $ex) { @@ -711,14 +721,17 @@ final class DiffusionRepositoryClusterEngine extends Phobject { /** * @task internal */ - private function synchronizeWorkingCopyFromBinding($binding) { + private function synchronizeWorkingCopyFromBinding( + AlmanacBinding $binding, + $local_version, + $remote_version) { + $repository = $this->getRepository(); $device = AlmanacKeys::getLiveDevice(); $this->logLine( pht( - 'Synchronizing this device ("%s") from cluster leader ("%s") before '. - 'read.', + 'Synchronizing this device ("%s") from cluster leader ("%s").', $device->getName(), $binding->getDevice()->getName())); @@ -746,17 +759,57 @@ final class DiffusionRepositoryClusterEngine extends Phobject { $future->setCWD($local_path); + $log = PhabricatorRepositorySyncEvent::initializeNewEvent() + ->setRepositoryPHID($repository->getPHID()) + ->setEpoch(PhabricatorTime::getNow()) + ->setDevicePHID($device->getPHID()) + ->setFromDevicePHID($binding->getDevice()->getPHID()) + ->setDeviceVersion($local_version) + ->setFromDeviceVersion($remote_version); + + $sync_start = microtime(true); + try { $future->resolvex(); } catch (Exception $ex) { + $log->setSyncWait(phutil_microseconds_since($sync_start)); + + if ($ex instanceof CommandException) { + if ($future->getWasKilledByTimeout()) { + $result_type = PhabricatorRepositorySyncEvent::RESULT_TIMEOUT; + } else { + $result_type = PhabricatorRepositorySyncEvent::RESULT_ERROR; + } + + $log + ->setResultCode($ex->getError()) + ->setResultType($result_type) + ->setProperty('stdout', $ex->getStdout()) + ->setProperty('stderr', $ex->getStderr()); + } else { + $log + ->setResultCode(1) + ->setResultType(PhabricatorRepositorySyncEvent::RESULT_EXCEPTION) + ->setProperty('message', $ex->getMessage()); + } + + $log->save(); + $this->logLine( pht( 'Synchronization of "%s" from leader "%s" failed: %s', $device->getName(), $binding->getDevice()->getName(), $ex->getMessage())); + throw $ex; } + + $log + ->setSyncWait(phutil_microseconds_since($sync_start)) + ->setResultCode(0) + ->setResultType(PhabricatorRepositorySyncEvent::RESULT_SYNC) + ->save(); } diff --git a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php index 0217c97d58..1666056d63 100644 --- a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php +++ b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php @@ -81,10 +81,10 @@ final class DiffusionCachedResolveRefsQuery $commits = queryfx_all( $conn_r, 'SELECT commitIdentifier FROM %T - WHERE repositoryID = %s AND %Q', + WHERE repositoryID = %s AND %LO', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID(), - implode(' OR ', $prefixes)); + $prefixes); foreach ($commits as $commit) { $hash = $commit['commitIdentifier']; diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index f2d0920b3a..cf8c25baf1 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -696,7 +696,7 @@ final class DiffusionCommitQuery pht('No commit identifiers.')); } - $where[] = '('.implode(' OR ', $sql).')'; + $where[] = qsprintf($conn, '%LO', $sql); } if ($this->auditIDs !== null) { diff --git a/src/applications/diffusion/query/DiffusionLintCountQuery.php b/src/applications/diffusion/query/DiffusionLintCountQuery.php index 4441ccf3ef..2c1a2a9867 100644 --- a/src/applications/diffusion/query/DiffusionLintCountQuery.php +++ b/src/applications/diffusion/query/DiffusionLintCountQuery.php @@ -70,7 +70,7 @@ final class DiffusionLintCountQuery extends PhabricatorQuery { } protected function buildCustomWhereClause( - AphrontDatabaseConnection $conn_r, + AphrontDatabaseConnection $conn, $part) { $where = array(); @@ -79,19 +79,19 @@ final class DiffusionLintCountQuery extends PhabricatorQuery { if ($this->codes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'code IN (%Ls)', $this->codes); } if ($this->branchIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'branchID IN (%Ld)', $this->branchIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function processPaths() { diff --git a/src/applications/diffusion/query/DiffusionPathQuery.php b/src/applications/diffusion/query/DiffusionPathQuery.php index 45dc978ec1..0783643dc0 100644 --- a/src/applications/diffusion/query/DiffusionPathQuery.php +++ b/src/applications/diffusion/query/DiffusionPathQuery.php @@ -10,12 +10,12 @@ final class DiffusionPathQuery extends Phobject { } public function execute() { - $conn_r = id(new PhabricatorRepository())->establishConnection('r'); + $conn = id(new PhabricatorRepository())->establishConnection('r'); - $where = $this->buildWhereClause($conn_r); + $where = $this->buildWhereClause($conn); $results = queryfx_all( - $conn_r, + $conn, 'SELECT * FROM %T %Q', PhabricatorRepository::TABLE_PATH, $where); @@ -23,20 +23,20 @@ final class DiffusionPathQuery extends Phobject { return ipull($results, null, 'id'); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->pathIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->pathIDs); } if ($where) { - return 'WHERE ('.implode(') AND (', $where).')'; + return qsprintf($conn, 'WHERE %LA', $where); } else { - return ''; + return qsprintf($conn, ''); } } diff --git a/src/applications/diffusion/query/DiffusionSymbolQuery.php b/src/applications/diffusion/query/DiffusionSymbolQuery.php index 0e08b7b32d..3415fbbefa 100644 --- a/src/applications/diffusion/query/DiffusionSymbolQuery.php +++ b/src/applications/diffusion/query/DiffusionSymbolQuery.php @@ -192,52 +192,52 @@ final class DiffusionSymbolQuery extends PhabricatorOffsetPagedQuery { /** * @task internal */ - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if (isset($this->context)) { $where[] = qsprintf( - $conn_r, + $conn, 'symbolContext = %s', $this->context); } if ($this->name) { $where[] = qsprintf( - $conn_r, + $conn, 'symbolName = %s', $this->name); } if ($this->namePrefix) { $where[] = qsprintf( - $conn_r, + $conn, 'symbolName LIKE %>', $this->namePrefix); } if ($this->repositoryPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } if ($this->language) { $where[] = qsprintf( - $conn_r, + $conn, 'symbolLanguage = %s', $this->language); } if ($this->type) { $where[] = qsprintf( - $conn_r, + $conn, 'symbolType = %s', $this->type); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } diff --git a/src/applications/diffusion/query/DiffusionSyncLogSearchEngine.php b/src/applications/diffusion/query/DiffusionSyncLogSearchEngine.php new file mode 100644 index 0000000000..6b321189f4 --- /dev/null +++ b/src/applications/diffusion/query/DiffusionSyncLogSearchEngine.php @@ -0,0 +1,154 @@ +newQuery(); + + if ($map['repositoryPHIDs']) { + $query->withRepositoryPHIDs($map['repositoryPHIDs']); + } + + if ($map['createdStart'] || $map['createdEnd']) { + $query->withEpochBetween( + $map['createdStart'], + $map['createdEnd']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchDatasourceField()) + ->setDatasource(new DiffusionRepositoryDatasource()) + ->setKey('repositoryPHIDs') + ->setAliases(array('repository', 'repositories', 'repositoryPHID')) + ->setLabel(pht('Repositories')) + ->setDescription( + pht('Search for sync logs for specific repositories.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + ); + } + + protected function newExportFields() { + $viewer = $this->requireViewer(); + + $fields = array( + id(new PhabricatorPHIDExportField()) + ->setKey('repositoryPHID') + ->setLabel(pht('Repository PHID')), + id(new PhabricatorStringExportField()) + ->setKey('repository') + ->setLabel(pht('Repository')), + id(new PhabricatorPHIDExportField()) + ->setKey('devicePHID') + ->setLabel(pht('Device PHID')), + id(new PhabricatorPHIDExportField()) + ->setKey('fromDevicePHID') + ->setLabel(pht('From Device PHID')), + id(new PhabricatorIntExportField()) + ->setKey('deviceVersion') + ->setLabel(pht('Device Version')), + id(new PhabricatorIntExportField()) + ->setKey('fromDeviceVersion') + ->setLabel(pht('From Device Version')), + id(new PhabricatorStringExportField()) + ->setKey('result') + ->setLabel(pht('Result')), + id(new PhabricatorIntExportField()) + ->setKey('code') + ->setLabel(pht('Code')), + id(new PhabricatorEpochExportField()) + ->setKey('date') + ->setLabel(pht('Date')), + id(new PhabricatorIntExportField()) + ->setKey('syncWait') + ->setLabel(pht('Sync Wait')), + ); + + return $fields; + } + + protected function newExportData(array $events) { + $viewer = $this->requireViewer(); + + $export = array(); + foreach ($events as $event) { + $repository = $event->getRepository(); + $repository_phid = $repository->getPHID(); + $repository_name = $repository->getDisplayName(); + + $map = array( + 'repositoryPHID' => $repository_phid, + 'repository' => $repository_name, + 'devicePHID' => $event->getDevicePHID(), + 'fromDevicePHID' => $event->getFromDevicePHID(), + 'deviceVersion' => $event->getDeviceVersion(), + 'fromDeviceVersion' => $event->getFromDeviceVersion(), + 'result' => $event->getResultType(), + 'code' => $event->getResultCode(), + 'date' => $event->getEpoch(), + 'syncWait' => $event->getSyncWait(), + ); + + $export[] = $map; + } + + return $export; + } + + protected function getURI($path) { + return '/diffusion/synclog/'.$path; + } + + protected function getBuiltinQueryNames() { + return array( + 'all' => pht('All Sync Logs'), + ); + } + + 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 $logs, + PhabricatorSavedQuery $query, + array $handles) { + + $table = id(new DiffusionSyncLogListView()) + ->setViewer($this->requireViewer()) + ->setLogs($logs); + + return id(new PhabricatorApplicationSearchResultView()) + ->setTable($table); + } + +} diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelFilesizeQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelFilesizeQuery.php new file mode 100644 index 0000000000..9d97a134ca --- /dev/null +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelFilesizeQuery.php @@ -0,0 +1,125 @@ +identifier = $identifier; + return $this; + } + + protected function executeQuery() { + if (!strlen($this->identifier)) { + throw new PhutilInvalidStateException('withIdentifier'); + } + + $type = $this->getRepository()->getVersionControlSystem(); + switch ($type) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $result = $this->loadGitFilesizes(); + break; + default: + throw new Exception(pht('Unsupported repository type "%s"!', $type)); + } + + return $result; + } + + private function loadGitFilesizes() { + $repository = $this->getRepository(); + $identifier = $this->identifier; + + $paths_future = $repository->getLocalCommandFuture( + 'diff-tree -z -r --no-commit-id %s --', + $identifier); + + // With "-z" we get "\0\0" for each line. Process the + // delimited text as ", " pairs. + + $path_lines = id(new LinesOfALargeExecFuture($paths_future)) + ->setDelimiter("\0"); + + $paths = array(); + + $path_pairs = new PhutilChunkedIterator($path_lines, 2); + foreach ($path_pairs as $path_pair) { + if (count($path_pair) != 2) { + throw new Exception( + pht( + 'Unexpected number of output lines from "git diff-tree" when '. + 'processing commit ("%s"): expected an even number of lines.', + $identifier)); + } + + list($fields, $pathname) = array_values($path_pair); + $fields = explode(' ', $fields); + + // Fields are: + // + // :100644 100644 aaaa bbbb M + // + // [0] Old file mode. + // [1] New file mode. + // [2] Old object hash. + // [3] New object hash. + // [4] Change mode. + + $paths[] = array( + 'path' => $pathname, + 'newHash' => $fields[3], + ); + } + + $path_sizes = array(); + + if (!$paths) { + return $path_sizes; + } + + $check_paths = array(); + foreach ($paths as $path) { + if ($path['newHash'] === DiffusionCommitHookEngine::EMPTY_HASH) { + $path_sizes[$path['path']] = 0; + continue; + } + $check_paths[$path['newHash']][] = $path['path']; + } + + if (!$check_paths) { + return $path_sizes; + } + + $future = $repository->getLocalCommandFuture( + 'cat-file --batch-check=%s', + '%(objectsize)'); + + $future->write(implode("\n", array_keys($check_paths))); + + $size_lines = id(new LinesOfALargeExecFuture($future)) + ->setDelimiter("\n"); + foreach ($size_lines as $line) { + $object_size = (int)$line; + + $object_hash = head_key($check_paths); + $path_names = $check_paths[$object_hash]; + unset($check_paths[$object_hash]); + + foreach ($path_names as $path_name) { + $path_sizes[$path_name] = $object_size; + } + } + + if ($check_paths) { + throw new Exception( + pht( + 'Unexpected number of output lines from "git cat-file" when '. + 'processing commit ("%s").', + $identifier)); + } + + return $path_sizes; + } + +} diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 1a46d43adf..52a09d12ae 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -489,7 +489,7 @@ abstract class DiffusionRequest extends Phobject { // Consume the back part of the URI, up to the first "$". Use a negative // lookbehind to prevent matching '$$'. We double the '$' symbol when // encoding so that files with names like "money/$100" will survive. - $pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d-,]+)$@'; + $pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d,-]+)$@'; if (preg_match($pattern, $blob, $matches)) { $result['line'] = $matches[1]; $blob = substr($blob, 0, -(strlen($matches[1]) + 1)); diff --git a/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php index d48abc159e..54220f0776 100644 --- a/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php @@ -30,7 +30,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { if ($this->shouldProxy()) { $command = $this->getProxyCommand(true); - $did_synchronize = false; + $did_write = false; if ($device) { $this->writeClusterEngineLogMessage( @@ -40,7 +40,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { } } else { $command = csprintf('git-receive-pack %s', $repository->getLocalPath()); - $did_synchronize = true; + $did_write = true; $cluster_engine->synchronizeWorkingCopyBeforeWrite(); if ($device) { @@ -60,7 +60,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { // We've committed the write (or rejected it), so we can release the lock // without waiting for the client to receive the acknowledgement. - if ($did_synchronize) { + if ($did_write) { $cluster_engine->synchronizeWorkingCopyAfterWrite(); } @@ -69,18 +69,23 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { } if (!$err) { - $repository->writeStatusMessage( - PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, - PhabricatorRepositoryStatusMessage::CODE_OKAY); $this->waitForGitClient(); - $host_wait_end = microtime(true); + // When a repository is clustered, we reach this cleanup code on both + // the proxy and the actual final endpoint node. Don't do more cleanup + // or logging than we need to. + if ($did_write) { + $repository->writeStatusMessage( + PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, + PhabricatorRepositoryStatusMessage::CODE_OKAY); - $this->updatePushLogWithTimingInformation( - $this->getClusterEngineLogProperty('writeWait'), - $this->getClusterEngineLogProperty('readWait'), - ($host_wait_end - $host_wait_start)); + $host_wait_end = microtime(true); + $this->updatePushLogWithTimingInformation( + $this->getClusterEngineLogProperty('writeWait'), + $this->getClusterEngineLogProperty('readWait'), + ($host_wait_end - $host_wait_start)); + } } return $err; diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index 12a5fa5494..d84667ffb5 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -41,9 +41,10 @@ final class DiffusionPushLogListView extends AphrontView { $any_host = false; foreach ($logs as $log) { $repository = $log->getRepository(); + $event = $log->getPushEvent(); if ($remotes_visible) { - $remote_address = $log->getPushEvent()->getRemoteAddress(); + $remote_address = $event->getRemoteAddress(); } else { $remote_address = null; } @@ -79,10 +80,10 @@ final class DiffusionPushLogListView extends AphrontView { phutil_tag('br'), $flag_names); - $reject_code = $log->getPushEvent()->getRejectCode(); + $reject_code = $event->getRejectCode(); if ($reject_code == $reject_herald) { - $rule_phid = $log->getPushEvent()->getRejectDetails(); + $rule_phid = $event->getRejectDetails(); $handle = $viewer->renderHandle($rule_phid); $reject_label = pht('Blocked: %s', $handle); } else { @@ -92,6 +93,11 @@ final class DiffusionPushLogListView extends AphrontView { pht('Unknown ("%s")', $reject_code)); } + $host_wait = $this->formatMicroseconds($event->getHostWait()); + $write_wait = $this->formatMicroseconds($event->getWriteWait()); + $read_wait = $this->formatMicroseconds($event->getReadWait()); + $hook_wait = $this->formatMicroseconds($event->getHookWait()); + $rows[] = array( phutil_tag( 'a', @@ -107,7 +113,7 @@ final class DiffusionPushLogListView extends AphrontView { $repository->getDisplayName()), $viewer->renderHandle($log->getPusherPHID()), $remote_address, - $log->getPushEvent()->getRemoteProtocol(), + $event->getRemoteProtocol(), $device, $log->getRefType(), $log->getRefName(), @@ -121,6 +127,10 @@ final class DiffusionPushLogListView extends AphrontView { $flag_names, $reject_label, $viewer->formatShortDateTime($log->getEpoch()), + $host_wait, + $write_wait, + $read_wait, + $hook_wait, ); } @@ -140,6 +150,10 @@ final class DiffusionPushLogListView extends AphrontView { pht('Flags'), pht('Result'), pht('Date'), + pht('Host Wait'), + pht('Write Wait'), + pht('Read Wait'), + pht('Hook Wait'), )) ->setColumnClasses( array( @@ -156,6 +170,10 @@ final class DiffusionPushLogListView extends AphrontView { '', '', 'right', + 'n right', + 'n right', + 'n right', + 'n right', )) ->setColumnVisibility( array( @@ -170,4 +188,12 @@ final class DiffusionPushLogListView extends AphrontView { return $table; } + private function formatMicroseconds($duration) { + if ($duration === null) { + return null; + } + + return pht('%sus', new PhutilNumber($duration)); + } + } diff --git a/src/applications/diffusion/view/DiffusionSyncLogListView.php b/src/applications/diffusion/view/DiffusionSyncLogListView.php new file mode 100644 index 0000000000..b4e195122e --- /dev/null +++ b/src/applications/diffusion/view/DiffusionSyncLogListView.php @@ -0,0 +1,79 @@ +logs = $logs; + return $this; + } + + public function render() { + $events = $this->logs; + $viewer = $this->getViewer(); + + $rows = array(); + foreach ($events as $event) { + $repository = $event->getRepository(); + $repository_link = phutil_tag( + 'a', + array( + 'href' => $repository->getURI(), + ), + $repository->getDisplayName()); + + $event_id = $event->getID(); + + $sync_wait = pht('%sus', new PhutilNumber($event->getSyncWait())); + + $device_link = $viewer->renderHandle($event->getDevicePHID()); + $from_device_link = $viewer->renderHandle($event->getFromDevicePHID()); + + $rows[] = array( + $event_id, + $repository_link, + $device_link, + $from_device_link, + $event->getDeviceVersion(), + $event->getFromDeviceVersion(), + $event->getResultType(), + $event->getResultCode(), + phabricator_datetime($event->getEpoch(), $viewer), + $sync_wait, + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Sync'), + pht('Repository'), + pht('Device'), + pht('From Device'), + pht('Version'), + pht('From Version'), + pht('Result'), + pht('Code'), + pht('Date'), + pht('Sync Wait'), + )) + ->setColumnClasses( + array( + 'n', + '', + '', + '', + 'n', + 'n', + 'wide right', + 'n', + 'right', + 'n right', + )); + + return $table; + } + +} diff --git a/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php index 2e0dab2c35..5ade7f3513 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php @@ -7,7 +7,7 @@ final class DiffusionCommitAcceptTransaction const ACTIONKEY = 'accept'; protected function getCommitActionLabel() { - return pht("Accept Commit \xE2\x9C\x94"); + return pht('Accept Commit'); } protected function getCommitActionDescription() { @@ -70,4 +70,12 @@ final class DiffusionCommitAcceptTransaction $this->renderObject()); } + public function getTransactionTypeForConduit($xaction) { + return 'accept'; + } + + public function getFieldValuesForConduit($object, $data) { + return array(); + } + } diff --git a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php index 1f6ae72621..ffd084412e 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php @@ -7,7 +7,7 @@ final class DiffusionCommitConcernTransaction const ACTIONKEY = 'concern'; protected function getCommitActionLabel() { - return pht("Raise Concern \xE2\x9C\x98"); + return pht('Raise Concern'); } protected function getCommitActionDescription() { @@ -76,4 +76,12 @@ final class DiffusionCommitConcernTransaction $this->renderObject()); } + public function getTransactionTypeForConduit($xaction) { + return 'concern'; + } + + public function getFieldValuesForConduit($object, $data) { + return array(); + } + } diff --git a/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php index 103d0fabfe..8adc8346dc 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php @@ -63,4 +63,12 @@ final class DiffusionCommitResignTransaction $this->renderObject()); } + public function getTransactionTypeForConduit($xaction) { + return 'resign'; + } + + public function getFieldValuesForConduit($object, $data) { + return array(); + } + } diff --git a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php index f1ec5834b1..f9f19f4290 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php @@ -68,4 +68,12 @@ final class DiffusionCommitVerifyTransaction $this->renderObject()); } + public function getTransactionTypeForConduit($xaction) { + return 'request-verification'; + } + + public function getFieldValuesForConduit($object, $data) { + return array(); + } + } diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php index 80f3bd7a1e..e9e1848d6e 100644 --- a/src/applications/diviner/publisher/DivinerLivePublisher.php +++ b/src/applications/diviner/publisher/DivinerLivePublisher.php @@ -101,11 +101,11 @@ final class DivinerLivePublisher extends DivinerPublisher { $strings[] = qsprintf($conn_w, '%s', $hash); } - foreach (PhabricatorLiskDAO::chunkSQL($strings, ', ') as $chunk) { + foreach (PhabricatorLiskDAO::chunkSQL($strings) as $chunk) { queryfx( $conn_w, 'UPDATE %T SET graphHash = NULL, nodeHash = NULL - WHERE graphHash IN (%Q)', + WHERE graphHash IN (%LQ)', $symbol_table->getTableName(), $chunk); } diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php index 5f4900d4dd..65a5634008 100644 --- a/src/applications/diviner/query/DivinerAtomQuery.php +++ b/src/applications/diviner/query/DivinerAtomQuery.php @@ -299,40 +299,40 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $atoms; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->bookPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'bookPHID IN (%Ls)', $this->bookPHIDs); } if ($this->types) { $where[] = qsprintf( - $conn_r, + $conn, 'type IN (%Ls)', $this->types); } if ($this->names) { $where[] = qsprintf( - $conn_r, + $conn, 'name IN (%Ls)', $this->names); } @@ -347,7 +347,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { } $where[] = qsprintf( - $conn_r, + $conn, 'titleSlugHash in (%Ls)', $hashes); } @@ -366,46 +366,46 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { if ($contexts && $with_null) { $where[] = qsprintf( - $conn_r, + $conn, 'context IN (%Ls) OR context IS NULL', $contexts); } else if ($contexts) { $where[] = qsprintf( - $conn_r, + $conn, 'context IN (%Ls)', $contexts); } else if ($with_null) { $where[] = qsprintf( - $conn_r, + $conn, 'context IS NULL'); } } if ($this->indexes) { $where[] = qsprintf( - $conn_r, + $conn, 'atomIndex IN (%Ld)', $this->indexes); } if ($this->isDocumentable !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'isDocumentable = %d', (int)$this->isDocumentable); } if ($this->isGhost !== null) { if ($this->isGhost) { - $where[] = qsprintf($conn_r, 'graphHash IS NULL'); + $where[] = qsprintf($conn, 'graphHash IS NULL'); } else { - $where[] = qsprintf($conn_r, 'graphHash IS NOT NULL'); + $where[] = qsprintf($conn, 'graphHash IS NOT NULL'); } } if ($this->nodeHashes) { $where[] = qsprintf( - $conn_r, + $conn, 'nodeHash IN (%Ls)', $this->nodeHashes); } @@ -415,21 +415,21 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { // the column has binary collation. Eventually, this should move into // fulltext. $where[] = qsprintf( - $conn_r, + $conn, 'CONVERT(name USING utf8) LIKE %~', $this->nameContains); } if ($this->repositoryPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } /** diff --git a/src/applications/diviner/query/DivinerBookQuery.php b/src/applications/diviner/query/DivinerBookQuery.php index 8e6726ef5c..d540d971b0 100644 --- a/src/applications/diviner/query/DivinerBookQuery.php +++ b/src/applications/diviner/query/DivinerBookQuery.php @@ -116,54 +116,54 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $books; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if (strlen($this->nameLike)) { $where[] = qsprintf( - $conn_r, + $conn, 'name LIKE %~', $this->nameLike); } if ($this->names !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'name IN (%Ls)', $this->names); } if (strlen($this->namePrefix)) { $where[] = qsprintf( - $conn_r, + $conn, 'name LIKE %>', $this->namePrefix); } if ($this->repositoryPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index ef904e1019..12ccf8a343 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -45,6 +45,16 @@ final class DrydockAlmanacServiceHostBlueprintImplementation return true; } + public function shouldAllocateSupplementalResource( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // We want to use every host in an Almanac service, since the amount of + // hardware is fixed and there's normally no value in packing leases onto a + // subset of it. Always build a new supplemental resource if we can. + return true; + } + public function canAllocateResourceForLease( DrydockBlueprint $blueprint, DrydockLease $lease) { @@ -113,6 +123,14 @@ final class DrydockAlmanacServiceHostBlueprintImplementation DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { + + // Require the binding to a given host be active before we'll hand out more + // leases on the corresponding resource. + $binding = $this->loadBindingForResource($resource); + if ($binding->getIsDisabled()) { + return false; + } + return true; } @@ -154,24 +172,10 @@ final class DrydockAlmanacServiceHostBlueprintImplementation DrydockLease $lease, $type) { - $viewer = PhabricatorUser::getOmnipotentUser(); - switch ($type) { case DrydockCommandInterface::INTERFACE_TYPE: $credential_phid = $blueprint->getFieldValue('credentialPHID'); - $binding_phid = $resource->getAttribute('almanacBindingPHID'); - - $binding = id(new AlmanacBindingQuery()) - ->setViewer($viewer) - ->withPHIDs(array($binding_phid)) - ->executeOne(); - if (!$binding) { - throw new Exception( - pht( - 'Unable to load binding "%s" to create command interface.', - $binding_phid)); - } - + $binding = $this->loadBindingForResource($resource); $interface = $binding->getInterface(); return id(new DrydockSSHCommandInterface()) @@ -213,7 +217,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation $blueprint->getBlueprintName())); } - $viewer = PhabricatorUser::getOmnipotentUser(); + $viewer = $this->getViewer(); $services = id(new AlmanacServiceQuery()) ->setViewer($viewer) ->withPHIDs($service_phids) @@ -246,7 +250,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation private function loadFreeBindings(DrydockBlueprint $blueprint) { if ($this->freeBindings === null) { - $viewer = PhabricatorUser::getOmnipotentUser(); + $viewer = $this->getViewer(); $pool = id(new DrydockResourceQuery()) ->setViewer($viewer) @@ -293,5 +297,31 @@ final class DrydockAlmanacServiceHostBlueprintImplementation ); } + private function loadBindingForResource(DrydockResource $resource) { + $binding_phid = $resource->getAttribute('almanacBindingPHID'); + if (!$binding_phid) { + throw new Exception( + pht( + 'Drydock resource ("%s") has no Almanac binding PHID, so its '. + 'binding can not be loaded.', + $resource->getPHID())); + } + + $viewer = $this->getViewer(); + + $binding = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withPHIDs(array($binding_phid)) + ->executeOne(); + if (!$binding) { + throw new Exception( + pht( + 'Unable to load Almanac binding ("%s") for resource ("%s").', + $binding_phid, + $resource->getPHID())); + } + + return $binding; + } } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 5acf63bf6b..88bc4d935a 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -139,6 +139,38 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockResource $resource, DrydockLease $lease); + /** + * Return true to try to allocate a new resource and expand the resource + * pool instead of permitting an otherwise valid acquisition on an existing + * resource. + * + * This allows the blueprint to provide a soft hint about when the resource + * pool should grow. + * + * Returning "true" in all cases generally makes sense when a blueprint + * controls a fixed pool of resources, like a particular number of physical + * hosts: you want to put all the hosts in service, so whenever it is + * possible to allocate a new host you want to do this. + * + * Returning "false" in all cases generally make sense when a blueprint + * has a flexible pool of expensive resources and you want to pack leases + * onto them as tightly as possible. + * + * @param DrydockBlueprint The blueprint for an existing resource being + * acquired. + * @param DrydockResource The resource being acquired, which we may want to + * build a supplemental resource for. + * @param DrydockLease The current lease performing acquisition. + * @return bool True to prefer allocating a supplemental resource. + * + * @task lease + */ + public function shouldAllocateSupplementalResource( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + return false; + } /* -( Resource Allocation )------------------------------------------------ */ diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index f45c4f2adb..d82f5c2c15 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -173,6 +173,7 @@ final class DrydockWorkingCopyBlueprintImplementation $map = $resource->getAttribute('repositories.map'); + $futures = array(); $repositories = $this->loadRepositories(ipull($map, 'phid')); foreach ($map as $directory => $spec) { // TODO: Validate directory isn't goofy like "/etc" or "../../lol" @@ -181,11 +182,18 @@ final class DrydockWorkingCopyBlueprintImplementation $repository = $repositories[$spec['phid']]; $path = "{$root}/repo/{$directory}/"; - // TODO: Run these in parallel? - $interface->execx( + $future = $interface->getExecFuture( 'git clone -- %s %s', (string)$repository->getCloneURIObject(), $path); + + $future->setTimeout($repository->getEffectiveCopyTimeLimit()); + + $futures[$directory] = $future; + } + + foreach (new FutureIterator($futures) as $key => $future) { + $future->resolvex(); } $resource @@ -240,8 +248,12 @@ final class DrydockWorkingCopyBlueprintImplementation $map = $lease->getAttribute('repositories.map'); $root = $resource->getAttribute('workingcopy.root'); + $repositories = $this->loadRepositories(ipull($map, 'phid')); + $default = null; foreach ($map as $directory => $spec) { + $repository = $repositories[$spec['phid']]; + $interface->pushWorkingDirectory("{$root}/repo/{$directory}/"); $cmd = array(); @@ -256,8 +268,9 @@ final class DrydockWorkingCopyBlueprintImplementation $ref = idx($spec, 'ref'); // Reset things first, in case previous builds left anything staged or - // dirty. - $cmd[] = 'git reset --hard HEAD'; + // dirty. Note that we don't reset to "HEAD" because that does not work + // in empty repositories. + $cmd[] = 'git reset --hard'; if ($commit !== null) { $cmd[] = 'git checkout %s --'; @@ -270,7 +283,9 @@ final class DrydockWorkingCopyBlueprintImplementation $arg[] = $branch; } - $this->execxv($interface, $cmd, $arg); + $this->newExecvFuture($interface, $cmd, $arg) + ->setTimeout($repository->getEffectiveCopyTimeLimit()) + ->resolvex(); if (idx($spec, 'default')) { $default = $directory; @@ -294,7 +309,9 @@ final class DrydockWorkingCopyBlueprintImplementation $arg[] = $ref_ref; try { - $this->execxv($interface, $cmd, $arg); + $this->newExecvFuture($interface, $cmd, $arg) + ->setTimeout($repository->getEffectiveCopyTimeLimit()) + ->resolvex(); } catch (CommandException $ex) { $display_command = csprintf( 'git fetch %R %R', @@ -508,12 +525,18 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockCommandInterface $interface, array $commands, array $arguments) { + return $this->newExecvFuture($interface, $commands, $arguments)->resolvex(); + } + + private function newExecvFuture( + DrydockCommandInterface $interface, + array $commands, + array $arguments) { $commands = implode(' && ', $commands); $argv = array_merge(array($commands), $arguments); - return call_user_func_array(array($interface, 'execx'), $argv); + return call_user_func_array(array($interface, 'getExecFuture'), $argv); } - } diff --git a/src/applications/drydock/conduit/DrydockLeaseSearchConduitAPIMethod.php b/src/applications/drydock/conduit/DrydockLeaseSearchConduitAPIMethod.php new file mode 100644 index 0000000000..6d75ed1718 --- /dev/null +++ b/src/applications/drydock/conduit/DrydockLeaseSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +setKey('type') ->setLabel(pht('Type')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( DrydockBlueprintTypeTransaction::TRANSACTIONTYPE) ->setDescription(pht('When creating a blueprint, set the type.')) diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 75636dab09..7371232620 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -20,9 +20,11 @@ final class DrydockManagementLeaseWorkflow 'help' => pht('Set lease expiration time.'), ), array( - 'name' => 'attributes', - 'param' => 'name=value,...', - 'help' => pht('Resource specification.'), + 'name' => 'attributes', + 'param' => 'file', + 'help' => pht( + 'JSON file with lease attributes. Use "-" to read attributes '. + 'from stdin.'), ), )); } @@ -49,11 +51,20 @@ final class DrydockManagementLeaseWorkflow } } - $attributes = $args->getArg('attributes'); - if ($attributes) { - $options = new PhutilSimpleOptions(); - $options->setCaseSensitive(true); - $attributes = $options->parse($attributes); + $attributes_file = $args->getArg('attributes'); + if (strlen($attributes_file)) { + if ($attributes_file == '-') { + echo tsprintf( + "%s\n", + 'Reading JSON attributes from stdin...'); + $data = file_get_contents('php://stdin'); + } else { + $data = Filesystem::readFile($attributes_file); + } + + $attributes = phutil_json_decode($data); + } else { + $attributes = array(); } $lease = id(new DrydockLease()) diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index c5fb41ff56..e524c1f9c5 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -5,6 +5,7 @@ final class DrydockLeaseQuery extends DrydockQuery { private $ids; private $phids; private $resourcePHIDs; + private $ownerPHIDs; private $statuses; private $datasourceQuery; private $needUnconsumedCommands; @@ -24,6 +25,11 @@ final class DrydockLeaseQuery extends DrydockQuery { return $this; } + public function withOwnerPHIDs(array $phids) { + $this->ownerPHIDs = $phids; + return $this; + } + public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; @@ -105,6 +111,13 @@ final class DrydockLeaseQuery extends DrydockQuery { $this->resourcePHIDs); } + if ($this->ownerPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'ownerPHID IN (%Ls)', + $this->ownerPHIDs); + } + if ($this->ids !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/drydock/query/DrydockLeaseSearchEngine.php b/src/applications/drydock/query/DrydockLeaseSearchEngine.php index 3b551023b2..39c91e66bf 100644 --- a/src/applications/drydock/query/DrydockLeaseSearchEngine.php +++ b/src/applications/drydock/query/DrydockLeaseSearchEngine.php @@ -40,6 +40,10 @@ final class DrydockLeaseSearchEngine $query->withStatuses($map['statuses']); } + if ($map['ownerPHIDs']) { + $query->withOwnerPHIDs($map['ownerPHIDs']); + } + return $query; } @@ -49,6 +53,11 @@ final class DrydockLeaseSearchEngine ->setLabel(pht('Statuses')) ->setKey('statuses') ->setOptions(DrydockLeaseStatus::getStatusMap()), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Owners')) + ->setKey('ownerPHIDs') + ->setAliases(array('owner', 'owners', 'ownerPHID')) + ->setDescription(pht('Search leases by owner.')), ); } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index f7654a8b31..61e2dbfcfa 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -278,6 +278,15 @@ final class DrydockBlueprint extends DrydockDAO return $interface; } + public function shouldAllocateSupplementalResource( + DrydockResource $resource, + DrydockLease $lease) { + return $this->getImplementation()->shouldAllocateSupplementalResource( + $this, + $resource, + $lease); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/drydock/storage/DrydockCommand.php b/src/applications/drydock/storage/DrydockCommand.php index e7d003bdd6..0f7f253217 100644 --- a/src/applications/drydock/storage/DrydockCommand.php +++ b/src/applications/drydock/storage/DrydockCommand.php @@ -11,6 +11,7 @@ final class DrydockCommand protected $targetPHID; protected $command; protected $isConsumed; + protected $properties = array(); private $commandTarget = self::ATTACHABLE; @@ -22,6 +23,9 @@ final class DrydockCommand protected function getConfiguration() { return array( + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( 'command' => 'text32', 'isConsumed' => 'bool', @@ -43,6 +47,14 @@ final class DrydockCommand return $this->assertAttached($this->commandTarget); } + public function setProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 866bb21b37..2b77146495 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -1,7 +1,9 @@ array( 'columns' => array('status'), ), + 'key_owner' => array( + 'columns' => array('ownerPHID'), + ), ), ) + parent::getConfiguration(); } @@ -535,4 +540,66 @@ final class DrydockLease extends DrydockDAO return pht('Leases inherit policies from the resources they lease.'); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('resourcePHID') + ->setType('phid?') + ->setDescription(pht('PHID of the leased resource, if any.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('resourceType') + ->setType('string') + ->setDescription(pht('Type of resource being leased.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('until') + ->setType('int?') + ->setDescription(pht('Epoch at which this lease expires, if any.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('ownerPHID') + ->setType('phid?') + ->setDescription(pht('The PHID of the object that owns this lease.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authorizingPHID') + ->setType('phid') + ->setDescription(pht( + 'The PHID of the object that authorized this lease.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht( + "The string constant and name of this lease's status.")), + ); + } + + public function getFieldValuesForConduit() { + $status = $this->getStatus(); + + $until = $this->getUntil(); + if ($until) { + $until = (int)$until; + } else { + $until = null; + } + + return array( + 'resourcePHID' => $this->getResourcePHID(), + 'resourceType' => $this->getResourceType(), + 'until' => $until, + 'ownerPHID' => $this->getOwnerPHID(), + 'authorizingPHID' => $this->getAuthorizingPHID(), + 'status' => array( + 'value' => $status, + 'name' => DrydockLeaseStatus::getNameForStatus($status), + ), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/drydock/storage/DrydockSlotLock.php b/src/applications/drydock/storage/DrydockSlotLock.php index 7e4e320946..7a9bce8b3f 100644 --- a/src/applications/drydock/storage/DrydockSlotLock.php +++ b/src/applications/drydock/storage/DrydockSlotLock.php @@ -140,9 +140,9 @@ final class DrydockSlotLock extends DrydockDAO { try { queryfx( $conn_w, - 'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %Q', + 'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %LQ', $table->getTableName(), - implode(', ', $sql)); + $sql); } catch (AphrontDuplicateKeyQueryException $ex) { // Try to improve the readability of the exception. We might miss on // this query if the lock has already been released, but most of the diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 4a0cba8de2..b83022f720 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -306,6 +306,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $allocated = false; foreach ($resources as $resource) { try { + $resource = $this->newResourceForAcquisition($resource, $lease); $this->acquireLease($resource, $lease); $allocated = true; break; @@ -319,6 +320,10 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { // got around to acquiring it, we just got unlucky. We can yield and // try again later. $yields[] = $ex; + } catch (PhabricatorWorkerYieldException $ex) { + // We can be told to yield, particularly by the supplemental allocator + // trying to give us a supplemental resource. + $yields[] = $ex; } catch (Exception $ex) { $exceptions[] = $ex; } @@ -599,6 +604,13 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { 'DrydockResourceUpdateWorker', array( 'resourcePHID' => $resource->getPHID(), + + // This task will generally yield while the resource activates, so + // wake it back up once the resource comes online. Most of the time, + // we'll be able to lease the newly activated resource. + 'awakenOnActivation' => array( + $this->getCurrentWorkerTaskID(), + ), ), array( 'objectPHID' => $resource->getPHID(), @@ -666,6 +678,26 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { DrydockLease $lease) { $viewer = $this->getViewer(); + // If this lease is marked as already in the process of reclaiming a + // resource, don't let it reclaim another one until the first reclaim + // completes. This stops one lease from reclaiming a large number of + // resources if the reclaims take a while to complete. + $reclaiming_phid = $lease->getAttribute('drydock.reclaimingPHID'); + if ($reclaiming_phid) { + $reclaiming_resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withPHIDs(array($reclaiming_phid)) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_ACTIVE, + DrydockResourceStatus::STATUS_RELEASED, + )) + ->executeOne(); + if ($reclaiming_resource) { + return null; + } + } + $resources = id(new DrydockResourceQuery()) ->setViewer($viewer) ->withBlueprintPHIDs(array($blueprint->getPHID())) @@ -764,6 +796,73 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } } + private function newResourceForAcquisition( + DrydockResource $resource, + DrydockLease $lease) { + + // If the resource has no leases against it, never build a new one. This is + // likely already a new resource that just activated. + $viewer = $this->getViewer(); + + $statuses = array( + DrydockLeaseStatus::STATUS_PENDING, + DrydockLeaseStatus::STATUS_ACQUIRED, + DrydockLeaseStatus::STATUS_ACTIVE, + ); + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withResourcePHIDs(array($resource->getPHID())) + ->withStatuses($statuses) + ->setLimit(1) + ->execute(); + if (!$leases) { + return $resource; + } + + // If we're about to get a lease on a resource, check if the blueprint + // wants to allocate a supplemental resource. If it does, try to perform a + // new allocation instead. + $blueprint = $resource->getBlueprint(); + if (!$blueprint->shouldAllocateSupplementalResource($resource, $lease)) { + return $resource; + } + + // If the blueprint is already overallocated, we can't allocate a new + // resource. Just return the existing resource. + $remaining = $this->removeOverallocatedBlueprints( + array($blueprint), + $lease); + if (!$remaining) { + return $resource; + } + + // Try to build a new resource. + try { + $new_resource = $this->allocateResource($blueprint, $lease); + } catch (Exception $ex) { + $blueprint->logEvent( + DrydockResourceAllocationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + return $resource; + } + + // If we can't actually acquire the new resource yet, just yield. + // (We could try to move forward with the original resource instead.) + $acquirable = $this->removeUnacquirableResources( + array($new_resource), + $lease); + if (!$acquirable) { + throw new PhabricatorWorkerYieldException(15); + } + + return $new_resource; + } + /* -( Activating Leases )-------------------------------------------------- */ diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index baa80f90d8..14ef8e4936 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -150,6 +150,13 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $this->releaseResource($resource, $reclaimer_phid); break; } + + // If the command specifies that other worker tasks should be awakened + // after it executes, awaken them now. + $awaken_ids = $command->getProperty('awakenTaskIDs'); + if (is_array($awaken_ids) && $awaken_ids) { + PhabricatorWorker::awakenTaskIDs($awaken_ids); + } } @@ -163,6 +170,11 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $blueprint = $resource->getBlueprint(); $blueprint->activateResource($resource); $this->validateActivatedResource($blueprint, $resource); + + $awaken_ids = $this->getTaskDataValue('awakenOnActivation'); + if (is_array($awaken_ids) && $awaken_ids) { + PhabricatorWorker::awakenTaskIDs($awaken_ids); + } } diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index 443780680d..fcb6876cfe 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -241,11 +241,25 @@ abstract class DrydockWorker extends PhabricatorWorker { DrydockLease $lease) { $viewer = $this->getViewer(); + // Mark the lease as reclaiming this resource. It won't be allowed to start + // another reclaim as long as this resource is still in the process of + // being reclaimed. + $lease->setAttribute('drydock.reclaimingPHID', $resource->getPHID()); + + // When the resource releases, we we want to reawaken this task since it + // should (usually) be able to start building a new resource right away. + $worker_task_id = $this->getCurrentWorkerTaskID(); + $command = DrydockCommand::initializeNewCommand($viewer) ->setTargetPHID($resource->getPHID()) ->setAuthorPHID($lease->getPHID()) ->setCommand(DrydockCommand::COMMAND_RECLAIM) - ->save(); + ->setProperty('awakenTaskIDs', array($worker_task_id)); + + $lease->openTransaction(); + $lease->save(); + $command->save(); + $lease->saveTransaction(); $resource->scheduleUpdate(); diff --git a/src/applications/fact/daemon/PhabricatorFactDaemon.php b/src/applications/fact/daemon/PhabricatorFactDaemon.php index 3fa6c6f0ff..57813b3021 100644 --- a/src/applications/fact/daemon/PhabricatorFactDaemon.php +++ b/src/applications/fact/daemon/PhabricatorFactDaemon.php @@ -189,7 +189,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon { $conn, 'INSERT INTO %T (keyID, objectID, dimensionID, value, epoch) - VALUES %Q', + VALUES %LQ', $table_name, $chunk); } diff --git a/src/applications/fact/query/PhabricatorFactDatapointQuery.php b/src/applications/fact/query/PhabricatorFactDatapointQuery.php index fee7c86d12..20945dbbd0 100644 --- a/src/applications/fact/query/PhabricatorFactDatapointQuery.php +++ b/src/applications/fact/query/PhabricatorFactDatapointQuery.php @@ -158,7 +158,7 @@ final class PhabricatorFactDatapointQuery extends Phobject { $this->dimensionMap); } - $where = '('.implode(') AND (', $where).')'; + $where = qsprintf($conn, '%LA', $where); if ($this->limit) { $limit = qsprintf( @@ -166,7 +166,7 @@ final class PhabricatorFactDatapointQuery extends Phobject { 'LIMIT %d', $this->limit); } else { - $limit = ''; + $limit = qsprintf($conn, ''); } return queryfx_all( diff --git a/src/applications/fact/storage/PhabricatorFactDimension.php b/src/applications/fact/storage/PhabricatorFactDimension.php index 9c05121c9c..5644da331e 100644 --- a/src/applications/fact/storage/PhabricatorFactDimension.php +++ b/src/applications/fact/storage/PhabricatorFactDimension.php @@ -75,7 +75,7 @@ abstract class PhabricatorFactDimension extends PhabricatorFactDAO { foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn, - 'INSERT IGNORE INTO %T (%C) VALUES %Q', + 'INSERT IGNORE INTO %T (%C) VALUES %LQ', $this->getTableName(), $column, $chunk); diff --git a/src/applications/feed/PhabricatorFeedStoryPublisher.php b/src/applications/feed/PhabricatorFeedStoryPublisher.php index 47dde8c98a..70d9a2f61c 100644 --- a/src/applications/feed/PhabricatorFeedStoryPublisher.php +++ b/src/applications/feed/PhabricatorFeedStoryPublisher.php @@ -133,9 +133,9 @@ final class PhabricatorFeedStoryPublisher extends Phobject { queryfx( $conn, - 'INSERT INTO %T (objectPHID, chronologicalKey) VALUES %Q', + 'INSERT INTO %T (objectPHID, chronologicalKey) VALUES %LQ', $ref->getTableName(), - implode(', ', $sql)); + $sql); } $subscribed_phids = $this->subscribedPHIDs; @@ -191,9 +191,9 @@ final class PhabricatorFeedStoryPublisher extends Phobject { $conn, 'INSERT INTO %T '. '(primaryObjectPHID, userPHID, chronologicalKey, hasViewed) '. - 'VALUES %Q', + 'VALUES %LQ', $notif->getTableName(), - implode(', ', $sql)); + $sql); } PhabricatorUserCache::clearCaches( diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index ae1da3aba0..ce472a6026 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -64,22 +64,20 @@ final class PhabricatorFeedQuery } if ($this->chronologicalKeys !== null) { - // NOTE: We want to use integers in the query so we can take advantage - // of keys, but can't use %d on 32-bit systems. Make sure all the keys - // are integers and then format them raw. + // NOTE: We can't use "%d" to format these large integers on 32-bit + // systems. Historically, we formatted these into integers in an + // awkward way because MySQL could sometimes (?) fail to use the proper + // keys if the values were formatted as strings instead of integers. - $keys = $this->chronologicalKeys; - foreach ($keys as $key) { - if (!ctype_digit($key)) { - throw new Exception( - pht("Key '%s' is not a valid chronological key!", $key)); - } - } + // After the "qsprintf()" update to use PhutilQueryString, we can no + // longer do this in a sneaky way. However, the MySQL key issue also + // no longer appears to reproduce across several systems. So: just use + // strings until problems turn up? $where[] = qsprintf( $conn, - 'ref.chronologicalKey IN (%Q)', - implode(', ', $keys)); + 'ref.chronologicalKey IN (%Ls)', + $this->chronologicalKeys); } // NOTE: We may not have 64-bit PHP, so do the shifts in MySQL instead. diff --git a/src/applications/files/query/PhabricatorFileChunkQuery.php b/src/applications/files/query/PhabricatorFileChunkQuery.php index b6fda13103..4398860569 100644 --- a/src/applications/files/query/PhabricatorFileChunkQuery.php +++ b/src/applications/files/query/PhabricatorFileChunkQuery.php @@ -86,26 +86,26 @@ final class PhabricatorFileChunkQuery return $chunks; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->chunkHandles !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'chunkHandle IN (%Ls)', $this->chunkHandles); } if ($this->rangeStart !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'byteEnd > %d', $this->rangeStart); } if ($this->rangeEnd !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'byteStart < %d', $this->rangeEnd); } @@ -113,18 +113,18 @@ final class PhabricatorFileChunkQuery if ($this->isComplete !== null) { if ($this->isComplete) { $where[] = qsprintf( - $conn_r, + $conn, 'dataFilePHID IS NOT NULL'); } else { $where[] = qsprintf( - $conn_r, + $conn, 'dataFilePHID IS NULL'); } } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 9f1659a7f3..4205ab5c5d 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -391,7 +391,7 @@ final class PhabricatorFileQuery $transform['transform']); } } - $where[] = qsprintf($conn, '(%Q)', implode(') OR (', $clauses)); + $where[] = qsprintf($conn, '%LO', $clauses); } if ($this->dateCreatedAfter !== null) { diff --git a/src/applications/flag/query/PhabricatorFlagQuery.php b/src/applications/flag/query/PhabricatorFlagQuery.php index ff154a512b..3418f10746 100644 --- a/src/applications/flag/query/PhabricatorFlagQuery.php +++ b/src/applications/flag/query/PhabricatorFlagQuery.php @@ -123,40 +123,40 @@ final class PhabricatorFlagQuery return $flags; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ownerPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'flag.ownerPHID IN (%Ls)', $this->ownerPHIDs); } if ($this->types) { $where[] = qsprintf( - $conn_r, + $conn, 'flag.type IN (%Ls)', $this->types); } if ($this->objectPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'flag.objectPHID IN (%Ls)', $this->objectPHIDs); } if ($this->colors) { $where[] = qsprintf( - $conn_r, + $conn, 'flag.color IN (%Ld)', $this->colors); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/fund/query/FundBackerQuery.php b/src/applications/fund/query/FundBackerQuery.php index 5f7406f5ad..2d6e13dfd9 100644 --- a/src/applications/fund/query/FundBackerQuery.php +++ b/src/applications/fund/query/FundBackerQuery.php @@ -68,47 +68,47 @@ final class FundBackerQuery return $backers; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->initiativePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'initiativePHID IN (%Ls)', $this->initiativePHIDs); } if ($this->backerPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'backerPHID IN (%Ls)', $this->backerPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ls)', $this->statuses); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php new file mode 100644 index 0000000000..b5e03b3071 --- /dev/null +++ b/src/applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php @@ -0,0 +1,18 @@ +setLabel(pht('Builds')) + ->setKey('buildPHIDs') + ->setAliases(array('build', 'builds', 'buildPHID')) + ->setDescription( + pht('Search for targets of a given build.')) + ->setDatasource(new HarbormasterBuildPlanDatasource()), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['buildPHIDs']) { + $query->withBuildPHIDs($map['buildPHIDs']); + } + + return $query; + } + + protected function getURI($path) { + return '/harbormaster/target/'.$path; + } + + protected function getBuiltinQueryNames() { + return array( + 'all' => pht('All Targets'), + ); + } + + 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 $builds, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($builds, 'HarbormasterBuildTarget'); + + // Currently, this only supports the "harbormaster.target.search" + // API method. + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index e0dfe05cc0..3bbb72cd95 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -253,12 +253,32 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { HarbormasterBuildTarget $target, array $futures) { + $did_close = false; + $wait_start = PhabricatorTime::getNow(); + $futures = new FutureIterator($futures); foreach ($futures->setUpdateInterval(5) as $key => $future) { - if ($future === null) { - $build->reload(); - if ($this->shouldAbort($build, $target)) { - throw new HarbormasterBuildAbortedException(); + if ($future !== null) { + continue; + } + + $build->reload(); + if ($this->shouldAbort($build, $target)) { + throw new HarbormasterBuildAbortedException(); + } + + // See PHI916. If we're waiting on a remote system for a while, clean + // up database connections to reduce the cost of having a large number + // of processes babysitting an `ssh ... ./run-huge-build.sh` process on + // a build host. + if (!$did_close) { + $now = PhabricatorTime::getNow(); + $elapsed = ($now - $wait_start); + $idle_limit = 5; + + if ($elapsed >= $idle_limit) { + LiskDAO::closeIdleConnections(); + $did_close = true; } } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index b24c4805e2..98f374579b 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -6,6 +6,7 @@ final class HarbormasterBuildable PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, HarbormasterBuildableInterface, + PhabricatorConduitResultInterface, PhabricatorDestructibleInterface { protected $buildablePHID; @@ -355,6 +356,46 @@ final class HarbormasterBuildable } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('objectPHID') + ->setType('phid') + ->setDescription(pht('PHID of the object that is built.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('containerPHID') + ->setType('phid') + ->setDescription(pht('PHID of the object containing this buildable.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('buildableStatus') + ->setType('map') + ->setDescription(pht('The current status of this buildable.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isManual') + ->setType('bool') + ->setDescription(pht('True if this is a manual buildable.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'objectPHID' => $this->getBuildablePHID(), + 'containerPHID' => $this->getContainerPHID(), + 'buildableStatus' => array( + 'value' => $this->getBuildableStatus(), + ), + 'isManual' => (bool)$this->getIsManualBuildable(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index b559a66198..30b1bd79e4 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -4,7 +4,8 @@ final class HarbormasterBuildTarget extends HarbormasterDAO implements PhabricatorPolicyInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $name; protected $buildPHID; @@ -412,4 +413,85 @@ final class HarbormasterBuildTarget } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the build target.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('buildPHID') + ->setType('phid') + ->setDescription(pht('The build the target is associated with.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('buildStepPHID') + ->setType('phid') + ->setDescription(pht('The build step the target runs.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('Status for the build target.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('epochStarted') + ->setType('epoch?') + ->setDescription( + pht( + 'Epoch timestamp for target start, if the target '. + 'has started.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('epochCompleted') + ->setType('epoch?') + ->setDescription( + pht( + 'Epoch timestamp for target completion, if the target '. + 'has completed.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('buildGeneration') + ->setType('int') + ->setDescription( + pht( + 'Build generation this target belongs to. When builds '. + 'restart, a new generation with new targets is created.')), + ); + } + + public function getFieldValuesForConduit() { + $status = $this->getTargetStatus(); + + $epoch_started = $this->getDateStarted(); + if ($epoch_started) { + $epoch_started = (int)$epoch_started; + } else { + $epoch_started = null; + } + + $epoch_completed = $this->getDateCompleted(); + if ($epoch_completed) { + $epoch_completed = (int)$epoch_completed; + } else { + $epoch_completed = null; + } + + return array( + 'name' => $this->getName(), + 'buildPHID' => $this->getBuildPHID(), + 'buildStepPHID' => $this->getBuildStepPHID(), + 'status' => array( + 'value' => $status, + 'name' => self::getBuildTargetStatusName($status), + ), + 'epochStarted' => $epoch_started, + 'epochCompleted' => $epoch_completed, + 'buildGeneration' => (int)$this->getBuildGeneration(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index 2a101ca355..fc9ab5f573 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -9,6 +9,7 @@ final class HarbormasterBuildPlan extends HarbormasterDAO PhabricatorPolicyInterface, PhabricatorSubscribableInterface, PhabricatorNgramsInterface, + PhabricatorConduitResultInterface, PhabricatorProjectInterface { protected $name; @@ -207,4 +208,34 @@ final class HarbormasterBuildPlan extends HarbormasterDAO ); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of this build plan.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('The current status of this build plan.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'status' => array( + 'value' => $this->getPlanStatus(), + ), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/herald/controller/HeraldDisableController.php b/src/applications/herald/controller/HeraldDisableController.php index bdbefa55ea..def87049f7 100644 --- a/src/applications/herald/controller/HeraldDisableController.php +++ b/src/applications/herald/controller/HeraldDisableController.php @@ -44,13 +44,13 @@ final class HeraldDisableController extends HeraldController { } if ($is_disable) { - $title = pht('Really archive this rule?'); + $title = pht('Really disable this rule?'); $body = pht('This rule will no longer activate.'); - $button = pht('Archive Rule'); + $button = pht('Disable Rule'); } else { - $title = pht('Really activate this rule?'); + $title = pht('Really enable this rule?'); $body = pht('This rule will become active again.'); - $button = pht('Activate Rule'); + $button = pht('Enable Rule'); } $dialog = id(new AphrontDialogView()) diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 9e696c23c4..70a2d20224 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -14,6 +14,7 @@ final class HeraldRuleViewController extends HeraldController { ->setViewer($viewer) ->withIDs(array($id)) ->needConditionsAndActions(true) + ->needValidateAuthors(true) ->executeOne(); if (!$rule) { return new Aphront404Response(); @@ -26,15 +27,11 @@ final class HeraldRuleViewController extends HeraldController { ->setHeaderIcon('fa-bullhorn'); if ($rule->getIsDisabled()) { - $header->setStatus( - 'fa-ban', - 'red', - pht('Archived')); + $header->setStatus('fa-ban', 'red', pht('Disabled')); + } else if (!$rule->hasValidAuthor()) { + $header->setStatus('fa-user', 'red', pht('Author Not Active')); } else { - $header->setStatus( - 'fa-check', - 'bluegrey', - pht('Active')); + $header->setStatus('fa-check', 'bluegrey', pht('Active')); } $curtain = $this->buildCurtain($rule); @@ -90,16 +87,15 @@ final class HeraldRuleViewController extends HeraldController { if ($rule->getIsDisabled()) { $disable_uri = "disable/{$id}/enable/"; $disable_icon = 'fa-check'; - $disable_name = pht('Activate Rule'); + $disable_name = pht('Enable Rule'); } else { $disable_uri = "disable/{$id}/disable/"; $disable_icon = 'fa-ban'; - $disable_name = pht('Archive Rule'); + $disable_name = pht('Disable Rule'); } $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Disable Rule')) ->setHref($this->getApplicationURI($disable_uri)) ->setIcon($disable_icon) ->setName($disable_name) diff --git a/src/applications/herald/management/HeraldManagementWorkflow.php b/src/applications/herald/management/HeraldManagementWorkflow.php new file mode 100644 index 0000000000..50559ec3bc --- /dev/null +++ b/src/applications/herald/management/HeraldManagementWorkflow.php @@ -0,0 +1,4 @@ +setName('test') + ->setExamples('**test** --object __object__ --type __type__') + ->setSynopsis( + pht( + 'Test content rules for an object. Executes a dry run, like the '. + 'web UI test console.')) + ->setArguments( + array( + array( + 'name' => 'object', + 'param' => 'object', + 'help' => pht('Run rules on this object.'), + ), + array( + 'name' => 'type', + 'param' => 'type', + 'help' => pht('Run rules for this content type.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $object_name = $args->getArg('object'); + if (!strlen($object_name)) { + throw new PhutilArgumentUsageException( + pht('Specify an object to test rules for with "--object".')); + } + + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($object_name)) + ->execute(); + if (!$objects) { + throw new PhutilArgumentUsageException( + pht( + 'Unable to load specified object ("%s").', + $object_name)); + } + $object = head($objects); + + $adapters = HeraldAdapter::getAllAdapters(); + + $can_select = array(); + $display_adapters = array(); + foreach ($adapters as $key => $adapter) { + if (!$adapter->isTestAdapterForObject($object)) { + continue; + } + + if (!$adapter->isAvailableToUser($viewer)) { + continue; + } + + $display_adapters[$key] = $adapter; + + if ($adapter->canCreateTestAdapterForObject($object)) { + $can_select[$key] = $adapter; + } + } + + + $content_type = $args->getArg('type'); + if (!strlen($content_type)) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a content type to run rules for. For this object, valid '. + 'content types are: %s.', + implode(', ', array_keys($can_select)))); + } + + if (!isset($can_select[$content_type])) { + if (!isset($display_adapters[$content_type])) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a content type to run rules for. The specified content '. + 'type ("%s") is not valid. For this object, valid content types '. + 'are: %s.', + $content_type, + implode(', ', array_keys($can_select)))); + } else { + throw new PhutilArgumentUsageException( + pht( + 'The specified content type ("%s") does not support dry runs. '. + 'Choose a testable content type. For this object, valid content '. + 'types are: %s.', + $content_type, + implode(', ', array_keys($can_select)))); + } + } + + $adapter = $can_select[$content_type]->newTestAdapter( + $viewer, + $object); + + $content_source = $this->newContentSource(); + + $adapter + ->setContentSource($content_source) + ->setIsNewObject(false) + ->setViewer($viewer); + + $rules = id(new HeraldRuleQuery()) + ->setViewer($viewer) + ->withContentTypes(array($adapter->getAdapterContentType())) + ->withDisabled(false) + ->needConditionsAndActions(true) + ->needAppliedToPHIDs(array($object->getPHID())) + ->needValidateAuthors(true) + ->execute(); + + $engine = id(new HeraldEngine()) + ->setDryRun(true); + + $effects = $engine->applyRules($rules, $adapter); + $engine->applyEffects($effects, $adapter, $rules); + + $xscript = $engine->getTranscript(); + + $uri = '/herald/transcript/'.$xscript->getID().'/'; + $uri = PhabricatorEnv::getProductionURI($uri); + + echo tsprintf( + "%s\n\n __%s__\n\n", + pht('Test run complete. Transcript:'), + $uri); + + return 0; + } + +} diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php index 88df18db24..e6dba43c7a 100644 --- a/src/applications/herald/query/HeraldRuleQuery.php +++ b/src/applications/herald/query/HeraldRuleQuery.php @@ -8,6 +8,7 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $ruleTypes; private $contentTypes; private $disabled; + private $active; private $datasourceQuery; private $triggerObjectPHIDs; @@ -45,6 +46,11 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } + public function withActive($active) { + $this->active = $active; + return $this; + } + public function withDatasourceQuery($query) { $this->datasourceQuery = $query; return $this; @@ -70,19 +76,12 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } + public function newResultObject() { + return new HeraldRule(); + } + protected function loadPage() { - $table = new HeraldRule(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT rule.* FROM %T rule %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $rules) { @@ -99,10 +98,31 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery { } } - if ($this->needValidateAuthors) { + if ($this->needValidateAuthors || ($this->active !== null)) { $this->validateRuleAuthors($rules); } + if ($this->active !== null) { + $need_active = (bool)$this->active; + foreach ($rules as $key => $rule) { + if ($rule->getIsDisabled()) { + $is_active = false; + } else if (!$rule->hasValidAuthor()) { + $is_active = false; + } else { + $is_active = true; + } + + if ($is_active != $need_active) { + unset($rules[$key]); + } + } + } + + if (!$rules) { + return array(); + } + if ($this->needConditionsAndActions) { $conditions = id(new HeraldCondition())->loadAllWhere( 'ruleID IN (%Ld)', @@ -175,68 +195,73 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $rules; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.phid IN (%Ls)', $this->phids); } - if ($this->authorPHIDs) { + if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.authorPHID IN (%Ls)', $this->authorPHIDs); } - if ($this->ruleTypes) { + if ($this->ruleTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.ruleType IN (%Ls)', $this->ruleTypes); } - if ($this->contentTypes) { + if ($this->contentTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.contentType IN (%Ls)', $this->contentTypes); } if ($this->disabled !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.isDisabled = %d', (int)$this->disabled); } - if ($this->datasourceQuery) { + if ($this->active !== null) { $where[] = qsprintf( - $conn_r, + $conn, + 'rule.isDisabled = %d', + (int)(!$this->active)); + } + + if ($this->datasourceQuery !== null) { + $where[] = qsprintf( + $conn, 'rule.name LIKE %>', $this->datasourceQuery); } - if ($this->triggerObjectPHIDs) { + if ($this->triggerObjectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'rule.triggerObjectPHID IN (%Ls)', $this->triggerObjectPHIDs); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } private function validateRuleAuthors(array $rules) { @@ -281,4 +306,8 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery { return 'PhabricatorHeraldApplication'; } + protected function getPrimaryTableAlias() { + return 'rule'; + } + } diff --git a/src/applications/herald/query/HeraldRuleSearchEngine.php b/src/applications/herald/query/HeraldRuleSearchEngine.php index 3b8196c13a..47a6832731 100644 --- a/src/applications/herald/query/HeraldRuleSearchEngine.php +++ b/src/applications/herald/query/HeraldRuleSearchEngine.php @@ -10,90 +10,80 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { return 'PhabricatorHeraldApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $saved->setParameter('contentType', $request->getStr('contentType')); - $saved->setParameter('ruleType', $request->getStr('ruleType')); - $saved->setParameter( - 'disabled', - $this->readBoolFromRequest($request, 'disabled')); - - return $saved; + public function newQuery() { + return id(new HeraldRuleQuery()) + ->needValidateAuthors(true); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new HeraldRuleQuery()); + protected function buildCustomSearchFields() { + $viewer = $this->requireViewer(); - $author_phids = $saved->getParameter('authorPHIDs'); - if ($author_phids) { - $query->withAuthorPHIDs($author_phids); + $rule_types = HeraldRuleTypeConfig::getRuleTypeMap(); + $content_types = HeraldAdapter::getEnabledAdapterMap($viewer); + + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Authors')) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors', 'authorPHID')) + ->setDescription( + pht('Search for rules with given authors.')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('ruleTypes') + ->setAliases(array('ruleType')) + ->setLabel(pht('Rule Type')) + ->setDescription( + pht('Search for rules of given types.')) + ->setOptions($rule_types), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('contentTypes') + ->setLabel(pht('Content Type')) + ->setDescription( + pht('Search for rules affecting given types of content.')) + ->setOptions($content_types), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Active Rules')) + ->setKey('active') + ->setOptions( + pht('(Show All)'), + pht('Show Only Active Rules'), + pht('Show Only Inactive Rules')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Disabled Rules')) + ->setKey('disabled') + ->setOptions( + pht('(Show All)'), + pht('Show Only Disabled Rules'), + pht('Show Only Enabled Rules')), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['authorPHIDs']) { + $query->withAuthorPHIDs($map['authorPHIDs']); } - $content_type = $saved->getParameter('contentType'); - $content_type = idx($this->getContentTypeValues(), $content_type); - if ($content_type) { - $query->withContentTypes(array($content_type)); + if ($map['contentTypes']) { + $query->withContentTypes($map['contentTypes']); } - $rule_type = $saved->getParameter('ruleType'); - $rule_type = idx($this->getRuleTypeValues(), $rule_type); - if ($rule_type) { - $query->withRuleTypes(array($rule_type)); + if ($map['ruleTypes']) { + $query->withRuleTypes($map['ruleTypes']); } - $disabled = $saved->getParameter('disabled'); - if ($disabled !== null) { - $query->withDisabled($disabled); + if ($map['disabled'] !== null) { + $query->withDisabled($map['disabled']); + } + + if ($map['active'] !== null) { + $query->withActive($map['active']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $author_phids = $saved_query->getParameter('authorPHIDs', array()); - $content_type = $saved_query->getParameter('contentType'); - $rule_type = $saved_query->getParameter('ruleType'); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('authors') - ->setLabel(pht('Authors')) - ->setValue($author_phids)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('contentType') - ->setLabel(pht('Content Type')) - ->setValue($content_type) - ->setOptions($this->getContentTypeOptions())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('ruleType') - ->setLabel(pht('Rule Type')) - ->setValue($rule_type) - ->setOptions($this->getRuleTypeOptions())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('disabled') - ->setLabel(pht('Rule Status')) - ->setValue($this->getBoolFromQuery($saved_query, 'disabled')) - ->setOptions( - array( - '' => pht('Show Enabled and Disabled Rules'), - 'false' => pht('Show Only Enabled Rules'), - 'true' => pht('Show Only Disabled Rules'), - ))); - } - protected function getURI($path) { return '/herald/'.$path; } @@ -121,7 +111,8 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { case 'all': return $query; case 'active': - return $query->setParameter('disabled', false); + return $query + ->setParameter('active', true); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer_phid)) @@ -131,35 +122,6 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { return parent::buildSavedQueryFromBuiltin($query_key); } - private function getContentTypeOptions() { - return array( - '' => pht('(All Content Types)'), - ) + HeraldAdapter::getEnabledAdapterMap($this->requireViewer()); - } - - private function getContentTypeValues() { - return array_fuse( - array_keys( - HeraldAdapter::getEnabledAdapterMap($this->requireViewer()))); - } - - private function getRuleTypeOptions() { - return array( - '' => pht('(All Rule Types)'), - ) + HeraldRuleTypeConfig::getRuleTypeMap(); - } - - private function getRuleTypeValues() { - return array_fuse(array_keys(HeraldRuleTypeConfig::getRuleTypeMap())); - } - - protected function getRequiredHandlePHIDsForResultList( - array $rules, - PhabricatorSavedQuery $query) { - - return mpull($rules, 'getAuthorPHID'); - } - protected function renderResultList( array $rules, PhabricatorSavedQuery $query, @@ -167,6 +129,7 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { assert_instances_of($rules, 'HeraldRule'); $viewer = $this->requireViewer(); + $handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID')); $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); @@ -195,6 +158,9 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { if ($rule->getIsDisabled()) { $item->setDisabled(true); $item->addIcon('fa-lock grey', pht('Disabled')); + } else if (!$rule->hasValidAuthor()) { + $item->setDisabled(true); + $item->addIcon('fa-user grey', pht('Author Not Active')); } $content_type_name = idx($content_type_map, $rule->getContentType()); diff --git a/src/applications/herald/query/HeraldTranscriptQuery.php b/src/applications/herald/query/HeraldTranscriptQuery.php index 7d0fdfc59e..71789e07c5 100644 --- a/src/applications/herald/query/HeraldTranscriptQuery.php +++ b/src/applications/herald/query/HeraldTranscriptQuery.php @@ -30,36 +30,35 @@ final class HeraldTranscriptQuery protected function loadPage() { $transcript = new HeraldTranscript(); - $conn_r = $transcript->establishConnection('r'); + $conn = $transcript->establishConnection('r'); // NOTE: Transcripts include a potentially enormous amount of serialized // data, so we're loading only some of the fields here if the caller asked // for partial records. if ($this->needPartialRecords) { - $fields = implode( - ', ', - array( - 'id', - 'phid', - 'objectPHID', - 'time', - 'duration', - 'dryRun', - 'host', - )); + $fields = array( + 'id', + 'phid', + 'objectPHID', + 'time', + 'duration', + 'dryRun', + 'host', + ); + $fields = qsprintf($conn, '%LC', $fields); } else { - $fields = '*'; + $fields = qsprintf($conn, '*'); } $rows = queryfx_all( - $conn_r, + $conn, 'SELECT %Q FROM %T t %Q %Q %Q', $fields, $transcript->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + $this->buildWhereClause($conn), + $this->buildOrderClause($conn), + $this->buildLimitClause($conn)); $transcripts = $transcript->loadAllFromArray($rows); @@ -91,33 +90,33 @@ final class HeraldTranscriptQuery return $transcripts; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->objectPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID in (%Ls)', $this->objectPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index 1748dba452..8ef35e3493 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -149,15 +149,16 @@ final class LegalpadDocumentSignController extends LegalpadController { } $errors = array(); + $hisec_token = null; if ($request->isFormOrHisecPost() && !$has_signed) { // Require two-factor auth to sign legal documents. if ($viewer->isLoggedIn()) { - $engine = new PhabricatorAuthSessionEngine(); - $engine->requireHighSecuritySession( - $viewer, - $request, - '/'.$document->getMonogram()); + $hisec_token = id(new PhabricatorAuthSessionEngine()) + ->requireHighSecurityToken( + $viewer, + $request, + $document->getURI()); } list($form_data, $errors, $field_errors) = $this->readSignatureForm( diff --git a/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php b/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php index 452ea6759b..c310dd3d64 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php @@ -86,61 +86,61 @@ final class LegalpadDocumentSignatureQuery return $signatures; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->documentPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'documentPHID IN (%Ls)', $this->documentPHIDs); } if ($this->signerPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'signerPHID IN (%Ls)', $this->signerPHIDs); } if ($this->documentVersions !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'documentVersion IN (%Ld)', $this->documentVersions); } if ($this->secretKeys !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'secretKey IN (%Ls)', $this->secretKeys); } if ($this->nameContains !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'signerName LIKE %~', $this->nameContains); } if ($this->emailContains !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'signerEmail LIKE %~', $this->emailContains); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index 55d1ef9fa9..004802de39 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -120,7 +120,7 @@ final class LegalpadDocument extends LegalpadDAO return 'L'.$this->getID(); } - public function getViewURI() { + public function getURI() { return '/'.$this->getMonogram(); } diff --git a/src/applications/macro/engine/PhabricatorMemeEngine.php b/src/applications/macro/engine/PhabricatorMemeEngine.php index 3e87a304c9..7433a4e8bc 100644 --- a/src/applications/macro/engine/PhabricatorMemeEngine.php +++ b/src/applications/macro/engine/PhabricatorMemeEngine.php @@ -174,6 +174,15 @@ final class PhabricatorMemeEngine extends Phobject { private function newAssetData(PhabricatorFile $template) { $template_data = $template->loadFileData(); + // When we aren't adding text, just return the data unmodified. This saves + // us from doing expensive stitching when we aren't actually making any + // changes to the image. + $above_text = $this->getAboveText(); + $below_text = $this->getBelowText(); + if (!strlen(trim($above_text)) && !strlen(trim($below_text))) { + return $template_data; + } + $result = $this->newImagemagickAsset($template, $template_data); if ($result) { return $result; diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 41722a3124..6558c33144 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -169,9 +169,7 @@ EODOCS ->setConduitDocumentation($column_documentation) ->setAliases(array('columnPHID', 'columns', 'columnPHIDs')) ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) - ->setIsReorderable(false) - ->setIsDefaultable(false) - ->setIsLockable(false) + ->setIsFormField(false) ->setCommentActionLabel(pht('Move on Workboard')) ->setCommentActionOrder(2000) ->setColumnMap($column_map), @@ -294,7 +292,7 @@ EODOCS ->setConduitDescription(pht('Change the parents of this task.')) ->setConduitTypeDescription(pht('List of parent task PHIDs.')) ->setUseEdgeTransactions(true) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $parent_type) ->setValue($parent_phids); @@ -306,7 +304,7 @@ EODOCS ->setConduitDescription(pht('Change the subtasks of this task.')) ->setConduitTypeDescription(pht('List of subtask PHIDs.')) ->setUseEdgeTransactions(true) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $subtask_type) ->setValue($parent_phids); diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 7c77e4acee..4b6c7707f6 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -290,7 +290,7 @@ final class ManiphestTransactionEditor $copy->setOwnerPHID($xaction->getNewValue()); break; default: - continue; + break; } } @@ -471,27 +471,28 @@ final class ManiphestTransactionEditor // be worth evaluating is to use "CASE". Another approach is to disable // strict mode for this query. - $extra_columns = array( - 'phid' => '""', - 'authorPHID' => '""', - 'status' => '""', - 'priority' => 0, - 'title' => '""', - 'description' => '""', - 'dateCreated' => 0, - 'dateModified' => 0, - 'mailKey' => '""', - 'viewPolicy' => '""', - 'editPolicy' => '""', - 'ownerOrdering' => '""', - 'spacePHID' => '""', - 'bridgedObjectPHID' => '""', - 'properties' => '""', - 'points' => 0, - 'subtype' => '""', - ); + $default_str = qsprintf($conn, '%s', ''); + $default_int = qsprintf($conn, '%d', 0); - $defaults = implode(', ', $extra_columns); + $extra_columns = array( + 'phid' => $default_str, + 'authorPHID' => $default_str, + 'status' => $default_str, + 'priority' => $default_int, + 'title' => $default_str, + 'description' => $default_str, + 'dateCreated' => $default_int, + 'dateModified' => $default_int, + 'mailKey' => $default_str, + 'viewPolicy' => $default_str, + 'editPolicy' => $default_str, + 'ownerOrdering' => $default_str, + 'spacePHID' => $default_str, + 'bridgedObjectPHID' => $default_str, + 'properties' => $default_str, + 'points' => $default_int, + 'subtype' => $default_str, + ); $sql = array(); $offset = 0; @@ -520,9 +521,9 @@ final class ManiphestTransactionEditor $sql[] = qsprintf( $conn, - '(%d, %Q, %f)', + '(%d, %LQ, %f)', $id, - $defaults, + $extra_columns, $subpriority); $offset++; @@ -531,10 +532,10 @@ final class ManiphestTransactionEditor foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn, - 'INSERT INTO %T (id, %Q, subpriority) VALUES %Q + 'INSERT INTO %T (id, %LC, subpriority) VALUES %LQ ON DUPLICATE KEY UPDATE subpriority = VALUES(subpriority)', $task->getTableName(), - implode(', ', array_keys($extra_columns)), + array_keys($extra_columns), $chunk); } diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 8a3a3d5d30..61308ad147 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -243,7 +243,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $where = $this->buildWhereClause($conn); - $group_column = ''; + $group_column = qsprintf($conn, ''); switch ($this->groupBy) { case self::GROUP_PROJECT: $group_column = qsprintf( @@ -633,10 +633,10 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { } if (!$subclause) { - return ''; + return qsprintf($conn, ''); } - return '('.implode(') OR (', $subclause).')'; + return qsprintf($conn, '%LO', $subclause); } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { @@ -768,7 +768,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $joins; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn) { $joined_multiple_rows = ($this->hasOpenParents !== null) || ($this->hasOpenSubtasks !== null) || @@ -782,13 +782,13 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { // task IDs. if ($joined_multiple_rows) { if ($joined_project_name) { - return 'GROUP BY task.phid, projectGroup.dst'; + return qsprintf($conn, 'GROUP BY task.phid, projectGroup.dst'); } else { - return 'GROUP BY task.phid'; + return qsprintf($conn, 'GROUP BY task.phid'); } - } else { - return ''; } + + return qsprintf($conn, ''); } diff --git a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php index 91b5c73249..5364a3d4fa 100644 --- a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php +++ b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php @@ -35,16 +35,25 @@ final class PhabricatorApplicationPolicyChangeTransaction $editor = $this->getEditor(); $content_source = $editor->getContentSource(); + + // NOTE: We allow applications to have custom edit policies, but they are + // currently stored in the Config application. The ability to edit Config + // values is always restricted to administrators, today. Empower this + // particular edit to punch through possible stricter policies, so normal + // users can change application configuration if the application allows + // them to do so. + PhabricatorConfigEditor::storeNewValue( - $user, + PhabricatorUser::getOmnipotentUser(), $config_entry, $current_value, - $content_source); + $content_source, + $user->getPHID()); } public function getTitle() { - $old = $this->renderPolicy($this->getOldValue()); - $new = $this->renderPolicy($this->getNewValue()); + $old = $this->renderApplicationPolicy($this->getOldValue()); + $new = $this->renderApplicationPolicy($this->getNewValue()); return pht( '%s changed the "%s" policy from "%s" to "%s".', @@ -55,8 +64,8 @@ final class PhabricatorApplicationPolicyChangeTransaction } public function getTitleForFeed() { - $old = $this->renderPolicy($this->getOldValue()); - $new = $this->renderPolicy($this->getNewValue()); + $old = $this->renderApplicationPolicy($this->getOldValue()); + $new = $this->renderApplicationPolicy($this->getNewValue()); return pht( '%s changed the "%s" policy for application %s from "%s" to "%s".', @@ -156,7 +165,7 @@ final class PhabricatorApplicationPolicyChangeTransaction return $errors; } - private function renderPolicy($name) { + private function renderApplicationPolicy($name) { $policies = $this->getAllPolicies(); if (empty($policies[$name])) { // Not a standard policy, check for a custom policy. diff --git a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php index 12fbc8ebd4..b76e63b5c0 100644 --- a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php +++ b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php @@ -43,11 +43,15 @@ final class PhabricatorApplicationUninstallTransaction $editor = $this->getEditor(); $content_source = $editor->getContentSource(); + + // Today, changing config requires "Administrator", but "Can Edit" on + // applications to let you uninstall them may be granted to any user. PhabricatorConfigEditor::storeNewValue( - $user, + PhabricatorUser::getOmnipotentUser(), $config_entry, $list, - $content_source); + $content_source, + $user->getPHID()); } public function getTitle() { diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index 6aaf4fb052..778e05b052 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -151,24 +151,6 @@ final class PhabricatorMetaMTAMailBody extends Phobject { return $this; } - /** - * Add a Herald section with a rule management URI and a transcript URI. - * - * @param string URI to rule transcripts. - * @return this - * @task compose - */ - public function addHeraldSection($xscript_uri) { - if (!PhabricatorEnv::getEnvConfig('metamta.herald.show-hints')) { - return $this; - } - - $this->addLinkSection( - pht('WHY DID I GET THIS EMAIL?'), - PhabricatorEnv::getProductionURI($xscript_uri)); - - return $this; - } /** * Add an attachment. diff --git a/src/applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php b/src/applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php index e8415c7675..5753802996 100644 --- a/src/applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php +++ b/src/applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php @@ -10,36 +10,15 @@ HEADER bass trout -WHY DID I GET THIS EMAIL? - http://test.com/xscript/ - EOTEXT; - $this->assertEmail($expect, true); + $this->assertEmail($expect); } - public function testBodyRenderNoHerald() { - $expect = <<assertEmail($expect, false); - } - - private function assertEmail($expect, $herald_hints) { - $env = PhabricatorEnv::beginScopedEnv(); - $env->overrideEnvConfig('phabricator.production-uri', 'http://test.com/'); - $env->overrideEnvConfig('metamta.herald.show-hints', $herald_hints); - + private function assertEmail($expect) { $body = new PhabricatorMetaMTAMailBody(); $body->addRawSection('salmon'); $body->addTextSection('HEADER', "bass\ntrout\n"); - $body->addHeraldSection('/xscript/'); $this->assertEqual($expect, $body->render()); } diff --git a/src/applications/multimeter/controller/MultimeterSampleController.php b/src/applications/multimeter/controller/MultimeterSampleController.php index 0023038185..da09641d22 100644 --- a/src/applications/multimeter/controller/MultimeterSampleController.php +++ b/src/applications/multimeter/controller/MultimeterSampleController.php @@ -52,8 +52,6 @@ final class MultimeterSampleController extends MultimeterController { } } - $where = '('.implode(') AND (', $where).')'; - $data = queryfx_all( $conn, 'SELECT *, @@ -61,13 +59,13 @@ final class MultimeterSampleController extends MultimeterController { SUM(sampleRate * resourceCost) AS totalCost, SUM(sampleRate * resourceCost) / SUM(sampleRate) AS averageCost FROM %T - WHERE %Q - GROUP BY %Q + WHERE %LA + GROUP BY %LC ORDER BY totalCost DESC, MAX(id) DESC LIMIT 100', $table->getTableName(), $where, - implode(', ', array_select_keys($group_map, $group))); + array_select_keys($group_map, $group)); $this->loadDimensions($data); $phids = array(); diff --git a/src/applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php index b5da9ecd5f..46fc514824 100644 --- a/src/applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php +++ b/src/applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php @@ -37,33 +37,33 @@ final class PhabricatorOAuthServerClientQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->creatorPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'creatorPHID IN (%Ls)', $this->creatorPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php index d2eee5685e..044cb8beda 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -159,7 +159,7 @@ EOTEXT ->setDescription(pht('Archive or enable the package.')) ->setTransactionType( PhabricatorOwnersPackageStatusTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setValue($object->getStatus()) ->setOptions($object->getStatusNameMap()), id(new PhabricatorCheckboxesEditField()) @@ -176,7 +176,7 @@ EOTEXT id(new PhabricatorConduitEditField()) ->setKey('paths.set') ->setLabel(pht('Paths')) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setTransactionType( PhabricatorOwnersPackagePathsTransaction::TRANSACTIONTYPE) ->setConduitDescription( diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index ff0665b344..6d6ccb2ed2 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -228,7 +228,7 @@ final class PhabricatorOwnersPackageQuery $repository_phid, $indexes); } - $where[] = implode(' OR ', $clauses); + $where[] = qsprintf($conn, '%LO', $clauses); } return $where; diff --git a/src/applications/packages/query/PhabricatorPackagesQuery.php b/src/applications/packages/query/PhabricatorPackagesQuery.php index 58b90599eb..e7e882bdad 100644 --- a/src/applications/packages/query/PhabricatorPackagesQuery.php +++ b/src/applications/packages/query/PhabricatorPackagesQuery.php @@ -32,7 +32,7 @@ abstract class PhabricatorPackagesQuery throw new PhabricatorEmptyQueryException(); } - return implode(' OR ', $parts); + return qsprintf($conn, '%LO', $parts); } } diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php index 5578a7c9f6..146565e87e 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -104,7 +104,7 @@ final class PhabricatorPasteEditEngine ->setLabel(pht('Status')) ->setTransactionType( PhabricatorPasteStatusTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setOptions(PhabricatorPaste::getStatusNameMap()) ->setDescription(pht('Active or archived status.')) ->setConduitDescription(pht('Active or archive the paste.')) diff --git a/src/applications/people/editor/PhabricatorUserEditEngine.php b/src/applications/people/editor/PhabricatorUserEditEngine.php index c547426b12..c4c1abf1e3 100644 --- a/src/applications/people/editor/PhabricatorUserEditEngine.php +++ b/src/applications/people/editor/PhabricatorUserEditEngine.php @@ -71,7 +71,7 @@ final class PhabricatorUserEditEngine ->setLabel(pht('Disabled')) ->setDescription(pht('Disable the user.')) ->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setConduitDescription(pht('Disable or enable the user.')) ->setConduitTypeDescription(pht('True to disable the user.')) ->setValue($object->getIsDisabled()), diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 35ff480833..131dc7c588 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -268,7 +268,7 @@ final class PhabricatorPeopleQuery 'user.username LIKE %>', $name_prefix); } - $where[] = '('.implode(' OR ', $parts).')'; + $where[] = qsprintf($conn, '%LO', $parts); } if ($this->emails !== null) { diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 78fe4566a0..5d75fe56b4 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -528,11 +528,14 @@ final class PhabricatorUser } public function loadPrimaryEmail() { + $email = new PhabricatorUserEmail(); + $conn = $email->establishConnection('r'); + return $this->loadOneRelative( - new PhabricatorUserEmail(), + $email, 'userPHID', 'getPHID', - '(isPrimary = 1)'); + qsprintf($conn, '(isPrimary = 1)')); } @@ -733,9 +736,9 @@ final class PhabricatorUser if ($sql) { queryfx( $conn_w, - 'INSERT INTO %T (userID, token) VALUES %Q', + 'INSERT INTO %T (userID, token) VALUES %LQ', $table, - implode(', ', $sql)); + $sql); } } @@ -1735,5 +1738,23 @@ final class PhabricatorUser return new PhutilOpaqueEnvelope($digest); } + public function newPasswordBlocklist( + PhabricatorUser $viewer, + PhabricatorAuthPasswordEngine $engine) { + + $list = array(); + $list[] = $this->getUsername(); + $list[] = $this->getRealName(); + + $emails = id(new PhabricatorUserEmail())->loadAllWhere( + 'userPHID = %s', + $this->getPHID()); + foreach ($emails as $email) { + $list[] = $email->getAddress(); + } + + return $list; + } + } diff --git a/src/applications/people/storage/PhabricatorUserCache.php b/src/applications/people/storage/PhabricatorUserCache.php index 2afb06a437..71b3e4816b 100644 --- a/src/applications/people/storage/PhabricatorUserCache.php +++ b/src/applications/people/storage/PhabricatorUserCache.php @@ -85,7 +85,7 @@ final class PhabricatorUserCache extends PhabricatorUserDAO { queryfx( $conn_w, 'INSERT INTO %T (userPHID, cacheIndex, cacheKey, cacheData, cacheType) - VALUES %Q + VALUES %LQ ON DUPLICATE KEY UPDATE cacheData = VALUES(cacheData), cacheType = VALUES(cacheType)', diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 9b6be308c4..e11dec6847 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -126,7 +126,7 @@ final class PhameBlogEditEngine ->setKey('status') ->setLabel(pht('Status')) ->setTransactionType(PhameBlogStatusTransaction::TRANSACTIONTYPE) - ->setIsConduitOnly(true) + ->setIsFormField(false) ->setOptions(PhameBlog::getStatusNameMap()) ->setDescription(pht('Active or archived status.')) ->setConduitDescription(pht('Active or archive the blog.')) diff --git a/src/applications/phlux/query/PhluxVariableQuery.php b/src/applications/phlux/query/PhluxVariableQuery.php index 82072485d4..75abd044d0 100644 --- a/src/applications/phlux/query/PhluxVariableQuery.php +++ b/src/applications/phlux/query/PhluxVariableQuery.php @@ -37,33 +37,33 @@ final class PhluxVariableQuery return $table->loadAllFromArray($rows); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->keys !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'variableKey IN (%Ls)', $this->keys); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } protected function getDefaultOrderVector() { diff --git a/src/applications/pholio/query/PholioImageQuery.php b/src/applications/pholio/query/PholioImageQuery.php index 8c722660cb..79ffdc56d7 100644 --- a/src/applications/pholio/query/PholioImageQuery.php +++ b/src/applications/pholio/query/PholioImageQuery.php @@ -61,40 +61,40 @@ final class PholioImageQuery return $images; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->mockIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'mockID IN (%Ld)', $this->mockIDs); } if ($this->obsolete !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'isObsolete = %d', $this->obsolete); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } protected function willFilterPage(array $images) { diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php index c91e5f0111..ee8291218c 100644 --- a/src/applications/phortune/query/PhortuneAccountQuery.php +++ b/src/applications/phortune/query/PhortuneAccountQuery.php @@ -42,20 +42,12 @@ final class PhortuneAccountQuery return $this; } + public function newResultObject() { + return new PhortuneAccount(); + } + protected function loadPage() { - $table = new PhortuneAccount(); - $conn = $table->establishConnection('r'); - - $rows = queryfx_all( - $conn, - 'SELECT a.* FROM %T a %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($rows); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $accounts) { @@ -73,39 +65,37 @@ final class PhortuneAccountQuery return $accounts; } - protected function buildWhereClause(AphrontDatabaseConnection $conn) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn); - - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( $conn, 'a.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, 'a.phid IN (%Ls)', $this->phids); } - if ($this->memberPHIDs) { + if ($this->memberPHIDs !== null) { $where[] = qsprintf( $conn, 'm.dst IN (%Ls)', $this->memberPHIDs); } - return $this->formatWhereClause($where); + return $where; } - protected function buildJoinClause(AphrontDatabaseConnection $conn) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); - if ($this->memberPHIDs) { + if ($this->memberPHIDs !== null) { $joins[] = qsprintf( $conn, 'LEFT JOIN %T m ON a.phid = m.src AND m.type = %d', @@ -113,11 +103,15 @@ final class PhortuneAccountQuery PhortuneAccountHasMemberEdgeType::EDGECONST); } - return implode(' ', $joins); + return $joins; } public function getQueryApplicationClass() { return 'PhabricatorPhortuneApplication'; } + protected function getPrimaryTableAlias() { + return 'a'; + } + } diff --git a/src/applications/phortune/query/PhortuneCartQuery.php b/src/applications/phortune/query/PhortuneCartQuery.php index 5009d3a685..0b3325b932 100644 --- a/src/applications/phortune/query/PhortuneCartQuery.php +++ b/src/applications/phortune/query/PhortuneCartQuery.php @@ -213,7 +213,7 @@ final class PhortuneCartQuery } } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/phortune/query/PhortuneChargeQuery.php b/src/applications/phortune/query/PhortuneChargeQuery.php index 6b11cbe95e..a7eda9d6a6 100644 --- a/src/applications/phortune/query/PhortuneChargeQuery.php +++ b/src/applications/phortune/query/PhortuneChargeQuery.php @@ -134,7 +134,7 @@ final class PhortuneChargeQuery $this->statuses); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/phortune/query/PhortuneMerchantQuery.php b/src/applications/phortune/query/PhortuneMerchantQuery.php index c91267b73c..b6cab7dbc2 100644 --- a/src/applications/phortune/query/PhortuneMerchantQuery.php +++ b/src/applications/phortune/query/PhortuneMerchantQuery.php @@ -28,20 +28,12 @@ final class PhortuneMerchantQuery return $this; } + public function newResultObject() { + return new PhortuneMerchant(); + } + protected function loadPage() { - $table = new PhortuneMerchant(); - $conn = $table->establishConnection('r'); - - $rows = queryfx_all( - $conn, - 'SELECT m.* FROM %T m %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($rows); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $merchants) { @@ -88,8 +80,8 @@ final class PhortuneMerchantQuery return $merchants; } - protected function buildWhereClause(AphrontDatabaseConnection $conn) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( @@ -112,13 +104,11 @@ final class PhortuneMerchantQuery $this->memberPHIDs); } - $where[] = $this->buildPagingClause($conn); - - return $this->formatWhereClause($where); + return $where; } - protected function buildJoinClause(AphrontDatabaseConnection $conn) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->memberPHIDs !== null) { $joins[] = qsprintf( @@ -128,11 +118,15 @@ final class PhortuneMerchantQuery PhortuneMerchantHasMemberEdgeType::EDGECONST); } - return implode(' ', $joins); + return $joins; } public function getQueryApplicationClass() { return 'PhabricatorPhortuneApplication'; } + protected function getPrimaryTableAlias() { + return 'm'; + } + } diff --git a/src/applications/phortune/query/PhortunePaymentProviderConfigQuery.php b/src/applications/phortune/query/PhortunePaymentProviderConfigQuery.php index d80b0e90d0..a850acec28 100644 --- a/src/applications/phortune/query/PhortunePaymentProviderConfigQuery.php +++ b/src/applications/phortune/query/PhortunePaymentProviderConfigQuery.php @@ -85,7 +85,7 @@ final class PhortunePaymentProviderConfigQuery $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/phortune/query/PhortuneProductQuery.php b/src/applications/phortune/query/PhortuneProductQuery.php index 99ba535585..30701d4e7b 100644 --- a/src/applications/phortune/query/PhortuneProductQuery.php +++ b/src/applications/phortune/query/PhortuneProductQuery.php @@ -105,12 +105,12 @@ final class PhortuneProductQuery PhabricatorHash::digestForIndex($ref)); } } - $where[] = implode(' OR ', $sql); + $where[] = qsprintf($conn, '%LO', $sql); } $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/phortune/query/PhortunePurchaseQuery.php b/src/applications/phortune/query/PhortunePurchaseQuery.php index 6e9e599240..275537c351 100644 --- a/src/applications/phortune/query/PhortunePurchaseQuery.php +++ b/src/applications/phortune/query/PhortunePurchaseQuery.php @@ -100,7 +100,7 @@ final class PhortunePurchaseQuery $this->cartPHIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/phortune/query/PhortuneSubscriptionQuery.php b/src/applications/phortune/query/PhortuneSubscriptionQuery.php index e8b39c0fee..6919e6a169 100644 --- a/src/applications/phortune/query/PhortuneSubscriptionQuery.php +++ b/src/applications/phortune/query/PhortuneSubscriptionQuery.php @@ -182,7 +182,7 @@ final class PhortuneSubscriptionQuery $this->statuses); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/phragment/query/PhragmentFragmentQuery.php b/src/applications/phragment/query/PhragmentFragmentQuery.php index d6dc67609d..56444217db 100644 --- a/src/applications/phragment/query/PhragmentFragmentQuery.php +++ b/src/applications/phragment/query/PhragmentFragmentQuery.php @@ -55,47 +55,47 @@ final class PhragmentFragmentQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->paths) { $where[] = qsprintf( - $conn_r, + $conn, 'path IN (%Ls)', $this->paths); } if ($this->leadingPath) { $where[] = qsprintf( - $conn_r, + $conn, 'path LIKE %>', $this->leadingPath); } if ($this->depths) { $where[] = qsprintf( - $conn_r, + $conn, 'depth IN (%Ld)', $this->depths); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } protected function didFilterPage(array $page) { diff --git a/src/applications/phragment/query/PhragmentFragmentVersionQuery.php b/src/applications/phragment/query/PhragmentFragmentVersionQuery.php index a874a8be1a..e95c3260a8 100644 --- a/src/applications/phragment/query/PhragmentFragmentVersionQuery.php +++ b/src/applications/phragment/query/PhragmentFragmentVersionQuery.php @@ -49,47 +49,47 @@ final class PhragmentFragmentVersionQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->fragmentPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'fragmentPHID IN (%Ls)', $this->fragmentPHIDs); } if ($this->sequences) { $where[] = qsprintf( - $conn_r, + $conn, 'sequence IN (%Ld)', $this->sequences); } if ($this->sequenceBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'sequence < %d', $this->sequenceBefore); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } protected function willFilterPage(array $page) { diff --git a/src/applications/phragment/query/PhragmentSnapshotChildQuery.php b/src/applications/phragment/query/PhragmentSnapshotChildQuery.php index 032fb0c49c..faa3493499 100644 --- a/src/applications/phragment/query/PhragmentSnapshotChildQuery.php +++ b/src/applications/phragment/query/PhragmentSnapshotChildQuery.php @@ -55,40 +55,40 @@ final class PhragmentSnapshotChildQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->snapshotPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'snapshotPHID IN (%Ls)', $this->snapshotPHIDs); } if ($this->fragmentPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'fragmentPHID IN (%Ls)', $this->fragmentPHIDs); } if ($this->fragmentVersionPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'fragmentVersionPHID IN (%Ls)', $this->fragmentVersionPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } protected function willFilterPage(array $page) { diff --git a/src/applications/phragment/query/PhragmentSnapshotQuery.php b/src/applications/phragment/query/PhragmentSnapshotQuery.php index d6f9d3422f..a4805650fc 100644 --- a/src/applications/phragment/query/PhragmentSnapshotQuery.php +++ b/src/applications/phragment/query/PhragmentSnapshotQuery.php @@ -43,40 +43,40 @@ final class PhragmentSnapshotQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->primaryFragmentPHIDs) { + if ($this->primaryFragmentPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'primaryFragmentPHID IN (%Ls)', $this->primaryFragmentPHIDs); } - if ($this->names) { + if ($this->names !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'name IN (%Ls)', $this->names); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } protected function willFilterPage(array $page) { diff --git a/src/applications/phrequent/query/PhrequentUserTimeQuery.php b/src/applications/phrequent/query/PhrequentUserTimeQuery.php index d0d1160df0..cf5122c020 100644 --- a/src/applications/phrequent/query/PhrequentUserTimeQuery.php +++ b/src/applications/phrequent/query/PhrequentUserTimeQuery.php @@ -116,7 +116,7 @@ final class PhrequentUserTimeQuery $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getOrderableColumns() { @@ -177,9 +177,9 @@ final class PhrequentUserTimeQuery $preempting_events = queryfx_all( $conn_r, - 'SELECT * FROM %T WHERE %Q ORDER BY dateStarted ASC, id ASC', + 'SELECT * FROM %T WHERE %LO ORDER BY dateStarted ASC, id ASC', $usertime->getTableName(), - implode(' OR ', $preempt)); + $preempt); $preempting_events = $usertime->loadAllFromArray($preempting_events); $preempting_events = mgroup($preempting_events, 'getUserPHID'); diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index 8a0fa325ae..5f508ad804 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -309,10 +309,14 @@ final class PhrictionDocumentQuery $max); } - $path_clauses[] = '('.implode(') AND (', $parts).')'; + if ($parts) { + $path_clauses[] = qsprintf($conn, '%LA', $parts); + } } - $where[] = '('.implode(') OR (', $path_clauses).')'; + if ($path_clauses) { + $where[] = qsprintf($conn, '%LO', $path_clauses); + } } return $where; diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index bb048f22ea..15e0c5d075 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -203,7 +203,7 @@ final class PhabricatorProjectBoardViewController // with the column filter. If the user currently has constraints on the // board, we want to add a new column or project constraint, not // completely replace the constraints. - $saved_query = clone $saved; + $saved_query = $saved->newCopy(); if ($query_column->getProxyPHID()) { $project_phids = $saved_query->getParameter('projectPHIDs'); diff --git a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php index 248677fbec..863f8c0e8c 100644 --- a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php +++ b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php @@ -297,9 +297,9 @@ final class PhabricatorBoardLayoutEngine extends Phobject { queryfx( $conn_w, 'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID) - VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)', + VALUES %LQ ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)', $table->getTableName(), - implode(', ', $pairs)); + $pairs); } foreach ($adds as $position) { diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 5eb6b49b87..1c84932656 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -287,13 +287,13 @@ final class PhabricatorProjectEditEngine // Show this on the web UI when creating a project, but not when editing // one. It is always available via Conduit. - $conduit_only = !$this->getIsCreate(); + $show_field = (bool)$this->getIsCreate(); $members_field = id(new PhabricatorUsersEditField()) ->setKey('members') ->setAliases(array('memberPHIDs')) ->setLabel(pht('Initial Members')) - ->setIsConduitOnly($conduit_only) + ->setIsFormField($show_field) ->setUseEdgeTransactions(true) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue( diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 98713078fb..9b051c00dd 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -514,7 +514,7 @@ final class PhabricatorProjectQuery 'name LIKE %>', $name_prefix); } - $where[] = '('.implode(' OR ', $parts).')'; + $where[] = qsprintf($conn, '%LO', $parts); } if ($this->icons !== null) { @@ -557,7 +557,7 @@ final class PhabricatorProjectQuery $ancestor_path['projectDepth']); } - $where[] = '('.implode(' OR ', $sql).')'; + $where[] = qsprintf($conn, '%LO', $sql); $where[] = qsprintf( $conn, diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index a049354ad7..f134b2c633 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -467,7 +467,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn_w, - 'INSERT INTO %T (projectID, token) VALUES %Q', + 'INSERT INTO %T (projectID, token) VALUES %LQ', $table, $chunk); } diff --git a/src/applications/releeph/query/ReleephBranchQuery.php b/src/applications/releeph/query/ReleephBranchQuery.php index 9d7c884012..97e47bdcaf 100644 --- a/src/applications/releeph/query/ReleephBranchQuery.php +++ b/src/applications/releeph/query/ReleephBranchQuery.php @@ -103,26 +103,26 @@ final class ReleephBranchQuery return $branches; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->productIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'releephProjectID IN (%Ld)', $this->productIDs); } @@ -133,16 +133,16 @@ final class ReleephBranchQuery break; case self::STATUS_OPEN: $where[] = qsprintf( - $conn_r, + $conn, 'isActive = 1'); break; default: throw new Exception(pht("Unknown status constant '%s'!", $status)); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/releeph/query/ReleephProductQuery.php b/src/applications/releeph/query/ReleephProductQuery.php index acfc39c1c2..c039950379 100644 --- a/src/applications/releeph/query/ReleephProductQuery.php +++ b/src/applications/releeph/query/ReleephProductQuery.php @@ -83,40 +83,40 @@ final class ReleephProductQuery return $projects; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->active !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'isActive = %d', (int)$this->active); } if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ls)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->repositoryPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getOrderableColumns() { diff --git a/src/applications/releeph/query/ReleephRequestQuery.php b/src/applications/releeph/query/ReleephRequestQuery.php index 7d4f19e624..3042260387 100644 --- a/src/applications/releeph/query/ReleephRequestQuery.php +++ b/src/applications/releeph/query/ReleephRequestQuery.php @@ -147,54 +147,54 @@ final class ReleephRequestQuery return $requests; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->branchIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'branchID IN (%Ld)', $this->branchIDs); } if ($this->requestedCommitPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'requestCommitPHID IN (%Ls)', $this->requestedCommitPHIDs); } if ($this->requestorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'requestUserPHID IN (%Ls)', $this->requestorPHIDs); } if ($this->requestedObjectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'requestedObjectPHID IN (%Ls)', $this->requestedObjectPHIDs); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function getKeepStatusConstants() { diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 882c1a11fa..0955d54b98 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -14,29 +14,6 @@ final class PhabricatorRepositoryEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorRepositoryTransaction::TYPE_VCS; - $types[] = PhabricatorRepositoryTransaction::TYPE_ACTIVATE; - $types[] = PhabricatorRepositoryTransaction::TYPE_NAME; - $types[] = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorRepositoryTransaction::TYPE_ENCODING; - $types[] = PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH; - $types[] = PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY; - $types[] = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY; - $types[] = PhabricatorRepositoryTransaction::TYPE_UUID; - $types[] = PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH; - $types[] = PhabricatorRepositoryTransaction::TYPE_NOTIFY; - $types[] = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE; - $types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY; - $types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS; - $types[] = PhabricatorRepositoryTransaction::TYPE_ENORMOUS; - $types[] = PhabricatorRepositoryTransaction::TYPE_SLUG; - $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; - $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; - $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; - $types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; - $types[] = PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS; - $types[] = PhabricatorRepositoryTransaction::TYPE_CALLSIGN; - $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -44,510 +21,6 @@ final class PhabricatorRepositoryEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorRepositoryTransaction::TYPE_VCS: - return $object->getVersionControlSystem(); - case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: - return $object->isTracked(); - case PhabricatorRepositoryTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: - return $object->getDetail('description'); - case PhabricatorRepositoryTransaction::TYPE_ENCODING: - return $object->getDetail('encoding'); - case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: - return $object->getDetail('default-branch'); - case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: - return array_keys($object->getDetail('branch-filter', array())); - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: - return array_keys($object->getDetail('close-commits-filter', array())); - case PhabricatorRepositoryTransaction::TYPE_UUID: - return $object->getUUID(); - case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: - return $object->getDetail('svn-subpath'); - case PhabricatorRepositoryTransaction::TYPE_NOTIFY: - return (int)!$object->getDetail('herald-disabled'); - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: - return (int)!$object->getDetail('disable-autoclose'); - case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: - return $object->getPushPolicy(); - case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: - return $object->shouldAllowDangerousChanges(); - case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: - return $object->shouldAllowEnormousChanges(); - case PhabricatorRepositoryTransaction::TYPE_SLUG: - return $object->getRepositorySlug(); - case PhabricatorRepositoryTransaction::TYPE_SERVICE: - return $object->getAlmanacServicePHID(); - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: - return $object->getSymbolLanguages(); - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: - return $object->getSymbolSources(); - case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: - return $object->getDetail('staging-uri'); - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: - return $object->getDetail('automation.blueprintPHIDs', array()); - case PhabricatorRepositoryTransaction::TYPE_CALLSIGN: - return $object->getCallsign(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: - case PhabricatorRepositoryTransaction::TYPE_NAME: - case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: - case PhabricatorRepositoryTransaction::TYPE_ENCODING: - case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: - case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: - case PhabricatorRepositoryTransaction::TYPE_UUID: - case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: - case PhabricatorRepositoryTransaction::TYPE_VCS: - case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: - case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: - case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: - case PhabricatorRepositoryTransaction::TYPE_SERVICE: - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: - case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: - return $xaction->getNewValue(); - case PhabricatorRepositoryTransaction::TYPE_SLUG: - case PhabricatorRepositoryTransaction::TYPE_CALLSIGN: - $name = $xaction->getNewValue(); - if (strlen($name)) { - return $name; - } - return null; - case PhabricatorRepositoryTransaction::TYPE_NOTIFY: - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: - return (int)$xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorRepositoryTransaction::TYPE_VCS: - $object->setVersionControlSystem($xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: - $active = $xaction->getNewValue(); - - // The first time a repository is activated, clear the "new repository" - // flag so we stop showing setup hints. - if ($active) { - $object->setDetail('newly-initialized', false); - } - - $object->setDetail('tracking-enabled', $active); - break; - case PhabricatorRepositoryTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: - $object->setDetail('description', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: - $object->setDetail('default-branch', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: - $object->setDetail( - 'branch-filter', - array_fill_keys($xaction->getNewValue(), true)); - break; - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: - $object->setDetail( - 'close-commits-filter', - array_fill_keys($xaction->getNewValue(), true)); - break; - case PhabricatorRepositoryTransaction::TYPE_UUID: - $object->setUUID($xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: - $object->setDetail('svn-subpath', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_NOTIFY: - $object->setDetail('herald-disabled', (int)!$xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: - $object->setDetail('disable-autoclose', (int)!$xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: - return $object->setPushPolicy($xaction->getNewValue()); - case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: - $object->setDetail('allow-dangerous-changes', $xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: - $object->setDetail('allow-enormous-changes', $xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_SLUG: - $object->setRepositorySlug($xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_SERVICE: - $object->setAlmanacServicePHID($xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: - $object->setDetail('symbol-languages', $xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: - $object->setDetail('symbol-sources', $xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: - $object->setDetail('staging-uri', $xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: - $object->setDetail( - 'automation.blueprintPHIDs', - $xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_CALLSIGN: - $object->setCallsign($xaction->getNewValue()); - return; - case PhabricatorRepositoryTransaction::TYPE_ENCODING: - $object->setDetail('encoding', $xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: - DrydockAuthorization::applyAuthorizationChanges( - $this->getActor(), - $object->getPHID(), - $xaction->getOldValue(), - $xaction->getNewValue()); - break; - } - - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: - case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: - foreach ($xactions as $xaction) { - foreach ($xaction->getNewValue() as $pattern) { - // Check for invalid regular expressions. - $regexp = PhabricatorRepository::extractBranchRegexp($pattern); - if ($regexp !== null) { - $ok = @preg_match($regexp, ''); - if ($ok === false) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Expression "%s" is not a valid regular expression. Note '. - 'that you must include delimiters.', - $regexp), - $xaction); - $errors[] = $error; - continue; - } - } - - // Check for formatting mistakes like `regex(...)` instead of - // `regexp(...)`. - $matches = null; - if (preg_match('/^([^(]+)\\(.*\\)\z/', $pattern, $matches)) { - switch ($matches[1]) { - case 'regexp': - break; - default: - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Matching function "%s(...)" is not recognized. Valid '. - 'functions are: regexp(...).', - $matches[1]), - $xaction); - $errors[] = $error; - break; - } - } - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: - foreach ($xactions as $xaction) { - $old = nonempty($xaction->getOldValue(), array()); - $new = nonempty($xaction->getNewValue(), array()); - - $add = array_diff($new, $old); - - $invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer( - $this->getActor(), - $add); - if ($invalid) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Some of the selected automation blueprints are invalid '. - 'or restricted: %s.', - implode(', ', $invalid)), - $xaction); - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_VCS: - $vcs_map = PhabricatorRepositoryType::getAllRepositoryTypes(); - $current_vcs = $object->getVersionControlSystem(); - - if (!$this->getIsNewObject()) { - foreach ($xactions as $xaction) { - if ($xaction->getNewValue() == $current_vcs) { - continue; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Immutable'), - pht( - 'You can not change the version control system an existing '. - 'repository uses. It can only be set when a repository is '. - 'first created.'), - $xaction); - } - } else { - $value = $object->getVersionControlSystem(); - foreach ($xactions as $xaction) { - $value = $xaction->getNewValue(); - - if (empty($vcs_map[$value])) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Specified version control system must be a VCS '. - 'recognized by Phabricator: %s.', - implode(', ', array_keys($vcs_map))), - $xaction); - } - } - - if (!strlen($value)) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht( - 'When creating a repository, you must specify a valid '. - 'underlying version control system: %s.', - implode(', ', array_keys($vcs_map))), - nonempty(last($xactions), null)); - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Repository name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - - case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: - $status_map = PhabricatorRepository::getStatusMap(); - foreach ($xactions as $xaction) { - $status = $xaction->getNewValue(); - if (empty($status_map[$status])) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Repository status "%s" is not valid.', - $status), - $xaction); - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_ENCODING: - foreach ($xactions as $xaction) { - // Make sure the encoding is valid by converting to UTF-8. This tests - // that the user has mbstring installed, and also that they didn't - // type a garbage encoding name. Note that we're converting from - // UTF-8 to the target encoding, because mbstring is fine with - // converting from a nonsense encoding. - $encoding = $xaction->getNewValue(); - if (!strlen($encoding)) { - continue; - } - - try { - phutil_utf8_convert('.', $encoding, 'UTF-8'); - } catch (Exception $ex) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Repository encoding "%s" is not valid: %s', - $encoding, - $ex->getMessage()), - $xaction); - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_SLUG: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - if (!strlen($new)) { - continue; - } - - if ($new === $old) { - continue; - } - - try { - PhabricatorRepository::assertValidRepositorySlug($new); - } catch (Exception $ex) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $ex->getMessage(), - $xaction); - continue; - } - - $other = id(new PhabricatorRepositoryQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withSlugs(array($new)) - ->executeOne(); - if ($other && ($other->getID() !== $object->getID())) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht( - 'The selected repository short name is already in use by '. - 'another repository. Choose a unique short name.'), - $xaction); - continue; - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_CALLSIGN: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - if (!strlen($new)) { - continue; - } - - if ($new === $old) { - continue; - } - - try { - PhabricatorRepository::assertValidCallsign($new); - } catch (Exception $ex) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $ex->getMessage(), - $xaction); - continue; - } - - $other = id(new PhabricatorRepositoryQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withCallsigns(array($new)) - ->executeOne(); - if ($other && ($other->getID() !== $object->getID())) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht( - 'The selected callsign ("%s") is already in use by another '. - 'repository. Choose a unique callsign.', - $new), - $xaction); - continue; - } - } - break; - - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: - foreach ($xactions as $xaction) { - $old = $object->getSymbolSources(); - $new = $xaction->getNewValue(); - - // If the viewer is adding new repositories, make sure they are - // valid and visible. - $add = array_diff($new, $old); - if (!$add) { - continue; - } - - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($add) - ->execute(); - $repositories = mpull($repositories, null, 'getPHID'); - - foreach ($add as $phid) { - if (isset($repositories[$phid])) { - continue; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Repository ("%s") does not exist, or you do not have '. - 'permission to see it.', - $phid), - $xaction); - break; - } - } - break; - - } - - return $errors; - } - protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php index 7178564067..2e2c3fcc92 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php @@ -117,7 +117,8 @@ final class PhabricatorRepositoryManagementClusterizeWorkflow $xactions = array(); $xactions[] = id(new PhabricatorRepositoryTransaction()) - ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_SERVICE) + ->setTransactionType( + PhabricatorRepositoryServiceTransaction::TRANSACTIONTYPE) ->setNewValue($service_phid); id(new PhabricatorRepositoryEditor()) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php index 671287c3d6..e201ef40fc 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php @@ -161,7 +161,7 @@ final class PhabricatorRepositoryManagementParentsWorkflow foreach (PhabricatorLiskDAO::chunkSQL($delete_sql) as $chunk) { queryfx( $conn_w, - 'DELETE FROM %T WHERE childCommitID IN (%Q)', + 'DELETE FROM %T WHERE childCommitID IN (%LQ)', PhabricatorRepository::TABLE_PARENTS, $chunk); } @@ -169,7 +169,7 @@ final class PhabricatorRepositoryManagementParentsWorkflow foreach (PhabricatorLiskDAO::chunkSQL($insert_sql) as $chunk) { queryfx( $conn_w, - 'INSERT INTO %T (childCommitID, parentCommitID) VALUES %Q', + 'INSERT INTO %T (childCommitID, parentCommitID) VALUES %LQ', PhabricatorRepository::TABLE_PARENTS, $chunk); } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php index 71076314da..4c4eed85b9 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php @@ -120,33 +120,71 @@ final class PhabricatorRepositoryManagementThawWorkflow $repository->getDisplayName())); } - $bindings = $service->getActiveBindings(); - $bindings = mpull($bindings, null, 'getDevicePHID'); - if (empty($bindings[$device->getPHID()])) { - throw new PhutilArgumentUsageException( - pht( - 'Repository "%s" has no active binding to device "%s". Only '. - 'actively bound devices can be promoted or demoted.', - $repository->getDisplayName(), - $device->getName())); - } - - $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions( - $repository->getPHID()); - - $versions = mpull($versions, null, 'getDevicePHID'); - $versions = array_select_keys($versions, array_keys($bindings)); - - if ($versions && $promote) { - throw new PhutilArgumentUsageException( - pht( - 'Unable to promote "%s" for repository "%s": the leaders for '. - 'this cluster are not ambiguous.', - $device->getName(), - $repository->getDisplayName())); - } - if ($promote) { + // You can only promote active devices. (You may demote active or + // inactive devices.) + $bindings = $service->getActiveBindings(); + $bindings = mpull($bindings, null, 'getDevicePHID'); + if (empty($bindings[$device->getPHID()])) { + throw new PhutilArgumentUsageException( + pht( + 'Repository "%s" has no active binding to device "%s". Only '. + 'actively bound devices can be promoted.', + $repository->getDisplayName(), + $device->getName())); + } + + $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions( + $repository->getPHID()); + $versions = mpull($versions, null, 'getDevicePHID'); + + // Before we promote, make sure there are no outstanding versions on + // devices with inactive bindings. If there are, you need to demote + // these first. + $inactive = array(); + foreach ($versions as $device_phid => $version) { + if (isset($bindings[$device_phid])) { + continue; + } + $inactive[$device_phid] = $version; + } + + if ($inactive) { + $handles = $viewer->loadHandles(array_keys($inactive)); + + $handle_list = iterator_to_array($handles); + $handle_list = mpull($handle_list, 'getName'); + $handle_list = implode(', ', $handle_list); + + throw new PhutilArgumentUsageException( + pht( + 'Repository "%s" has versions on inactive devices. Demote '. + '(or reactivate) these devices before promoting a new '. + 'leader: %s.', + $repository->getDisplayName(), + $handle_list)); + } + + // Now, make sure there are no outstanding versions on devices with + // active bindings. These also need to be demoted (or promoting is a + // mistake or already happened). + $active = array_select_keys($versions, array_keys($bindings)); + if ($active) { + $handles = $viewer->loadHandles(array_keys($active)); + + $handle_list = iterator_to_array($handles); + $handle_list = mpull($handle_list, 'getName'); + $handle_list = implode(', ', $handle_list); + + throw new PhutilArgumentUsageException( + pht( + 'Unable to promote "%s" for repository "%s" because this '. + 'cluster already has one or more unambiguous leaders: %s.', + $device->getName(), + $repository->getDisplayName(), + $handle_list)); + } + PhabricatorRepositoryWorkingCopyVersion::updateVersion( $repository->getPHID(), $device->getPHID(), diff --git a/src/applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php b/src/applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php new file mode 100644 index 0000000000..ade9560a93 --- /dev/null +++ b/src/applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php @@ -0,0 +1,39 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $event = $objects[$phid]; + + $handle->setName(pht('Sync Event %d', $event->getID())); + } + } + +} diff --git a/src/applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php b/src/applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php index 87b2e44740..271d8a9c14 100644 --- a/src/applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php @@ -162,6 +162,9 @@ final class PhabricatorRepositoryPushLogSearchEngine id(new PhabricatorStringExportField()) ->setKey('resultDetails') ->setLabel(pht('Result Details')), + id(new PhabricatorIntExportField()) + ->setKey('hostWait') + ->setLabel(pht('Host Wait (us)')), id(new PhabricatorIntExportField()) ->setKey('writeWait') ->setLabel(pht('Write Wait (us)')), @@ -169,8 +172,8 @@ final class PhabricatorRepositoryPushLogSearchEngine ->setKey('readWait') ->setLabel(pht('Read Wait (us)')), id(new PhabricatorIntExportField()) - ->setKey('hostWait') - ->setLabel(pht('Host Wait (us)')), + ->setKey('hookWait') + ->setLabel(pht('Hook Wait (us)')), ); if ($viewer->getIsAdmin()) { @@ -251,9 +254,10 @@ final class PhabricatorRepositoryPushLogSearchEngine 'result' => $result, 'resultName' => $result_name, 'resultDetails' => $event->getRejectDetails(), + 'hostWait' => $event->getHostWait(), 'writeWait' => $event->getWriteWait(), 'readWait' => $event->getReadWait(), - 'hostWait' => $event->getHostWait(), + 'hookWait' => $event->getHookWait(), ); if ($viewer->getIsAdmin()) { diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index e1246cfc3e..5271a040ef 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -491,10 +491,10 @@ final class PhabricatorRepositoryQuery protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { $parts = parent::buildSelectClauseParts($conn); - $parts[] = 'r.*'; + $parts[] = qsprintf($conn, 'r.*'); if ($this->shouldJoinSummaryTable()) { - $parts[] = 's.*'; + $parts[] = qsprintf($conn, 's.*'); } return $parts; @@ -513,8 +513,8 @@ final class PhabricatorRepositoryQuery if ($this->shouldJoinURITable()) { $joins[] = qsprintf( $conn, - 'LEFT JOIN %T uri ON r.phid = uri.repositoryPHID', - id(new PhabricatorRepositoryURIIndex())->getTableName()); + 'LEFT JOIN %R uri ON r.phid = uri.repositoryPHID', + new PhabricatorRepositoryURIIndex()); } return $joins; @@ -639,7 +639,7 @@ final class PhabricatorRepositoryQuery $this->slugIdentifiers); } - $where = array('('.implode(' OR ', $identifier_clause).')'); + $where[] = qsprintf($conn, '%LO', $identifier_clause); } if ($this->types) { diff --git a/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php b/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php new file mode 100644 index 0000000000..542cb5cdc0 --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php @@ -0,0 +1,115 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withRepositoryPHIDs(array $repository_phids) { + $this->repositoryPHIDs = $repository_phids; + return $this; + } + + public function withEpochBetween($min, $max) { + $this->epochMin = $min; + $this->epochMax = $max; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositorySyncEvent(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function willFilterPage(array $events) { + $repository_phids = mpull($events, 'getRepositoryPHID'); + $repository_phids = array_filter($repository_phids); + + if ($repository_phids) { + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($repository_phids) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + } else { + $repositories = array(); + } + + foreach ($events as $key => $event) { + $phid = $event->getRepositoryPHID(); + + if (empty($repositories[$phid])) { + unset($events[$key]); + $this->didRejectResult($event); + continue; + } + + $event->attachRepository($repositories[$phid]); + } + + return $events; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->epochMin !== null) { + $where[] = qsprintf( + $conn, + 'epoch >= %d', + $this->epochMin); + } + + if ($this->epochMax !== null) { + $where[] = qsprintf( + $conn, + 'epoch <= %d', + $this->epochMax); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 52824c2aa4..3b7918cd59 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1889,6 +1889,51 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } + /** + * Time limit for cloning or copying this repository. + * + * This limit is used to timeout operations like `git clone` or `git fetch` + * when doing intracluster synchronization, building working copies, etc. + * + * @return int Maximum number of seconds to spend copying this repository. + */ + public function getCopyTimeLimit() { + return $this->getDetail('limit.copy'); + } + + public function setCopyTimeLimit($limit) { + return $this->setDetail('limit.copy', $limit); + } + + public function getDefaultCopyTimeLimit() { + return phutil_units('15 minutes in seconds'); + } + + public function getEffectiveCopyTimeLimit() { + $limit = $this->getCopyTimeLimit(); + if ($limit) { + return $limit; + } + + return $this->getDefaultCopyTimeLimit(); + } + + public function getFilesizeLimit() { + return $this->getDetail('limit.filesize'); + } + + public function setFilesizeLimit($limit) { + return $this->setDetail('limit.filesize', $limit); + } + + public function getTouchLimit() { + return $this->getDetail('limit.touch'); + } + + public function setTouchLimit($limit) { + return $this->setDetail('limit.touch', $limit); + } + /** * Retrieve the service URI for the device hosting this repository. * diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php b/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php index 682b367926..ac97aa2bcf 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php @@ -19,6 +19,7 @@ final class PhabricatorRepositoryPushEvent protected $writeWait; protected $readWait; protected $hostWait; + protected $hookWait; private $repository = self::ATTACHABLE; private $logs = self::ATTACHABLE; @@ -41,6 +42,7 @@ final class PhabricatorRepositoryPushEvent 'writeWait' => 'uint64?', 'readWait' => 'uint64?', 'hostWait' => 'uint64?', + 'hookWait' => 'uint64?', ), self::CONFIG_KEY_SCHEMA => array( 'key_repository' => array( diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php index c2d3456da6..cfb5402c2a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php @@ -24,6 +24,8 @@ final class PhabricatorRepositoryPushLog const CHANGEFLAG_REWRITE = 8; const CHANGEFLAG_DANGEROUS = 16; const CHANGEFLAG_ENORMOUS = 32; + const CHANGEFLAG_OVERSIZED = 64; + const CHANGEFLAG_TOUCHES = 128; const REJECT_ACCEPT = 0; const REJECT_DANGEROUS = 1; @@ -31,6 +33,8 @@ final class PhabricatorRepositoryPushLog const REJECT_EXTERNAL = 3; const REJECT_BROKEN = 4; const REJECT_ENORMOUS = 5; + const REJECT_OVERSIZED = 6; + const REJECT_TOUCHES = 7; protected $repositoryPHID; protected $epoch; @@ -63,6 +67,8 @@ final class PhabricatorRepositoryPushLog self::CHANGEFLAG_REWRITE => pht('Rewrite'), self::CHANGEFLAG_DANGEROUS => pht('Dangerous'), self::CHANGEFLAG_ENORMOUS => pht('Enormous'), + self::CHANGEFLAG_OVERSIZED => pht('Oversized'), + self::CHANGEFLAG_TOUCHES => pht('Touches Too Many Paths'), ); } @@ -74,6 +80,8 @@ final class PhabricatorRepositoryPushLog self::REJECT_EXTERNAL => pht('Rejected: External Hook'), self::REJECT_BROKEN => pht('Rejected: Broken'), self::REJECT_ENORMOUS => pht('Rejected: Enormous'), + self::REJECT_OVERSIZED => pht('Rejected: Oversized File'), + self::REJECT_TOUCHES => pht('Rejected: Touches Too Many Paths'), ); } diff --git a/src/applications/repository/storage/PhabricatorRepositorySyncEvent.php b/src/applications/repository/storage/PhabricatorRepositorySyncEvent.php new file mode 100644 index 0000000000..dd5765985e --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositorySyncEvent.php @@ -0,0 +1,99 @@ + true, + self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'deviceVersion' => 'uint32?', + 'fromDeviceVersion' => 'uint32?', + 'resultType' => 'text32', + 'resultCode' => 'uint32', + 'syncWait' => 'uint64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_repository' => array( + 'columns' => array('repositoryPHID'), + ), + 'key_epoch' => array( + 'columns' => array('epoch'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getPHIDType() { + return PhabricatorRepositorySyncEventPHIDType::TYPECONST; + } + + public function attachRepository(PhabricatorRepository $repository) { + $this->repository = $repository; + return $this; + } + + public function getRepository() { + return $this->assertAttached($this->repository); + } + + public function setProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getRepository()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getRepository()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + "A repository's sync events are visible to users who can see the ". + "repository."); + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index 866800161e..85c354ba67 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -1,43 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_PUSH_POLICY: - case self::TYPE_SERVICE: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - case self::TYPE_SYMBOLS_SOURCES: - case self::TYPE_AUTOMATION_BLUEPRINTS: - if ($old) { - $phids = array_merge($phids, $old); - } - if ($new) { - $phids = array_merge($phids, $new); - } - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_REMOTE_URI: - case self::TYPE_SSH_LOGIN: - case self::TYPE_SSH_KEY: - case self::TYPE_SSH_KEYFILE: - case self::TYPE_HTTP_LOGIN: - case self::TYPE_HTTP_PASS: - // Hide null vs empty string changes. - return (!strlen($old) && !strlen($new)); - case self::TYPE_LOCAL_PATH: - case self::TYPE_NAME: - // Hide these on create, they aren't interesting and we have an - // explicit "create" transaction. - if (!strlen($old)) { - return true; - } - break; - } - - return parent::shouldHide(); - } - - public function getIcon() { - switch ($this->getTransactionType()) { - case self::TYPE_VCS: - return 'fa-plus'; - } - return parent::getIcon(); - } - - public function getColor() { - switch ($this->getTransactionType()) { - case self::TYPE_VCS: - return 'green'; - } - return parent::getIcon(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_VCS: - return pht( - '%s created this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ACTIVATE: - // TODO: Old versions of this transaction use a boolean value, but - // should be migrated. - $is_deactivate = - (!$new) || - ($new == PhabricatorRepository::STATUS_INACTIVE); - - if (!$is_deactivate) { - return pht( - '%s activated this repository.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s deactivated this repository.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_NAME: - return pht( - '%s renamed this repository from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description of this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ENCODING: - if (strlen($old) && !strlen($new)) { - return pht( - '%s removed the "%s" encoding configured for this repository.', - $this->renderHandleLink($author_phid), - $old); - } else if (strlen($new) && !strlen($old)) { - return pht( - '%s set the encoding for this repository to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s changed the repository encoding from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_DEFAULT_BRANCH: - if (!strlen($new)) { - return pht( - '%s removed "%s" as the default branch.', - $this->renderHandleLink($author_phid), - $old); - } else if (!strlen($old)) { - return pht( - '%s set the default branch to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s changed the default branch from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_TRACK_ONLY: - if (!$new) { - return pht( - '%s set this repository to track all branches.', - $this->renderHandleLink($author_phid)); - } else if (!$old) { - return pht( - '%s set this repository to track branches: %s.', - $this->renderHandleLink($author_phid), - implode(', ', $new)); - } else { - return pht( - '%s changed track branches from "%s" to "%s".', - $this->renderHandleLink($author_phid), - implode(', ', $old), - implode(', ', $new)); - } - break; - case self::TYPE_AUTOCLOSE_ONLY: - if (!$new) { - return pht( - '%s set this repository to autoclose on all branches.', - $this->renderHandleLink($author_phid)); - } else if (!$old) { - return pht( - '%s set this repository to autoclose on branches: %s.', - $this->renderHandleLink($author_phid), - implode(', ', $new)); - } else { - return pht( - '%s changed autoclose branches from "%s" to "%s".', - $this->renderHandleLink($author_phid), - implode(', ', $old), - implode(', ', $new)); - } - break; - case self::TYPE_UUID: - if (!strlen($new)) { - return pht( - '%s removed "%s" as the repository UUID.', - $this->renderHandleLink($author_phid), - $old); - } else if (!strlen($old)) { - return pht( - '%s set the repository UUID to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s changed the repository UUID from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_SVN_SUBPATH: - if (!strlen($new)) { - return pht( - '%s removed "%s" as the Import Only path.', - $this->renderHandleLink($author_phid), - $old); - } else if (!strlen($old)) { - return pht( - '%s set the repository to import only "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s changed the import path from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_NOTIFY: - if ($new) { - return pht( - '%s enabled notifications and publishing for this repository.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s disabled notifications and publishing for this repository.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_AUTOCLOSE: - if ($new) { - return pht( - '%s enabled autoclose for this repository.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s disabled autoclose for this repository.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_REMOTE_URI: - if (!strlen($old)) { - return pht( - '%s set the remote URI for this repository to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else if (!strlen($new)) { - return pht( - '%s removed the remote URI for this repository.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the remote URI for this repository from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_SSH_LOGIN: - return pht( - '%s updated the SSH login for this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_SSH_KEY: - return pht( - '%s updated the SSH key for this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_SSH_KEYFILE: - return pht( - '%s updated the SSH keyfile for this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_HTTP_LOGIN: - return pht( - '%s updated the HTTP login for this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_HTTP_PASS: - return pht( - '%s updated the HTTP password for this repository.', - $this->renderHandleLink($author_phid)); - case self::TYPE_LOCAL_PATH: - return pht( - '%s changed the local path from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_HOSTING: - if ($new) { - return pht( - '%s changed this repository to be hosted on Phabricator.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed this repository to track a remote elsewhere.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_PROTOCOL_HTTP: - return pht( - '%s changed the availability of this repository over HTTP from '. - '"%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_PROTOCOL_SSH: - return pht( - '%s changed the availability of this repository over SSH from '. - '"%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_PUSH_POLICY: - return pht( - '%s changed the push policy of this repository from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - case self::TYPE_DANGEROUS: - if ($new) { - return pht( - '%s disabled protection against dangerous changes.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s enabled protection against dangerous changes.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_ENORMOUS: - if ($new) { - return pht( - '%s disabled protection against enormous changes.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s enabled protection against enormous changes.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_SLUG: - if (strlen($old) && !strlen($new)) { - return pht( - '%s removed the short name of this repository.', - $this->renderHandleLink($author_phid)); - } else if (strlen($new) && !strlen($old)) { - return pht( - '%s set the short name of this repository to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s changed the short name of this repository from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_SERVICE: - if (strlen($old) && !strlen($new)) { - return pht( - '%s moved storage for this repository from %s to local.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old)); - } else if (!strlen($old) && strlen($new)) { - // TODO: Possibly, we should distinguish between automatic assignment - // on creation vs explicit adjustment. - return pht( - '%s set storage for this repository to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s moved storage for this repository from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - case self::TYPE_SYMBOLS_SOURCES: - return pht( - '%s changed symbol sources from %s to %s.', - $this->renderHandleLink($author_phid), - empty($old) ? pht('None') : $this->renderHandleList($old), - empty($new) ? pht('None') : $this->renderHandleList($new)); - - case self::TYPE_SYMBOLS_LANGUAGE: - return pht('%s changed indexed languages from %s to %s.', - $this->renderHandleLink($author_phid), - $old ? implode(', ', $old) : pht('Any'), - $new ? implode(', ', $new) : pht('Any')); - - case self::TYPE_STAGING_URI: - if (!$old) { - return pht( - '%s set "%s" as the staging area for this repository.', - $this->renderHandleLink($author_phid), - $new); - } else if (!$new) { - return pht( - '%s removed "%s" as the staging area for this repository.', - $this->renderHandleLink($author_phid), - $old); - } else { - return pht( - '%s changed the staging area for this repository from '. - '"%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - case self::TYPE_AUTOMATION_BLUEPRINTS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed %s automation blueprint(s), '. - 'added %s: %s; removed %s: %s.', - $this->renderHandleLink($author_phid), - new PhutilNumber(count($add) + count($rem)), - new PhutilNumber(count($add)), - $this->renderHandleList($add), - new PhutilNumber(count($rem)), - $this->renderHandleList($rem)); - } else if ($add) { - return pht( - '%s added %s automation blueprint(s): %s.', - $this->renderHandleLink($author_phid), - new PhutilNumber(count($add)), - $this->renderHandleList($add)); - } else { - return pht( - '%s removed %s automation blueprint(s): %s.', - $this->renderHandleLink($author_phid), - new PhutilNumber(count($rem)), - $this->renderHandleList($rem)); - } - - case self::TYPE_CALLSIGN: - if ($old === null) { - return pht( - '%s set the callsign for this repository to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else if ($new === null) { - return pht( - '%s removed the callsign ("%s") for this repository.', - $this->renderHandleLink($author_phid), - $old); - } else { - return pht( - '%s changed the callsign for this repository from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - } - - return parent::getTitle(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'PhabricatorRepositoryTransactionType'; } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryURIIndex.php b/src/applications/repository/storage/PhabricatorRepositoryURIIndex.php index 2cfa0c4471..3ef41f8089 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURIIndex.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURIIndex.php @@ -48,16 +48,16 @@ final class PhabricatorRepositoryURIIndex queryfx( $conn_w, - 'DELETE FROM %T WHERE repositoryPHID = %s', - $table->getTableName(), + 'DELETE FROM %R WHERE repositoryPHID = %s', + $table, $repository_phid); if ($sql) { queryfx( $conn_w, - 'INSERT INTO %T (repositoryPHID, repositoryURI) VALUES %Q', - $table->getTableName(), - implode(', ', $sql)); + 'INSERT INTO %R (repositoryPHID, repositoryURI) VALUES %LQ', + $table, + $sql); } $table->saveTransaction(); diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php index 6d79742a32..b544228561 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php @@ -70,9 +70,9 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker } queryfx( $conn_w, - 'INSERT IGNORE INTO %T (path, pathHash) VALUES %Q', + 'INSERT IGNORE INTO %T (path, pathHash) VALUES %LQ', PhabricatorRepository::TABLE_PATH, - implode(', ', $sql)); + $sql); } $result_map += self::lookupPaths($missing_paths); } @@ -151,7 +151,7 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker 'INSERT INTO %T (repositoryID, pathID, commitID, targetPathID, targetCommitID, changeType, fileType, isDirect, commitSequence) - VALUES %Q', + VALUES %LQ', PhabricatorRepository::TABLE_PATHCHANGE, $chunk); } diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php index 6725ddb1d1..6be2251f1e 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php @@ -463,9 +463,9 @@ final class PhabricatorRepositorySvnCommitChangeParserWorker $conn_w, 'INSERT INTO %T (repositoryID, parentID, svnCommit, pathID, existed, fileType) - VALUES %Q', + VALUES %LQ', PhabricatorRepository::TABLE_FILESYSTEM, - implode(', ', $sql_chunk)); + $sql_chunk); } } diff --git a/src/applications/repository/xaction/PhabricatorRepositoryActivateTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryActivateTransaction.php new file mode 100644 index 0000000000..31298c55aa --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryActivateTransaction.php @@ -0,0 +1,61 @@ +isTracked(); + } + + public function applyInternalEffects($object, $value) { + // The first time a repository is activated, clear the "new repository" + // flag so we stop showing setup hints. + if ($value) { + $object->setDetail('newly-initialized', false); + } + + $object->setDetail('tracking-enabled', $value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + // TODO: Old versions of this transaction use a boolean value, but + // should be migrated. + $is_deactivate = + (!$new) || + ($new == PhabricatorRepository::STATUS_INACTIVE); + + if (!$is_deactivate) { + return pht( + '%s activated this repository.', + $this->renderAuthor()); + } else { + return pht( + '%s deactivated this repository.', + $this->renderAuthor()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $status_map = PhabricatorRepository::getStatusMap(); + foreach ($xactions as $xaction) { + $status = $xaction->getNewValue(); + if (empty($status_map[$status])) { + $errors[] = $this->newInvalidError( + pht( + 'Repository status "%s" is not valid. Valid statuses are: %s.', + $status, + implode(', ', array_keys($status_map))), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php new file mode 100644 index 0000000000..feacc73c44 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php @@ -0,0 +1,42 @@ +getDetail('close-commits-filter', array())); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('close-commits-filter', array_fill_keys($value, true)); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!$new) { + return pht( + '%s set this repository to autoclose on all branches.', + $this->renderAuthor()); + } else if (!$old) { + return pht( + '%s set this repository to autoclose on branches: %s.', + $this->renderAuthor(), + $this->renderValue(implode(', ', $new))); + } else { + return pht( + '%s changed autoclose branches from %s to %s.', + $this->renderAuthor(), + $this->renderValue(implode(', ', $old)), + $this->renderValue(implode(', ', $new))); + } + } + + public function validateTransactions($object, array $xactions) { + return $this->validateRefList($object, $xactions); + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php new file mode 100644 index 0000000000..6ab677fb4a --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php @@ -0,0 +1,34 @@ +getDetail('disable-autoclose'); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('disable-autoclose', (int)!$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s enabled autoclose for this repository.', + $this->renderAuthor()); + } else { + return pht( + '%s disabled autoclose for this repository.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryBlueprintsTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryBlueprintsTransaction.php new file mode 100644 index 0000000000..aae164d706 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryBlueprintsTransaction.php @@ -0,0 +1,81 @@ +getDetail('automation.blueprintPHIDs', array()); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('automation.blueprintPHIDs', $value); + } + + public function applyExternalEffects($object, $value) { + DrydockAuthorization::applyAuthorizationChanges( + $this->getActor(), + $object->getPHID(), + $this->getOldValue(), + $this->getNewValue()); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s changed %s automation blueprint(s), '. + 'added %s: %s; removed %s: %s.', + $this->renderAuthor(), + new PhutilNumber(count($add) + count($rem)), + new PhutilNumber(count($add)), + $this->renderHandleList($add), + new PhutilNumber(count($rem)), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added %s automation blueprint(s): %s.', + $this->renderAuthor(), + new PhutilNumber(count($add)), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %s automation blueprint(s): %s.', + $this->renderAuthor(), + new PhutilNumber(count($rem)), + $this->renderHandleList($rem)); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = nonempty($xaction->getOldValue(), array()); + $new = nonempty($xaction->getNewValue(), array()); + + $add = array_diff($new, $old); + + $invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer( + $this->getActor(), + $add); + if ($invalid) { + $errors[] = $this->newInvalidError( + pht( + 'Some of the selected automation blueprints are invalid '. + 'or restricted: %s.', + implode(', ', $invalid)), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryCallsignTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryCallsignTransaction.php new file mode 100644 index 0000000000..ab13c6a571 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryCallsignTransaction.php @@ -0,0 +1,90 @@ +getCallsign(); + } + + public function generateNewValue($object, $value) { + if (strlen($value)) { + return $value; + } + + return null; + } + + public function applyInternalEffects($object, $value) { + $object->setCallsign($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!strlen($old)) { + return pht( + '%s set the callsign for this repository to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if (!strlen($new)) { + return pht( + '%s removed the callsign (%s) for this repository.', + $this->renderAuthor(), + $this->renderOldValue()); + } else { + return pht( + '%s changed the callsign for this repository from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + if (!strlen($new)) { + continue; + } + + if ($new === $old) { + continue; + } + + try { + PhabricatorRepository::assertValidCallsign($new); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + $ex->getMessage(), + $xaction); + continue; + } + + $other = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withCallsigns(array($new)) + ->executeOne(); + if ($other && ($other->getID() !== $object->getID())) { + $errors[] = $this->newError( + pht('Duplicate'), + pht( + 'The selected callsign ("%s") is already in use by another '. + 'repository. Choose a unique callsign.', + $new), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryCopyTimeLimitTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryCopyTimeLimitTransaction.php new file mode 100644 index 0000000000..6be3466ae7 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryCopyTimeLimitTransaction.php @@ -0,0 +1,77 @@ +getCopyTimeLimit(); + } + + public function generateNewValue($object, $value) { + if (!strlen($value)) { + return null; + } + + $value = (int)$value; + if (!$value) { + return null; + } + + return $value; + } + + public function applyInternalEffects($object, $value) { + $object->setCopyTimeLimit($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old && $new) { + return pht( + '%s changed the copy time limit for this repository from %s seconds '. + 'to %s seconds.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if ($new) { + return pht( + '%s set the copy time limit for this repository to %s seconds.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s reset the copy time limit (%s seconds) for this repository '. + 'to the default value.', + $this->renderAuthor(), + $this->renderOldValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if (!strlen($new)) { + continue; + } + + if (!preg_match('/^\d+\z/', $new)) { + $errors[] = $this->newInvalidError( + pht( + 'Unable to parse copy time limit, specify a positive number '. + 'of seconds.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryDangerousTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryDangerousTransaction.php new file mode 100644 index 0000000000..218e1d1182 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryDangerousTransaction.php @@ -0,0 +1,30 @@ +shouldAllowDangerousChanges(); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('allow-dangerous-changes', $value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s disabled protection against dangerous changes.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled protection against dangerous changes.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryDefaultBranchTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryDefaultBranchTransaction.php new file mode 100644 index 0000000000..23eebf60c8 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryDefaultBranchTransaction.php @@ -0,0 +1,39 @@ +getDetail('default-branch'); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('default-branch', $value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!strlen($new)) { + return pht( + '%s removed %s as the default branch.', + $this->renderAuthor(), + $this->renderOldValue()); + } else if (!strlen($old)) { + return pht( + '%s set the default branch to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s changed the default branch from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryDescriptionTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryDescriptionTransaction.php new file mode 100644 index 0000000000..9e86d80d06 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryDescriptionTransaction.php @@ -0,0 +1,49 @@ +getDetail('description'); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('description', $value); + } + + public function getTitle() { + return pht( + '%s updated the description for this repository.', + $this->renderAuthor()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO REPOSITORY DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryEncodingTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryEncodingTransaction.php new file mode 100644 index 0000000000..ef129da832 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryEncodingTransaction.php @@ -0,0 +1,68 @@ +getDetail('encoding'); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('encoding', $value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && !strlen($new)) { + return pht( + '%s removed the %s encoding configured for this repository.', + $this->renderAuthor(), + $this->renderOldValue()); + } else if (strlen($new) && !strlen($old)) { + return pht( + '%s set the encoding for this repository to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s changed the repository encoding from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + // Make sure the encoding is valid by converting to UTF-8. This tests + // that the user has mbstring installed, and also that they didn't + // type a garbage encoding name. Note that we're converting from + // UTF-8 to the target encoding, because mbstring is fine with + // converting from a nonsense encoding. + $encoding = $xaction->getNewValue(); + if (!strlen($encoding)) { + continue; + } + + try { + phutil_utf8_convert('.', $encoding, 'UTF-8'); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + pht( + 'Repository encoding "%s" is not valid: %s', + $encoding, + $ex->getMessage()), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php new file mode 100644 index 0000000000..aae18ba8ff --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php @@ -0,0 +1,30 @@ +shouldAllowEnormousChanges(); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('allow-enormous-changes', $value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s disabled protection against enormous changes.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled protection against enormous changes.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php new file mode 100644 index 0000000000..6bf74cc757 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php @@ -0,0 +1,78 @@ +getFilesizeLimit(); + } + + public function generateNewValue($object, $value) { + if (!strlen($value)) { + return null; + } + + $value = phutil_parse_bytes($value); + if (!$value) { + return null; + } + + return $value; + } + + public function applyInternalEffects($object, $value) { + $object->setFilesizeLimit($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old && $new) { + return pht( + '%s changed the filesize limit for this repository from %s bytes to '. + '%s bytes.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if ($new) { + return pht( + '%s set the filesize limit for this repository to %s bytes.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s removed the filesize limit (%s bytes) for this repository.', + $this->renderAuthor(), + $this->renderOldValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if (!strlen($new)) { + continue; + } + + try { + $value = phutil_parse_bytes($new); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + pht( + 'Unable to parse filesize limit: %s', + $ex->getMessage()), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryNameTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryNameTransaction.php new file mode 100644 index 0000000000..c168037c84 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryNameTransaction.php @@ -0,0 +1,35 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this repository from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Repositories must have a name.')); + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php new file mode 100644 index 0000000000..4ffe1918ff --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php @@ -0,0 +1,34 @@ +getDetail('herald-disabled'); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('herald-disabled', (int)!$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s enabled notifications and publishing for this repository.', + $this->renderAuthor()); + } else { + return pht( + '%s disabled notifications and publishing for this repository.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryPushPolicyTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryPushPolicyTransaction.php new file mode 100644 index 0000000000..1bd8ce9e47 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryPushPolicyTransaction.php @@ -0,0 +1,24 @@ +getPushPolicy(); + } + + public function applyInternalEffects($object, $value) { + $object->setPushPolicy($value); + } + + public function getTitle() { + return pht( + '%s changed the push policy of this repository from %s to %s.', + $this->renderAuthor(), + $this->renderOldPolicy(), + $this->renderNewPolicy()); + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositorySVNSubpathTransaction.php b/src/applications/repository/xaction/PhabricatorRepositorySVNSubpathTransaction.php new file mode 100644 index 0000000000..10de2d9980 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositorySVNSubpathTransaction.php @@ -0,0 +1,39 @@ +getDetail('svn-subpath'); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('svn-subpath', $value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!strlen($new)) { + return pht( + '%s removed %s as the "Import Only" path.', + $this->renderAuthor(), + $this->renderOldValue()); + } else if (!strlen($old)) { + return pht( + '%s set the repository "Import Only" path to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s changed the "Import Only" path from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryServiceTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryServiceTransaction.php new file mode 100644 index 0000000000..f6d89a61f5 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryServiceTransaction.php @@ -0,0 +1,59 @@ +getAlmanacServicePHID(); + } + + public function generateNewValue($object, $value) { + if (strlen($value)) { + return $value; + } + + return null; + } + + public function applyInternalEffects($object, $value) { + $object->setAlmanacServicePHID($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getOldValue(); + + if (strlen($old) && !strlen($new)) { + return pht( + '%s moved storage for this repository from %s to local.', + $this->renderAuthor(), + $this->renderOldHandle()); + } else if (!strlen($old) && strlen($new)) { + // TODO: Possibly, we should distinguish between automatic assignment + // on creation vs explicit adjustment. + return pht( + '%s set storage for this repository to %s.', + $this->renderAuthor(), + $this->renderNewHandle()); + } else { + return pht( + '%s moved storage for this repository from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + // TODO: This could use some validation, values should be valid Almanac + // services of appropriate types. It's only reachable via the CLI so it's + // difficult to get wrong in practice. + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositorySlugTransaction.php b/src/applications/repository/xaction/PhabricatorRepositorySlugTransaction.php new file mode 100644 index 0000000000..79e8f7121e --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositorySlugTransaction.php @@ -0,0 +1,88 @@ +getRepositorySlug(); + } + + public function generateNewValue($object, $value) { + if (strlen($value)) { + return $value; + } + + return null; + } + + public function applyInternalEffects($object, $value) { + $object->setRepositorySlug($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && !strlen($new)) { + return pht( + '%s removed the short name of this repository.', + $this->renderAuthor()); + } else if (strlen($new) && !strlen($old)) { + return pht( + '%s set the short name of this repository to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s changed the short name of this repository from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + if (!strlen($new)) { + continue; + } + + if ($new === $old) { + continue; + } + + try { + PhabricatorRepository::assertValidRepositorySlug($new); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + $ex->getMessage(), + $xaction); + continue; + } + + $other = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withSlugs(array($new)) + ->executeOne(); + if ($other && ($other->getID() !== $object->getID())) { + $errors[] = $this->newError( + pht('Duplicate'), + pht( + 'The selected repository short name is already in use by '. + 'another repository. Choose a unique short name.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryStagingURITransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryStagingURITransaction.php new file mode 100644 index 0000000000..4297d5e244 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryStagingURITransaction.php @@ -0,0 +1,68 @@ +getDetail('staging-uri'); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('staging-uri', $value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!strlen($old)) { + return pht( + '%s set %s as the staging area for this repository.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if (!strlen($new)) { + return pht( + '%s removed %s as the staging area for this repository.', + $this->renderAuthor(), + $this->renderOldValue()); + } else { + return pht( + '%s changed the staging area for this repository from '. + '%s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $old = $this->generateOldValue($object); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if (!strlen($new)) { + continue; + } + + if ($new === $old) { + continue; + } + + try { + PhabricatorRepository::assertValidRemoteURI($new); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + $ex->getMessage(), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositorySymbolLanguagesTransaction.php b/src/applications/repository/xaction/PhabricatorRepositorySymbolLanguagesTransaction.php new file mode 100644 index 0000000000..18653c78fd --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositorySymbolLanguagesTransaction.php @@ -0,0 +1,39 @@ +getSymbolLanguages(); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('symbol-languages', $value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old) { + $display_old = implode(', ', $old); + } else { + $display_old = pht('Any'); + } + + if ($new) { + $display_new = implode(', ', $new); + } else { + $display_new = pht('Any'); + } + + return pht( + '%s changed indexed languages from %s to %s.', + $this->renderAuthor(), + $this->renderValue($display_old), + $this->renderValue($display_new)); + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositorySymbolSourcesTransaction.php b/src/applications/repository/xaction/PhabricatorRepositorySymbolSourcesTransaction.php new file mode 100644 index 0000000000..781fad9d99 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositorySymbolSourcesTransaction.php @@ -0,0 +1,78 @@ +getSymbolSources(); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('symbol-sources', $value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old) { + $display_old = $this->renderHandleList($old); + } else { + $display_old = $this->renderValue(pht('None')); + } + + if ($new) { + $display_new = $this->renderHandleList($new); + } else { + $display_new = $this->renderValue(pht('None')); + } + + return pht( + '%s changed symbol sources from %s to %s.', + $this->renderAuthor(), + $display_old, + $display_new); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $object->getSymbolSources(); + $new = $xaction->getNewValue(); + + // If the viewer is adding new repositories, make sure they are + // valid and visible. + $add = array_diff($new, $old); + if (!$add) { + continue; + } + + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($add) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($add as $phid) { + if (isset($repositories[$phid])) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Repository ("%s") does not exist, or you do not have '. + 'permission to see it.', + $phid), + $xaction); + break; + } + } + + return $errors; + } + + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php new file mode 100644 index 0000000000..e3052d0894 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php @@ -0,0 +1,76 @@ +getTouchLimit(); + } + + public function generateNewValue($object, $value) { + if (!strlen($value)) { + return null; + } + + $value = (int)$value; + if (!$value) { + return null; + } + + return $value; + } + + public function applyInternalEffects($object, $value) { + $object->setTouchLimit($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old && $new) { + return pht( + '%s changed the touch limit for this repository from %s paths to '. + '%s paths.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if ($new) { + return pht( + '%s set the touch limit for this repository to %s paths.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s removed the touch limit (%s paths) for this repository.', + $this->renderAuthor(), + $this->renderOldValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if (!strlen($new)) { + continue; + } + + if (!preg_match('/^\d+\z/', $new)) { + $errors[] = $this->newInvalidError( + pht( + 'Unable to parse touch limit, specify a positive number of '. + 'paths.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryTrackOnlyTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryTrackOnlyTransaction.php new file mode 100644 index 0000000000..20ff1baf08 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryTrackOnlyTransaction.php @@ -0,0 +1,42 @@ +getDetail('branch-filter', array())); + } + + public function applyInternalEffects($object, $value) { + $object->setDetail('branch-filter', array_fill_keys($value, true)); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!$new) { + return pht( + '%s set this repository to track all branches.', + $this->renderAuthor()); + } else if (!$old) { + return pht( + '%s set this repository to track branches: %s.', + $this->renderAuthor(), + $this->renderValue(implode(', ', $new))); + } else { + return pht( + '%s changed tracked branches from %s to %s.', + $this->renderAuthor(), + $this->renderValue(implode(', ', $old)), + $this->renderValue(implode(', ', $new))); + } + } + + public function validateTransactions($object, array $xactions) { + return $this->validateRefList($object, $xactions); + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryTransactionType.php b/src/applications/repository/xaction/PhabricatorRepositoryTransactionType.php new file mode 100644 index 0000000000..6a2bd44feb --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryTransactionType.php @@ -0,0 +1,49 @@ +getNewValue() as $pattern) { + // Check for invalid regular expressions. + $regexp = PhabricatorRepository::extractBranchRegexp($pattern); + if ($regexp !== null) { + $ok = @preg_match($regexp, ''); + if ($ok === false) { + $errors[] = $this->newInvalidError( + pht( + 'Expression "%s" is not a valid regular expression. Note '. + 'that you must include delimiters.', + $regexp), + $xaction); + continue; + } + } + + // Check for formatting mistakes like `regex(...)` instead of + // `regexp(...)`. + $matches = null; + if (preg_match('/^([^(]+)\\(.*\\)\z/', $pattern, $matches)) { + switch ($matches[1]) { + case 'regexp': + break; + default: + $errors[] = $this->newInvalidError( + pht( + 'Matching function "%s(...)" is not recognized. Valid '. + 'functions are: regexp(...).', + $matches[1]), + $xaction); + break; + } + } + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php new file mode 100644 index 0000000000..d5944d5425 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php @@ -0,0 +1,66 @@ +getVersionControlSystem(); + } + + public function applyInternalEffects($object, $value) { + $object->setVersionControlSystem($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $vcs_map = PhabricatorRepositoryType::getAllRepositoryTypes(); + $current_vcs = $object->getVersionControlSystem(); + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + if ($xaction->getNewValue() == $current_vcs) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'You can not change the version control system an existing '. + 'repository uses. It can only be set when a repository is '. + 'first created.'), + $xaction); + } + + return $errors; + } + + $value = $object->getVersionControlSystem(); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + if (isset($vcs_map[$value])) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Specified version control system must be a VCS '. + 'recognized by Phabricator. Valid systems are: %s.', + implode(', ', array_keys($vcs_map))), + $xaction); + } + + if ($value === null) { + $errors[] = $this->newRequiredError( + pht( + 'When creating a repository, you must specify a valid '. + 'underlying version control system. Valid systems are: %s.', + implode(', ', array_keys($vcs_map)))); + } + + return $errors; + } +} diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index b808291a52..a89a017e85 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -103,6 +103,14 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } public function saveQuery(PhabricatorSavedQuery $query) { + if ($query->getID()) { + throw new Exception( + pht( + 'Query (with ID "%s") has already been saved. Queries are '. + 'immutable once saved.', + $query->getID())); + } + $query->setEngineClassName(get_class($this)); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); diff --git a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php index fe50518f3f..8647bc83eb 100644 --- a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php @@ -201,7 +201,7 @@ final class PhabricatorFerretFulltextEngineExtension foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn, - 'INSERT INTO %T (documentID, ngram) VALUES %Q', + 'INSERT INTO %T (documentID, ngram) VALUES %LQ', $engine->getNgramsTableName(), $chunk); } diff --git a/src/applications/search/index/PhabricatorIndexEngine.php b/src/applications/search/index/PhabricatorIndexEngine.php index 1dde3ce9ab..1e1781f169 100644 --- a/src/applications/search/index/PhabricatorIndexEngine.php +++ b/src/applications/search/index/PhabricatorIndexEngine.php @@ -141,10 +141,10 @@ final class PhabricatorIndexEngine extends Phobject { queryfx( $conn_w, 'INSERT INTO %T (objectPHID, extensionKey, version) - VALUES %Q + VALUES %LQ ON DUPLICATE KEY UPDATE version = VALUES(version)', $table->getTableName(), - implode(', ', $sql)); + $sql); } } diff --git a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php index 1a1124b4d8..3d87378849 100644 --- a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php @@ -123,7 +123,7 @@ final class PhabricatorSearchManagementNgramsWorkflow queryfx( $conn, 'INSERT IGNORE INTO %T (ngram, needsCollection) - VALUES %Q', + VALUES %LQ', $engine->getCommonNgramsTableName(), $chunk); } diff --git a/src/applications/search/ngrams/PhabricatorSearchNgrams.php b/src/applications/search/ngrams/PhabricatorSearchNgrams.php index 9ff8157e9e..fc55af5eb0 100644 --- a/src/applications/search/ngrams/PhabricatorSearchNgrams.php +++ b/src/applications/search/ngrams/PhabricatorSearchNgrams.php @@ -102,9 +102,9 @@ abstract class PhabricatorSearchNgrams if ($sql) { queryfx( $conn_w, - 'INSERT INTO %T (objectID, ngram) VALUES %Q', + 'INSERT INTO %T (objectID, ngram) VALUES %LQ', $this->getTableName(), - implode(', ', $sql)); + $sql); } return $this; diff --git a/src/applications/search/query/PhabricatorSavedQueryQuery.php b/src/applications/search/query/PhabricatorSavedQueryQuery.php index 623b001662..765c751940 100644 --- a/src/applications/search/query/PhabricatorSavedQueryQuery.php +++ b/src/applications/search/query/PhabricatorSavedQueryQuery.php @@ -37,33 +37,33 @@ final class PhabricatorSavedQueryQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->engineClassNames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'engineClassName IN (%Ls)', $this->engineClassNames); } if ($this->queryKeys !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'queryKey IN (%Ls)', $this->queryKeys); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildPagingClause($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } public function getQueryApplicationClass() { diff --git a/src/applications/search/storage/PhabricatorSavedQuery.php b/src/applications/search/storage/PhabricatorSavedQuery.php index 3f75027888..837f4fde42 100644 --- a/src/applications/search/storage/PhabricatorSavedQuery.php +++ b/src/applications/search/storage/PhabricatorSavedQuery.php @@ -63,6 +63,13 @@ final class PhabricatorSavedQuery extends PhabricatorSearchDAO return $this->assertAttachedKey($this->parameterMap, $key); } + public function newCopy() { + return id(new self()) + ->setParameters($this->getParameters()) + ->setQueryKey(null) + ->setEngineClassName($this->getEngineClassName()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php index c69a825c3a..62913b09e3 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php @@ -7,6 +7,10 @@ final class PhabricatorSlowvoteVoteController $viewer = $request->getViewer(); $id = $request->getURIData('id'); + if (!$request->isFormPost()) { + return id(new Aphront404Response()); + } + $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($viewer) ->withIDs(array($id)) @@ -16,69 +20,36 @@ final class PhabricatorSlowvoteVoteController if (!$poll) { return new Aphront404Response(); } + if ($poll->getIsClosed()) { return new Aphront400Response(); } $options = $poll->getOptions(); - $viewer_choices = $poll->getViewerChoices($viewer); + $options = mpull($options, null, 'getID'); - $old_votes = mpull($viewer_choices, null, 'getOptionID'); - - if ($request->isAjax()) { - $vote = $request->getInt('vote'); - $votes = array_keys($old_votes); - $votes = array_fuse($votes); - - if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) { - if (idx($votes, $vote, false)) { - $votes = array(); - } else { - $votes = array($vote); - } - } else { - if (idx($votes, $vote, false)) { - unset($votes[$vote]); - } else { - $votes[$vote] = $vote; - } - } - - $this->updateVotes($viewer, $poll, $old_votes, $votes); - - $updated_choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( - 'pollID = %d AND authorPHID = %s', - $poll->getID(), - $viewer->getPHID()); - - $embed = id(new SlowvoteEmbedView()) - ->setPoll($poll) - ->setOptions($options) - ->setViewerChoices($updated_choices); - - return id(new AphrontAjaxResponse()) - ->setContent(array( - 'pollID' => $poll->getID(), - 'contentHTML' => $embed->render(), - )); - } - - if (!$request->isFormPost()) { - return id(new Aphront404Response()); - } + $old_votes = $poll->getViewerChoices($viewer); + $old_votes = mpull($old_votes, null, 'getOptionID'); $votes = $request->getArr('vote'); $votes = array_fuse($votes); - $this->updateVotes($viewer, $poll, $old_votes, $votes); + $method = $poll->getMethod(); + $is_plurality = ($method == PhabricatorSlowvotePoll::METHOD_PLURALITY); - return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID()); - } + if ($is_plurality && count($votes) > 1) { + throw new Exception( + pht('In this poll, you may only vote for one option.')); + } - private function updateVotes($viewer, $poll, $old_votes, $votes) { - if (!empty($votes) && count($votes) > 1 && - $poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) { - return id(new Aphront400Response()); + foreach ($votes as $vote) { + if (!isset($options[$vote])) { + throw new Exception( + pht( + 'Option ("%s") is not a valid poll option. You may only '. + 'vote for valid options.', + $vote)); + } } foreach ($old_votes as $old_vote) { @@ -98,6 +69,9 @@ final class PhabricatorSlowvoteVoteController ->setOptionID($vote) ->save(); } + + return id(new AphrontRedirectResponse()) + ->setURI($poll->getURI()); } } diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php index 452cfbe9eb..1183f3b1cd 100644 --- a/src/applications/slowvote/view/SlowvoteEmbedView.php +++ b/src/applications/slowvote/view/SlowvoteEmbedView.php @@ -39,12 +39,6 @@ final class SlowvoteEmbedView extends AphrontView { } require_celerity_resource('phabricator-slowvote-css'); - require_celerity_resource('javelin-behavior-slowvote-embed'); - - $config = array( - 'pollID' => $poll->getID(), - ); - Javelin::initBehavior('slowvote-embed', $config); $user_choices = $poll->getViewerChoices($this->getUser()); $user_choices = mpull($user_choices, 'getOptionID', 'getOptionID'); diff --git a/src/applications/system/engine/PhabricatorSystemActionEngine.php b/src/applications/system/engine/PhabricatorSystemActionEngine.php index b6a8e26711..6b8352a29e 100644 --- a/src/applications/system/engine/PhabricatorSystemActionEngine.php +++ b/src/applications/system/engine/PhabricatorSystemActionEngine.php @@ -153,7 +153,7 @@ final class PhabricatorSystemActionEngine extends Phobject { queryfx( $conn_w, 'INSERT INTO %T (actorHash, actorIdentity, action, score, epoch) - VALUES %Q', + VALUES %LQ', $log->getTableName(), $chunk); } diff --git a/src/applications/tokens/query/PhabricatorTokenCountQuery.php b/src/applications/tokens/query/PhabricatorTokenCountQuery.php index 64333715fd..c4694af607 100644 --- a/src/applications/tokens/query/PhabricatorTokenCountQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenCountQuery.php @@ -24,17 +24,17 @@ final class PhabricatorTokenCountQuery return ipull($rows, 'tokenCount', 'objectPHID'); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->objectPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } } diff --git a/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php index 4d3b9ca5df..d363e5a14e 100644 --- a/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php +++ b/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php @@ -9,6 +9,7 @@ abstract class PhabricatorEditEngineCommentAction extends Phobject { private $order; private $groupKey; private $conflictKey; + private $submitButtonText; abstract public function getPHUIXControlType(); abstract public function getPHUIXControlSpecification(); @@ -81,4 +82,13 @@ abstract class PhabricatorEditEngineCommentAction extends Phobject { return $this->initialValue; } + public function setSubmitButtonText($text) { + $this->submitButtonText = $text; + return $this; + } + + public function getSubmitButtonText() { + return $this->submitButtonText; + } + } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php index 553bfa3e10..bef6fef5a8 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php @@ -32,7 +32,7 @@ final class PhabricatorApplicationTransactionValueController case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: - case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: + case PhabricatorRepositoryPushPolicyTransaction::TRANSACTIONTYPE: break; default: return new Aphront404Response(); diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php index 340431dd19..f7361d50cf 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php @@ -30,6 +30,11 @@ final class PhabricatorEditEngineConfigurationDefaultsController $fields = $engine->getFieldsForConfig($config); foreach ($fields as $key => $field) { + if (!$field->getIsFormField()) { + unset($fields[$key]); + continue; + } + if (!$field->getIsDefaultable()) { unset($fields[$key]); continue; diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php index 790eaccb47..34b099b9f0 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php @@ -79,6 +79,10 @@ EOTEXT ); foreach ($fields as $field) { + if (!$field->getIsFormField()) { + continue; + } + if (!$field->getIsLockable()) { continue; } diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php index 15eb9530fd..6ff36cdfa4 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php @@ -62,6 +62,10 @@ final class PhabricatorEditEngineConfigurationReorderController $key_order = array(); foreach ($fields as $field) { + if (!$field->getIsFormField()) { + continue; + } + if (!$field->getIsReorderable()) { continue; } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 579b53a989..4e189df164 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1289,6 +1289,10 @@ abstract class PhabricatorEditEngine } foreach ($fields as $field) { + if (!$field->getIsFormField()) { + continue; + } + $field->appendToForm($form); } diff --git a/src/applications/transactions/editfield/PhabricatorApplyEditField.php b/src/applications/transactions/editfield/PhabricatorApplyEditField.php index d349767f94..02d4cad31f 100644 --- a/src/applications/transactions/editfield/PhabricatorApplyEditField.php +++ b/src/applications/transactions/editfield/PhabricatorApplyEditField.php @@ -5,6 +5,7 @@ final class PhabricatorApplyEditField private $actionDescription; private $actionConflictKey; + private $actionSubmitButtonText; private $options; protected function newControl() { @@ -29,6 +30,15 @@ final class PhabricatorApplyEditField return $this->actionConflictKey; } + public function setActionSubmitButtonText($text) { + $this->actionSubmitButtonText = $text; + return $this; + } + + public function getActionSubmitButtonText() { + return $this->actionSubmitButtonText; + } + public function setOptions(array $options) { $this->options = $options; return $this; @@ -59,14 +69,16 @@ final class PhabricatorApplyEditField protected function newCommentAction() { $options = $this->getOptions(); if ($options) { - return id(new PhabricatorEditEngineCheckboxesCommentAction()) - ->setConflictKey($this->getActionConflictKey()) + $action = id(new PhabricatorEditEngineCheckboxesCommentAction()) ->setOptions($options); } else { - return id(new PhabricatorEditEngineStaticCommentAction()) - ->setConflictKey($this->getActionConflictKey()) + $action = id(new PhabricatorEditEngineStaticCommentAction()) ->setDescription($this->getActionDescription()); } + + return $action + ->setConflictKey($this->getActionConflictKey()) + ->setSubmitButtonText($this->getActionSubmitButtonText()); } } diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 07bf3589a8..7eafbc60cf 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -44,7 +44,7 @@ abstract class PhabricatorEditField extends Phobject { private $isDefaultable = true; private $isLockable = true; private $isCopyable = false; - private $isConduitOnly = false; + private $isFormField = true; private $conduitEditTypes; private $bulkEditTypes; @@ -139,13 +139,13 @@ abstract class PhabricatorEditField extends Phobject { return $this->isReorderable; } - public function setIsConduitOnly($is_conduit_only) { - $this->isConduitOnly = $is_conduit_only; + public function setIsFormField($is_form_field) { + $this->isFormField = $is_form_field; return $this; } - public function getIsConduitOnly() { - return $this->isConduitOnly; + public function getIsFormField() { + return $this->isFormField; } public function setDescription($description) { @@ -336,7 +336,7 @@ abstract class PhabricatorEditField extends Phobject { } protected function buildControl() { - if ($this->getIsConduitOnly()) { + if (!$this->getIsFormField()) { return null; } @@ -637,7 +637,7 @@ abstract class PhabricatorEditField extends Phobject { } final public function getHTTPParameterType() { - if ($this->getIsConduitOnly()) { + if (!$this->getIsFormField()) { return null; } @@ -840,7 +840,7 @@ abstract class PhabricatorEditField extends Phobject { } public function shouldGenerateTransactionsFromSubmit() { - if ($this->getIsConduitOnly()) { + if (!$this->getIsFormField()) { return false; } @@ -853,7 +853,7 @@ abstract class PhabricatorEditField extends Phobject { } public function shouldReadValueFromRequest() { - if ($this->getIsConduitOnly()) { + if (!$this->getIsFormField()) { return false; } @@ -869,7 +869,7 @@ abstract class PhabricatorEditField extends Phobject { } public function shouldReadValueFromSubmit() { - if ($this->getIsConduitOnly()) { + if (!$this->getIsFormField()) { return false; } @@ -885,7 +885,7 @@ abstract class PhabricatorEditField extends Phobject { } public function shouldGenerateTransactionsFromComment() { - if ($this->getIsConduitOnly()) { + if (!$this->getCommentActionLabel()) { return false; } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 6202a7df5a..795105bca9 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -869,6 +869,24 @@ abstract class PhabricatorApplicationTransactionEditor return $xactions; } + final protected function didCommitTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + foreach ($xactions as $xaction) { + $type = $xaction->getTransactionType(); + + $xtype = $this->getModularTransactionType($type); + if (!$xtype) { + continue; + } + + $xtype = clone $xtype; + $xtype->setStorage($xaction); + $xtype->didCommitTransaction($object, $xaction->getNewValue()); + } + } + public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source; return $this; @@ -1106,6 +1124,9 @@ abstract class PhabricatorApplicationTransactionEditor $object->saveTransaction(); $transaction_open = false; } + + $this->didCommitTransactions($object, $xactions); + } catch (Exception $ex) { if ($read_locking) { $object->endReadLocking(); diff --git a/src/applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php index c5da130b68..0d20533798 100644 --- a/src/applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php @@ -57,10 +57,7 @@ final class PhabricatorCommentEditEngineExtension ->setBulkEditLabel(pht('Add comment')) ->setBulkEditGroupKey('comments') ->setAliases(array('comments')) - ->setIsHidden(true) - ->setIsReorderable(false) - ->setIsDefaultable(false) - ->setIsLockable(false) + ->setIsFormField(false) ->setCanApplyWithoutEditCapability($is_interact) ->setTransactionType($comment_type) ->setConduitDescription(pht('Make comments.')) diff --git a/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php index 338702478c..7d32545416 100644 --- a/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php @@ -36,20 +36,20 @@ final class PhabricatorSubtypeEditEngineExtension $subtype_field = id(new PhabricatorSelectEditField()) ->setKey(self::EDITKEY) ->setLabel(pht('Subtype')) - ->setIsConduitOnly(true) - ->setIsHidden(true) - ->setIsReorderable(false) - ->setIsDefaultable(false) - ->setIsLockable(false) + ->setIsFormField(false) ->setTransactionType($subtype_type) ->setConduitDescription(pht('Change the object subtype.')) ->setConduitTypeDescription(pht('New object subtype key.')) ->setValue($object->getEditEngineSubtype()) ->setOptions($options); - // If subtypes are configured, enable changing them from the bulk editor. + // If subtypes are configured, enable changing them from the bulk editor + // and comment action stack. if (count($map) > 1) { - $subtype_field->setBulkEditLabel(pht('Change subtype to')); + $subtype_field + ->setBulkEditLabel(pht('Change subtype to')) + ->setCommentActionLabel(pht('Change Subtype')) + ->setCommentActionOrder(3000); } return array( diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php index f79de81ba3..4ca56101fc 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php @@ -57,46 +57,48 @@ abstract class PhabricatorApplicationTransactionCommentQuery return $table->loadAllFromArray($data); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - return $this->formatWhereClause($this->buildWhereClauseComponents($conn_r)); + protected function buildWhereClause(AphrontDatabaseConnection $conn) { + return $this->formatWhereClause( + $conn, + $this->buildWhereClauseComponents($conn)); } protected function buildWhereClauseComponents( - AphrontDatabaseConnection $conn_r) { + AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->transactionPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.transactionPHID IN (%Ls)', $this->transactionPHIDs); } if ($this->isDeleted !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.isDeleted = %d', (int)$this->isDeleted); } @@ -104,11 +106,11 @@ abstract class PhabricatorApplicationTransactionCommentQuery if ($this->hasTransaction !== null) { if ($this->hasTransaction) { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.transactionPHID IS NOT NULL'); } else { $where[] = qsprintf( - $conn_r, + $conn, 'xcomment.transactionPHID IS NULL'); } } diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php index ada979c45c..3a1c8ec60b 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -139,9 +139,14 @@ final class PhabricatorEditEngineConfiguration $values = $this->getProperty('defaults', array()); foreach ($fields as $key => $field) { + if (!$field->getIsFormField()) { + continue; + } + if (!$field->getIsDefaultable()) { continue; } + if ($is_new) { if (array_key_exists($key, $values)) { $field->readDefaultValueFromConfiguration($values[$key]); diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 3d81e02c38..ca82af1c66 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -35,6 +35,10 @@ abstract class PhabricatorModularTransactionType return; } + public function didCommitTransaction($object, $value) { + return; + } + public function getTransactionHasEffect($object, $old, $new) { return ($old !== $new); } @@ -195,6 +199,35 @@ abstract class PhabricatorModularTransactionType return $this->renderHandle($this->getNewValue()); } + final protected function renderOldPolicy() { + return $this->renderPolicy($this->getOldValue(), 'old'); + } + + final protected function renderNewPolicy() { + return $this->renderPolicy($this->getNewValue(), 'new'); + } + + final protected function renderPolicy($phid, $mode) { + $viewer = $this->getViewer(); + $handles = $viewer->loadHandles(array($phid)); + + $policy = PhabricatorPolicy::newFromPolicyAndHandle( + $phid, + $handles[$phid]); + + if ($this->isTextMode()) { + return $this->renderValue($policy->getFullName()); + } + + $storage = $this->getStorage(); + if ($policy->getType() == PhabricatorPolicyType::TYPE_CUSTOM) { + $policy->setHref('/transactions/'.$mode.'/'.$storage->getPHID().'/'); + $policy->setWorkflow(true); + } + + return $this->renderValue($policy->renderDescription()); + } + final protected function renderHandleList(array $phids) { $viewer = $this->getViewer(); $display = $viewer->renderHandleList($phids) diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index c0f514c904..227854c79f 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -329,7 +329,6 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { $key = $comment_action->getKey(); $label = $comment_action->getLabel(); - $action_map[$key] = array( 'key' => $key, 'label' => $label, @@ -339,6 +338,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { 'groupKey' => $comment_action->getGroupKey(), 'conflictKey' => $comment_action->getConflictKey(), 'auralLabel' => pht('Remove Action: %s', $label), + 'buttonText' => $comment_action->getSubmitButtonText(), ); $type_map[$key] = $comment_action; @@ -404,6 +404,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { 'showPreview' => $this->getShowPreview(), 'actionURI' => $this->getAction(), 'drafts' => $draft_keys, + 'defaultButtonText' => $this->getSubmitButtonName(), )); } @@ -426,6 +427,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { id(new AphrontFormSubmitControl()) ->addClass('phui-comment-fullwidth-control') ->addClass('phui-comment-submit-control') + ->addSigil('submit-transactions') ->setValue($this->getSubmitButtonName())); return $form; diff --git a/src/docs/user/cluster/cluster_repositories.diviner b/src/docs/user/cluster/cluster_repositories.diviner index 8d764c2e3e..cdf2641933 100644 --- a/src/docs/user/cluster/cluster_repositories.diviner +++ b/src/docs/user/cluster/cluster_repositories.diviner @@ -414,28 +414,24 @@ present on the leaders but not present on the followers by examining the push logs. If you are comfortable discarding these changes, you can instruct Phabricator -that it can forget about the leaders in two ways: disable the service bindings -to all of the leader devices so they are no longer part of the cluster, or use -`bin/repository thaw` to `--demote` the leaders explicitly. +that it can forget about the leaders by doing this: -If you do this, **you will lose data**. Either action will discard any changes -on the affected leaders which have not replicated to other devices in the -cluster. + - Disable the service bindings to all of the leader devices so they are no + longer part of the cluster. + - Then, use `bin/repository thaw` to `--demote` the leaders explicitly. -To remove a device from the cluster, disable all of the bindings to it -in Almanac, using the web UI. - -{icon exclamation-triangle, color="red"} Any data which is only present on -the disabled device will be lost. - -To demote a device without removing it from the cluster, run this command: +To demote a device, run this command: ``` phabricator/ $ ./bin/repository thaw rXYZ --demote repo002.corp.net ``` {icon exclamation-triangle, color="red"} Any data which is only present on -**this** device will be lost. +the demoted device will be lost. + +If you do this, **you will lose unreplicated data**. You will discard any +changes on the affected leaders which have not replicated to other devices +in the cluster. Ambiguous Leaders diff --git a/src/docs/user/configuration/configuring_outbound_email.diviner b/src/docs/user/configuration/configuring_outbound_email.diviner index a33134fe4e..db04c21877 100644 --- a/src/docs/user/configuration/configuring_outbound_email.diviner +++ b/src/docs/user/configuration/configuring_outbound_email.diviner @@ -133,7 +133,7 @@ tricky because of shell escaping. The easiest way to do it is to use the Then set the value like this: ``` -phabricator/ $ ./bin/config set --stdin < mailers.json +phabricator/ $ ./bin/config set --stdin cluster.mailers < mailers.json ``` For alternatives and more information on configuration, see diff --git a/src/docs/user/userguide/diffusion_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner index cc03433532..a671b0f09e 100644 --- a/src/docs/user/userguide/diffusion_managing.diviner +++ b/src/docs/user/userguide/diffusion_managing.diviner @@ -236,36 +236,46 @@ fetch from, serve from, and push to. These options are covered in detail in @{article:Diffusion User Guide: URIs}. -Staging Area -============ - -The **Staging Area** panel configures staging areas, used to make proposed -changes available to build and continuous integration systems. - -For more details, see @{article:Harbormaster User Guide}. - - -Automation -========== - -The **Automation** panel configures support for allowing Phabricator to make -writes directly to the repository, so that it can perform operations like -automatically landing revisions from the web UI. - -For details on repository automation, see -@{article:Drydock User Guide: Repository Automation}. - - -Symbols +Limits ====== -The **Symbols** panel allows you to customize how symbols (like class and -function names) are linked when viewing code in the repository, and when -viewing revisions which propose code changes to the repository. +The **Limits** panel allows you to configure limits and timeouts. -To take advantage of this feature, you need to do additional work to build -symbol indexes. For details on configuring and populating symbol indexes, see -@{article:User Guide: Symbol Indexes}. +**Filesize Limit**: Allows you to set a maximum filesize for any file in the +repository. If a commit creates a larger file (or modifies an existing file so +it becomes too large) it will be rejected. This option only applies to hosted +repositories. + +This limit is primarily intended to make it more difficult to accidentally push +very large files that shouldn't be version controlled (like logs, binaries, +machine learning data, or media assets). Pushing huge datafiles by mistake can +make the repository unwieldy by dramatically increasing how much data must be +transferred over the network to clone it, and simply reverting the changes +doesn't reduce the impact of this kind of mistake. + +**Clone/Fetch Timeout**: Configure the internal timeout for creating copies +of this repository during operations like intracluster synchronization and +Drydock working copy construction. This timeout does not affect external +users. + +**Touch Limit**: Apply a limit to the maximum number of paths that any commit +may touch. If a commit affects more paths than this limit, it will be rejected. +This option only applies to hosted repositories. Users may work around this +limit by breaking the commit into several smaller commits which each affect +fewer paths. + +This limit is intended to offer a guard rail against users making silly +mistakes that create obviously mistaken changes, like copying an entire +repository into itself and pushing the result. This kind of change can take +some effort to clean up if it becomes part of repository history. + +Note that if you move a file, both the old and new locations count as touched +paths. You should generally configure this limit to be more than twice the +number of files you anticipate any user ever legitimately wanting to move in +a single commit. For example, a limit of `20000` will let users move up to +10,000 files in a single commit, but will reject users mistakenly trying to +push a copy of another repository or a directory with a million logfiles or +whatever other kind of creative nonsense they manage to dream up. Branches @@ -311,6 +321,38 @@ revisions and tasks. If you don't want Phabricator to close objects when it discovers new commits, disable **Autoclose** for the repository. +Staging Area +============ + +The **Staging Area** panel configures staging areas, used to make proposed +changes available to build and continuous integration systems. + +For more details, see @{article:Harbormaster User Guide}. + + +Automation +========== + +The **Automation** panel configures support for allowing Phabricator to make +writes directly to the repository, so that it can perform operations like +automatically landing revisions from the web UI. + +For details on repository automation, see +@{article:Drydock User Guide: Repository Automation}. + + +Symbols +====== + +The **Symbols** panel allows you to customize how symbols (like class and +function names) are linked when viewing code in the repository, and when +viewing revisions which propose code changes to the repository. + +To take advantage of this feature, you need to do additional work to build +symbol indexes. For details on configuring and populating symbol indexes, see +@{article:User Guide: Symbol Indexes}. + + Repository Identifiers and Names ================================ diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php index 7dc55427ca..89435b5869 100644 --- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php @@ -557,8 +557,14 @@ final class PhabricatorDatabaseRef $conn = $this->newManagementConnection(); try { - $value = queryfx_one($conn, 'SELECT @@%Q', $key); - $value = $value['@@'.$key]; + $value = queryfx_one($conn, 'SELECT @@%C', $key); + + // NOTE: Although MySQL allows us to escape configuration values as if + // they are column names, the escaping is included in the column name + // of the return value: if we select "@@`x`", we get back a column named + // "@@`x`", not "@@x" as we might expect. + $value = head($value); + } catch (AphrontQueryException $ex) { $value = null; } diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php index 27b8276c85..1a63e040e2 100644 --- a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php +++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php @@ -59,7 +59,7 @@ final class PhabricatorCustomFieldEditField } protected function buildControl() { - if ($this->getIsConduitOnly()) { + if (!$this->getIsFormField()) { return null; } diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 36db8f239b..d7df3c5b78 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -1145,10 +1145,10 @@ abstract class PhabricatorCustomField extends Phobject { return $this->proxy->newStandardEditField(); } - if (!$this->shouldAppearInEditView()) { - $conduit_only = true; + if ($this->shouldAppearInEditView()) { + $form_field = true; } else { - $conduit_only = false; + $form_field = false; } $bulk_label = $this->getBulkEditLabel(); @@ -1160,7 +1160,7 @@ abstract class PhabricatorCustomField extends Phobject { ->setBulkEditLabel($bulk_label) ->setDescription($this->getFieldDescription()) ->setTransactionType($this->getApplicationTransactionType()) - ->setIsConduitOnly($conduit_only) + ->setIsFormField($form_field) ->setValue($this->getNewValueForApplicationTransactions()); } diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php index efe12e68b8..8ae5529f95 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php @@ -303,7 +303,7 @@ final class PhabricatorCustomFieldList extends Phobject { foreach (PhabricatorLiskDAO::chunkSQL($sql_list) as $chunk) { queryfx( $conn_w, - 'INSERT INTO %T (objectPHID, indexKey, indexValue) VALUES %Q', + 'INSERT INTO %T (objectPHID, indexKey, indexValue) VALUES %LQ', $table, $chunk); } diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php index bb70f03d88..0163143ae7 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php @@ -209,39 +209,47 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { } protected function buildCustomWhereClause( - AphrontDatabaseConnection $conn_w, + AphrontDatabaseConnection $conn, $phase) { $where = array(); switch ($phase) { case self::PHASE_LEASED: - $where[] = 'leaseOwner IS NOT NULL'; - $where[] = 'leaseExpires >= UNIX_TIMESTAMP()'; + $where[] = qsprintf( + $conn, + 'leaseOwner IS NOT NULL'); + $where[] = qsprintf( + $conn, + 'leaseExpires >= UNIX_TIMESTAMP()'); break; case self::PHASE_UNLEASED: - $where[] = 'leaseOwner IS NULL'; + $where[] = qsprintf( + $conn, + 'leaseOwner IS NULL'); break; case self::PHASE_EXPIRED: - $where[] = 'leaseExpires < UNIX_TIMESTAMP()'; + $where[] = qsprintf( + $conn, + 'leaseExpires < UNIX_TIMESTAMP()'); break; default: throw new Exception(pht("Unknown phase '%s'!", $phase)); } if ($this->ids !== null) { - $where[] = qsprintf($conn_w, 'id IN (%Ld)', $this->ids); + $where[] = qsprintf($conn, 'id IN (%Ld)', $this->ids); } if ($this->objectPHIDs !== null) { - $where[] = qsprintf($conn_w, 'objectPHID IN (%Ls)', $this->objectPHIDs); + $where[] = qsprintf($conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function buildUpdateWhereClause( - AphrontDatabaseConnection $conn_w, + AphrontDatabaseConnection $conn, $phase, array $rows) { @@ -257,25 +265,25 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { 'Trying to lease tasks selected in the leased phase! This is '. 'intended to be impossible.')); case self::PHASE_UNLEASED: - $where[] = qsprintf($conn_w, 'leaseOwner IS NULL'); - $where[] = qsprintf($conn_w, 'id IN (%Ld)', ipull($rows, 'id')); + $where[] = qsprintf($conn, 'leaseOwner IS NULL'); + $where[] = qsprintf($conn, 'id IN (%Ld)', ipull($rows, 'id')); break; case self::PHASE_EXPIRED: $in = array(); foreach ($rows as $row) { $in[] = qsprintf( - $conn_w, + $conn, '(id = %d AND leaseOwner = %s)', $row['id'], $row['leaseOwner']); } - $where[] = qsprintf($conn_w, '(%Q)', implode(' OR ', $in)); + $where[] = qsprintf($conn, '%LO', $in); break; default: throw new Exception(pht('Unknown phase "%s"!', $phase)); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function buildOrderClause(AphrontDatabaseConnection $conn_w, $phase) { diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTaskQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTaskQuery.php index ae6e2cc442..3797de50f9 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTaskQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTaskQuery.php @@ -48,81 +48,81 @@ abstract class PhabricatorWorkerTaskQuery return $this; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id in (%Ld)', $this->ids); } if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } if ($this->dateModifiedSince !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'dateModified > %d', $this->dateModifiedSince); } if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'dateCreated < %d', $this->dateCreatedBefore); } if ($this->classNames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'taskClass IN (%Ls)', $this->classNames); } if ($this->minFailureCount !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'failureCount >= %d', $this->minFailureCount); } if ($this->maxFailureCount !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'failureCount <= %d', $this->maxFailureCount); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } - protected function buildOrderClause(AphrontDatabaseConnection $conn_r) { + protected function buildOrderClause(AphrontDatabaseConnection $conn) { // NOTE: The garbage collector executes this query with a date constraint, // and the query is inefficient if we don't use the same key for ordering. // See T9808 for discussion. if ($this->dateCreatedBefore) { - return qsprintf($conn_r, 'ORDER BY dateCreated DESC, id DESC'); + return qsprintf($conn, 'ORDER BY dateCreated DESC, id DESC'); } else if ($this->dateModifiedSince) { - return qsprintf($conn_r, 'ORDER BY dateModified DESC, id DESC'); + return qsprintf($conn, 'ORDER BY dateModified DESC, id DESC'); } else { - return qsprintf($conn_r, 'ORDER BY id DESC'); + return qsprintf($conn, 'ORDER BY id DESC'); } } - protected function buildLimitClause(AphrontDatabaseConnection $conn_r) { - $clause = ''; + protected function buildLimitClause(AphrontDatabaseConnection $conn) { if ($this->limit) { - $clause = qsprintf($conn_r, 'LIMIT %d', $this->limit); + return qsprintf($conn, 'LIMIT %d', $this->limit); + } else { + return qsprintf($conn, ''); } - return $clause; } } diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php index a8dc5061e7..87c16a48c5 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php @@ -145,67 +145,71 @@ final class PhabricatorWorkerTriggerQuery return $triggers; } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { + protected function buildJoinClause(AphrontDatabaseConnection $conn) { $joins = array(); if (($this->nextEpochMin !== null) || ($this->nextEpochMax !== null) || ($this->order == self::ORDER_EXECUTION)) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T e ON e.triggerID = t.id', id(new PhabricatorWorkerTriggerEvent())->getTableName()); } - return implode(' ', $joins); + if ($joins) { + return qsprintf($conn, '%LJ', $joins); + } else { + return qsprintf($conn, ''); + } } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 't.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 't.phid IN (%Ls)', $this->phids); } if ($this->versionMin !== null) { $where[] = qsprintf( - $conn_r, + $conn, 't.triggerVersion >= %d', $this->versionMin); } if ($this->versionMax !== null) { $where[] = qsprintf( - $conn_r, + $conn, 't.triggerVersion <= %d', $this->versionMax); } if ($this->nextEpochMin !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'e.nextEventEpoch >= %d', $this->nextEpochMin); } if ($this->nextEpochMax !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'e.nextEventEpoch <= %d', $this->nextEpochMax); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } private function buildOrderClause(AphrontDatabaseConnection $conn_r) { diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php index e1a6ef1500..ed1eeaea63 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -169,8 +169,7 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { $t_start = microtime(true); $worker->executeTask(); - $t_end = microtime(true); - $duration = (int)(1000000 * ($t_end - $t_start)); + $duration = phutil_microseconds_since($t_start); $result = $this->archiveTask( PhabricatorWorkerArchiveTask::RESULT_SUCCESS, diff --git a/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php b/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php index 700a11ea36..588b5267b4 100644 --- a/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php +++ b/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php @@ -275,13 +275,13 @@ final class PhabricatorEdgeEditor extends Phobject { $conn_w->openTransaction(); $this->openTransactions[] = $conn_w; - foreach (array_chunk($sql, 256) as $chunk) { + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn_w, 'INSERT INTO %T (src, type, dst, dateCreated, seq, dataID) - VALUES %Q ON DUPLICATE KEY UPDATE dataID = VALUES(dataID)', + VALUES %LQ ON DUPLICATE KEY UPDATE dataID = VALUES(dataID)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, - implode(', ', $chunk)); + $chunk); } } } @@ -320,9 +320,9 @@ final class PhabricatorEdgeEditor extends Phobject { foreach (array_chunk($sql, 256) as $chunk) { queryfx( $conn_w, - 'DELETE FROM %T WHERE (%Q)', + 'DELETE FROM %T WHERE %LO', PhabricatorEdgeConfig::TABLE_NAME_EDGE, - implode(' OR ', $chunk)); + $chunk); } } } diff --git a/src/infrastructure/edges/query/PhabricatorEdgeQuery.php b/src/infrastructure/edges/query/PhabricatorEdgeQuery.php index 2dfceb7fbc..6519c47724 100644 --- a/src/infrastructure/edges/query/PhabricatorEdgeQuery.php +++ b/src/infrastructure/edges/query/PhabricatorEdgeQuery.php @@ -290,19 +290,19 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { /** * @task internal */ - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->sourcePHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'edge.src IN (%Ls)', $this->sourcePHIDs); } if ($this->edgeTypes) { $where[] = qsprintf( - $conn_r, + $conn, 'edge.type IN (%Ls)', $this->edgeTypes); } @@ -310,23 +310,23 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { if ($this->destPHIDs) { // potentially complain if $this->edgeType was not set $where[] = qsprintf( - $conn_r, + $conn, 'edge.dst IN (%Ls)', $this->destPHIDs); } - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } /** * @task internal */ - private function buildOrderClause($conn_r) { + private function buildOrderClause(AphrontDatabaseConnection $conn) { if ($this->order == self::ORDER_NEWEST_FIRST) { - return 'ORDER BY edge.dateCreated DESC, edge.seq DESC'; + return qsprintf($conn, 'ORDER BY edge.dateCreated DESC, edge.seq DESC'); } else { - return 'ORDER BY edge.dateCreated ASC, edge.seq ASC'; + return qsprintf($conn, 'ORDER BY edge.dateCreated ASC, edge.seq ASC'); } } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index c2241995e7..5169c08ffd 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -9,10 +9,6 @@ final class PhabricatorUSEnglishTranslation protected function getTranslations() { return array( - 'No daemon(s) with id(s) "%s" exist!' => array( - 'No daemon with id %s exists!', - 'No daemons with ids %s exist!', - ), 'These %d configuration value(s) are related:' => array( 'This configuration value is related:', 'These configuration values are related:', diff --git a/src/infrastructure/query/PhabricatorOffsetPagedQuery.php b/src/infrastructure/query/PhabricatorOffsetPagedQuery.php index ef97a4ebe4..fd9ea18e3f 100644 --- a/src/infrastructure/query/PhabricatorOffsetPagedQuery.php +++ b/src/infrastructure/query/PhabricatorOffsetPagedQuery.php @@ -27,15 +27,15 @@ abstract class PhabricatorOffsetPagedQuery extends PhabricatorQuery { return $this->limit; } - protected function buildLimitClause(AphrontDatabaseConnection $conn_r) { + protected function buildLimitClause(AphrontDatabaseConnection $conn) { if ($this->limit && $this->offset) { - return qsprintf($conn_r, 'LIMIT %d, %d', $this->offset, $this->limit); + return qsprintf($conn, 'LIMIT %d, %d', $this->offset, $this->limit); } else if ($this->limit) { - return qsprintf($conn_r, 'LIMIT %d', $this->limit); + return qsprintf($conn, 'LIMIT %d', $this->limit); } else if ($this->offset) { - return qsprintf($conn_r, 'LIMIT %d, %d', $this->offset, PHP_INT_MAX); + return qsprintf($conn, 'LIMIT %d, %d', $this->offset, PHP_INT_MAX); } else { - return ''; + return qsprintf($conn, ''); } } diff --git a/src/infrastructure/query/PhabricatorQuery.php b/src/infrastructure/query/PhabricatorQuery.php index 5893297dbc..4315ef79ae 100644 --- a/src/infrastructure/query/PhabricatorQuery.php +++ b/src/infrastructure/query/PhabricatorQuery.php @@ -15,90 +15,65 @@ abstract class PhabricatorQuery extends Phobject { /** * @task format */ - protected function formatWhereClause(array $parts) { + protected function formatWhereClause( + AphrontDatabaseConnection $conn, + array $parts) { + $parts = $this->flattenSubclause($parts); if (!$parts) { - return ''; + return qsprintf($conn, ''); } - return 'WHERE '.$this->formatWhereSubclause($parts); + return qsprintf($conn, 'WHERE %LA', $parts); + } + + + + /** + * @task format + */ + protected function formatSelectClause( + AphrontDatabaseConnection $conn, + array $parts) { + + $parts = $this->flattenSubclause($parts); + if (!$parts) { + throw new Exception(pht('Can not build empty SELECT clause!')); + } + + return qsprintf($conn, 'SELECT %LQ', $parts); } /** * @task format */ - protected function formatWhereSubclause(array $parts) { + protected function formatJoinClause( + AphrontDatabaseConnection $conn, + array $parts) { + $parts = $this->flattenSubclause($parts); if (!$parts) { - return null; + return qsprintf($conn, ''); } - return '('.implode(') AND (', $parts).')'; + return qsprintf($conn, '%LJ', $parts); } /** * @task format */ - protected function formatSelectClause(array $parts) { + protected function formatHavingClause( + AphrontDatabaseConnection $conn, + array $parts) { + $parts = $this->flattenSubclause($parts); if (!$parts) { - throw new Exception(pht('Can not build empty select clause!')); + return qsprintf($conn, ''); } - return 'SELECT '.$this->formatSelectSubclause($parts); - } - - - /** - * @task format - */ - protected function formatSelectSubclause(array $parts) { - $parts = $this->flattenSubclause($parts); - if (!$parts) { - return null; - } - return implode(', ', $parts); - } - - - /** - * @task format - */ - protected function formatJoinClause(array $parts) { - $parts = $this->flattenSubclause($parts); - if (!$parts) { - return ''; - } - - return implode(' ', $parts); - } - - - /** - * @task format - */ - protected function formatHavingClause(array $parts) { - $parts = $this->flattenSubclause($parts); - if (!$parts) { - return ''; - } - - return 'HAVING '.$this->formatHavingSubclause($parts); - } - - - /** - * @task format - */ - protected function formatHavingSubclause(array $parts) { - $parts = $this->flattenSubclause($parts); - if (!$parts) { - return null; - } - - return '('.implode(') AND (', $parts).')'; + return qsprintf($conn, 'HAVING %LA', $parts); } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 1784a8ce17..931840e8fb 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -123,12 +123,19 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery AphrontDatabaseConnection $conn, $table_name) { + $table_alias = $this->getPrimaryTableAlias(); + if ($table_alias === null) { + $table_alias = qsprintf($conn, ''); + } else { + $table_alias = qsprintf($conn, '%T', $table_alias); + } + return qsprintf( $conn, '%Q FROM %T %Q %Q %Q %Q %Q %Q %Q', $this->buildSelectClause($conn), $table_name, - (string)$this->getPrimaryTableAlias(), + $table_alias, $this->buildJoinClause($conn), $this->buildWhereClause($conn), $this->buildGroupClause($conn), @@ -195,15 +202,15 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } - final protected function buildLimitClause(AphrontDatabaseConnection $conn_r) { + final protected function buildLimitClause(AphrontDatabaseConnection $conn) { if ($this->shouldLimitResults()) { $limit = $this->getRawResultLimit(); if ($limit) { - return qsprintf($conn_r, 'LIMIT %d', $limit); + return qsprintf($conn, 'LIMIT %d', $limit); } } - return ''; + return qsprintf($conn, ''); } protected function shouldLimitResults() { @@ -277,7 +284,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ protected function buildSelectClause(AphrontDatabaseConnection $conn) { $parts = $this->buildSelectClauseParts($conn); - return $this->formatSelectClause($parts); + return $this->formatSelectClause($conn, $parts); } @@ -291,7 +298,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery if ($alias) { $select[] = qsprintf($conn, '%T.*', $alias); } else { - $select[] = '*'; + $select[] = qsprintf($conn, '*'); } $select[] = $this->buildEdgeLogicSelectClause($conn); @@ -306,7 +313,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ protected function buildJoinClause(AphrontDatabaseConnection $conn) { $joins = $this->buildJoinClauseParts($conn); - return $this->formatJoinClause($joins); + return $this->formatJoinClause($conn, $joins); } @@ -328,7 +335,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = $this->buildWhereClauseParts($conn); - return $this->formatWhereClause($where); + return $this->formatWhereClause($conn, $where); } @@ -352,7 +359,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ protected function buildHavingClause(AphrontDatabaseConnection $conn) { $having = $this->buildHavingClauseParts($conn); - return $this->formatHavingClause($having); + return $this->formatHavingClause($conn, $having); } @@ -371,13 +378,13 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ protected function buildGroupClause(AphrontDatabaseConnection $conn) { if (!$this->shouldGroupQueryResultRows()) { - return ''; + return qsprintf($conn, ''); } return qsprintf( $conn, 'GROUP BY %Q', - $this->getApplicationSearchObjectPHIDColumn()); + $this->getApplicationSearchObjectPHIDColumn($conn)); } @@ -425,7 +432,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } else { // No paging is being applied to this query so we do not need to // construct a paging clause. - return ''; + return qsprintf($conn, ''); } $keys = array(); @@ -655,24 +662,16 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $conn, '%Q %Q %Q', $field, - $reverse ? '>' : '<', + $reverse ? qsprintf($conn, '>') : qsprintf($conn, '<'), $value); } if ($parts) { - if (count($parts) > 1) { - $clause[] = '('.implode(') OR (', $parts).')'; - } else { - $clause[] = head($parts); - } + $clause[] = qsprintf($conn, '%LO', $parts); } if ($clause) { - if (count($clause) > 1) { - $clauses[] = '('.implode(') AND (', $clause).')'; - } else { - $clauses[] = head($clause); - } + $clauses[] = qsprintf($conn, '%LA', $clause); } if ($value === null) { @@ -689,7 +688,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } - return '('.implode(') OR (', $clauses).')'; + if ($clauses) { + return qsprintf($conn, '%LO', $clauses); + } + + return qsprintf($conn, ''); } @@ -1134,7 +1137,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } - return qsprintf($conn, 'ORDER BY %Q', implode(', ', $sql)); + return qsprintf($conn, 'ORDER BY %LQ', $sql); } @@ -1244,17 +1247,18 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * See @{method:getPrimaryTableAlias} if the column needs to be qualified with * a table alias. * - * @return string Column name. + * @param AphrontDatabaseConnection Connection executing queries. + * @return PhutilQueryString Column name. * @task appsearch */ - protected function getApplicationSearchObjectPHIDColumn() { - if ($this->getPrimaryTableAlias()) { - $prefix = $this->getPrimaryTableAlias().'.'; - } else { - $prefix = ''; - } + protected function getApplicationSearchObjectPHIDColumn( + AphrontDatabaseConnection $conn) { - return $prefix.'phid'; + if ($this->getPrimaryTableAlias()) { + return qsprintf($conn, '%T.phid', $this->getPrimaryTableAlias()); + } else { + return qsprintf($conn, 'phid'); + } } @@ -1308,15 +1312,15 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * @task appsearch */ protected function buildApplicationSearchGroupClause( - AphrontDatabaseConnection $conn_r) { + AphrontDatabaseConnection $conn) { if ($this->getApplicationSearchMayJoinMultipleRows()) { return qsprintf( - $conn_r, + $conn, 'GROUP BY %Q', - $this->getApplicationSearchObjectPHIDColumn()); + $this->getApplicationSearchObjectPHIDColumn($conn)); } else { - return ''; + return qsprintf($conn, ''); } } @@ -1338,16 +1342,16 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias = $constraint['alias']; $index = $constraint['index']; $cond = $constraint['cond']; - $phid_column = $this->getApplicationSearchObjectPHIDColumn(); + $phid_column = $this->getApplicationSearchObjectPHIDColumn($conn); switch ($cond) { case '=': // Figure out whether we need to do a LEFT JOIN or not. We need to // LEFT JOIN if we're going to select "IS NULL" rows. - $join_type = 'JOIN'; + $join_type = qsprintf($conn, 'JOIN'); foreach ($constraint['constraints'] as $query_constraint) { $op = $query_constraint->getOperator(); if ($op === PhabricatorQueryConstraint::OPERATOR_NULL) { - $join_type = 'LEFT JOIN'; + $join_type = qsprintf($conn, 'LEFT JOIN'); break; } } @@ -1410,7 +1414,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } - $phid_column = $this->getApplicationSearchObjectPHIDColumn(); + $phid_column = $this->getApplicationSearchObjectPHIDColumn($conn); $orderable = $this->getOrderableColumns(); $vector = $this->getOrderVector(); @@ -1436,7 +1440,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $key); } - return implode(' ', $joins); + if ($joins) { + return qsprintf($conn, '%LJ', $joins); + } else { + return qsprintf($conn, ''); + } } /** @@ -1512,7 +1520,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } if ($constraint_parts) { - $where[] = '('.implode(') OR (', $constraint_parts).')'; + $where[] = qsprintf($conn, '%LO', $constraint_parts); } break; } @@ -1666,7 +1674,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } if (!$this->ferretEngine) { - $select[] = '0 _ft_rank'; + $select[] = qsprintf($conn, '0 _ft_rank'); return $select; } @@ -1732,12 +1740,21 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } - $parts[] = '0'; + $parts[] = qsprintf($conn, '%d', 0); + + $sum = array_shift($parts); + foreach ($parts as $part) { + $sum = qsprintf( + $conn, + '%Q + %Q', + $sum, + $part); + } $select[] = qsprintf( $conn, '%Q _ft_rank', - implode(' + ', $parts)); + $sum); return $select; } @@ -2027,20 +2044,20 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery if ($is_not) { $where[] = qsprintf( $conn, - '(%Q)', - implode(' AND ', $term_constraints)); + '%LA', + $term_constraints); } else if ($is_quoted) { $where[] = qsprintf( $conn, - '(%T.rawCorpus LIKE %~ AND (%Q))', + '(%T.rawCorpus LIKE %~ AND %LO)', $table_alias, $value, - implode(' OR ', $term_constraints)); + $term_constraints); } else { $where[] = qsprintf( $conn, - '(%Q)', - implode(' OR ', $term_constraints)); + '%LO', + $term_constraints); } } @@ -2349,7 +2366,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery (array)$constraint->getValue(), $idx++); } - $parts = implode(', ', $parts); + $parts = qsprintf($conn, '%LQ', $parts); $select[] = qsprintf( $conn, @@ -2373,7 +2390,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ public function buildEdgeLogicJoinClause(AphrontDatabaseConnection $conn) { $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; - $phid_column = $this->getApplicationSearchObjectPHIDColumn(); + $phid_column = $this->getApplicationSearchObjectPHIDColumn($conn); $joins = array(); foreach ($this->edgeLogicConstraints as $type => $constraints) { @@ -2436,9 +2453,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery // this to a LEFT join. We'll use WHERE to select matching rows // later. if ($has_null) { - $join_type = 'LEFT'; + $join_type = qsprintf($conn, 'LEFT'); } else { - $join_type = ''; + $join_type = qsprintf($conn, ''); } $joins[] = qsprintf( @@ -2531,9 +2548,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } if ($full && $null) { - $full = $this->formatWhereSubclause($full); - $null = $this->formatWhereSubclause($null); - $where[] = qsprintf($conn, '(%Q OR %Q)', $full, $null); + $where[] = qsprintf($conn, '(%LA OR %LA)', $full, $null); } else if ($full) { foreach ($full as $condition) { $where[] = $condition; @@ -2913,7 +2928,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery if ($alias) { $col = qsprintf($conn, '%T.spacePHID', $alias); } else { - $col = 'spacePHID'; + $col = qsprintf($conn, 'spacePHID'); } if ($space_phids && $include_null) { diff --git a/src/infrastructure/storage/__tests__/QueryFormattingTestCase.php b/src/infrastructure/storage/__tests__/QueryFormattingTestCase.php index 6bd0eeedaf..cff7390949 100644 --- a/src/infrastructure/storage/__tests__/QueryFormattingTestCase.php +++ b/src/infrastructure/storage/__tests__/QueryFormattingTestCase.php @@ -3,23 +3,23 @@ final class QueryFormattingTestCase extends PhabricatorTestCase { public function testQueryFormatting() { - $conn_r = id(new PhabricatorUser())->establishConnection('r'); + $conn = id(new PhabricatorUser())->establishConnection('r'); $this->assertEqual( 'NULL', - qsprintf($conn_r, '%nd', null)); + (string)qsprintf($conn, '%nd', null)); $this->assertEqual( '0', - qsprintf($conn_r, '%nd', 0)); + (string)qsprintf($conn, '%nd', 0)); $this->assertEqual( '0', - qsprintf($conn_r, '%d', 0)); + (string)qsprintf($conn, '%d', 0)); $raised = null; try { - qsprintf($conn_r, '%d', 'derp'); + qsprintf($conn, '%d', 'derp'); } catch (Exception $ex) { $raised = $ex; } @@ -29,27 +29,40 @@ final class QueryFormattingTestCase extends PhabricatorTestCase { $this->assertEqual( "''", - qsprintf($conn_r, '%s', null)); + (string)qsprintf($conn, '%s', null)); $this->assertEqual( 'NULL', - qsprintf($conn_r, '%ns', null)); + (string)qsprintf($conn, '%ns', null)); $this->assertEqual( "'', ''", - qsprintf($conn_r, '%Ls', array('x', 'y'))); + (string)qsprintf($conn, '%Ls', array('x', 'y'))); $this->assertEqual( "''", - qsprintf($conn_r, '%B', null)); + (string)qsprintf($conn, '%B', null)); $this->assertEqual( 'NULL', - qsprintf($conn_r, '%nB', null)); + (string)qsprintf($conn, '%nB', null)); $this->assertEqual( "'', ''", - qsprintf($conn_r, '%LB', array('x', 'y'))); + (string)qsprintf($conn, '%LB', array('x', 'y'))); + + $this->assertEqual( + '', + (string)qsprintf($conn, '%T', 'x')); + + $this->assertEqual( + '', + (string)qsprintf($conn, '%C', 'y')); + + $this->assertEqual( + '.', + (string)qsprintf($conn, '%R', new AphrontDatabaseTableRef('x', 'y'))); + } diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php index 0c940ef5f0..aa12f8e614 100644 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -162,7 +162,8 @@ * @task xaction Managing Transactions * @task isolate Isolation for Unit Testing */ -abstract class LiskDAO extends Phobject { +abstract class LiskDAO extends Phobject + implements AphrontDatabaseTableRefInterface { const CONFIG_IDS = 'id-mechanism'; const CONFIG_TIMESTAMPS = 'timestamps'; @@ -235,8 +236,11 @@ abstract class LiskDAO extends Phobject { * @return string Connection namespace for cache * @task conn */ - abstract protected function getConnectionNamespace(); + protected function getConnectionNamespace() { + return $this->getDatabaseName(); + } + abstract protected function getDatabaseName(); /** * Get an existing, cached connection for this object. @@ -513,26 +517,25 @@ abstract class LiskDAO extends Phobject { protected function loadRawDataWhere($pattern /* , $args... */) { - $connection = $this->establishConnection('r'); + $conn = $this->establishConnection('r'); - $lock_clause = ''; - if ($connection->isReadLocking()) { - $lock_clause = 'FOR UPDATE'; - } else if ($connection->isWriteLocking()) { - $lock_clause = 'LOCK IN SHARE MODE'; + if ($conn->isReadLocking()) { + $lock_clause = qsprintf($conn, 'FOR UPDATE'); + } else if ($conn->isWriteLocking()) { + $lock_clause = qsprintf($conn, 'LOCK IN SHARE MODE'); + } else { + $lock_clause = qsprintf($conn, ''); } $args = func_get_args(); $args = array_slice($args, 1); - $pattern = 'SELECT * FROM %T WHERE '.$pattern.' %Q'; - array_unshift($args, $this->getTableName()); + $pattern = 'SELECT * FROM %R WHERE '.$pattern.' %Q'; + array_unshift($args, $this); array_push($args, $lock_clause); array_unshift($args, $pattern); - return call_user_func_array( - array($connection, 'queryData'), - $args); + return call_user_func_array(array($conn, 'queryData'), $args); } @@ -1146,12 +1149,11 @@ abstract class LiskDAO extends Phobject { $map[$key] = qsprintf($conn, '%C = %ns', $key, $value); } } - $map = implode(', ', $map); $id = $this->getID(); $conn->query( - 'UPDATE %T SET %Q WHERE %C = '.(is_int($id) ? '%d' : '%s'), - $this->getTableName(), + 'UPDATE %R SET %LQ WHERE %C = '.(is_int($id) ? '%d' : '%s'), + $this, $map, $this->getIDKeyForUse(), $id); @@ -1178,8 +1180,8 @@ abstract class LiskDAO extends Phobject { $conn = $this->establishConnection('w'); $conn->query( - 'DELETE FROM %T WHERE %C = %d', - $this->getTableName(), + 'DELETE FROM %R WHERE %C = %d', + $this, $this->getIDKeyForUse(), $this->getID()); @@ -1252,12 +1254,25 @@ abstract class LiskDAO extends Phobject { $parameter_exception); } } - $data = implode(', ', $data); + + switch ($mode) { + case 'INSERT': + $verb = qsprintf($conn, 'INSERT'); + break; + case 'REPLACE': + $verb = qsprintf($conn, 'REPLACE'); + break; + default: + throw new Exception( + pht( + 'Insert mode verb "%s" is not recognized, use INSERT or REPLACE.', + $mode)); + } $conn->query( - '%Q INTO %T (%LC) VALUES (%Q)', - $mode, - $this->getTableName(), + '%Q INTO %R (%LC) VALUES (%LQ)', + $verb, + $this, $columns, $data); @@ -1637,6 +1652,11 @@ abstract class LiskDAO extends Phobject { $now = PhabricatorTime::getNow(); foreach ($connections as $key => $connection) { + // If the connection is not idle, never consider it inactive. + if (!$connection->isIdle()) { + continue; + } + $last_active = $connection->getLastActiveEpoch(); $idle_duration = ($now - $last_active); @@ -1657,6 +1677,18 @@ abstract class LiskDAO extends Phobject { } } + public static function closeIdleConnections() { + $connections = self::$connections; + + foreach ($connections as $key => $connection) { + if (!$connection->isIdle()) { + continue; + } + + self::closeConnection($key); + } + } + private static function closeConnection($key) { if (empty(self::$connections[$key])) { throw new Exception( @@ -2019,4 +2051,17 @@ abstract class LiskDAO extends Phobject { ->getMaximumByteLengthForDataType($data_type); } + +/* -( AphrontDatabaseTableRefInterface )----------------------------------- */ + + + public function getAphrontRefDatabaseName() { + return $this->getDatabaseName(); + } + + public function getAphrontRefTableName() { + return $this->getTableName(); + } + + } diff --git a/src/infrastructure/storage/lisk/LiskDAOSet.php b/src/infrastructure/storage/lisk/LiskDAOSet.php index e1bc1e50d1..90eba708ea 100644 --- a/src/infrastructure/storage/lisk/LiskDAOSet.php +++ b/src/infrastructure/storage/lisk/LiskDAOSet.php @@ -77,11 +77,20 @@ final class LiskDAOSet extends Phobject { } else { $set = new LiskDAOSet(); $this->subsets[] = $set; + + $conn = $object->establishConnection('r'); + + if (strlen($where)) { + $where_clause = qsprintf($conn, 'AND %Q', $where); + } else { + $where_clause = qsprintf($conn, ''); + } + $relatives = $object->putInSet($set)->loadAllWhere( '%C IN (%Ls) %Q', $foreign_column, $ids, - ($where != '' ? 'AND '.$where : '')); + $where_clause); $relatives = mgroup($relatives, 'get'.$foreign_column); } } diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php index b3b324e951..e47d701df9 100644 --- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php +++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php @@ -187,24 +187,20 @@ abstract class PhabricatorLiskDAO extends LiskDAO { */ abstract public function getApplicationName(); - protected function getConnectionNamespace() { + protected function getDatabaseName() { return self::getStorageNamespace().'_'.$this->getApplicationName(); } - /** * Break a list of escaped SQL statement fragments (e.g., VALUES lists for * INSERT, previously built with @{function:qsprintf}) into chunks which will * fit under the MySQL 'max_allowed_packet' limit. * - * Chunks are glued together with `$glue`, by default ", ". - * * If a statement is too large to fit within the limit, it is broken into * its own chunk (but might fail when the query executes). */ public static function chunkSQL( array $fragments, - $glue = ', ', $limit = null) { if ($limit === null) { @@ -217,9 +213,13 @@ abstract class PhabricatorLiskDAO extends LiskDAO { $chunk = array(); $len = 0; - $glue_len = strlen($glue); + $glue_len = strlen(', '); foreach ($fragments as $fragment) { - $this_len = strlen($fragment); + if ($fragment instanceof PhutilQueryString) { + $this_len = strlen($fragment->getUnmaskedString()); + } else { + $this_len = strlen($fragment); + } if ($chunk) { // Chunks after the first also imply glue. @@ -233,7 +233,7 @@ abstract class PhabricatorLiskDAO extends LiskDAO { if ($chunk) { $result[] = $chunk; } - $len = strlen($fragment); + $len = ($this_len - $glue_len); $chunk = array($fragment); } } @@ -242,10 +242,6 @@ abstract class PhabricatorLiskDAO extends LiskDAO { $result[] = $chunk; } - foreach ($result as $key => $fragment_list) { - $result[$key] = implode($glue, $fragment_list); - } - return $result; } diff --git a/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php b/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php index 8ce7608a8b..66ffdb17bc 100644 --- a/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php +++ b/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php @@ -15,13 +15,15 @@ final class LiskChunkTestCase extends PhabricatorTestCase { $this->assertEqual( array( - 'aa', - 'bb', - 'ccc', - 'dd', - 'e', + array('a'), + array('a'), + array('b'), + array('b'), + array('ccc'), + array('dd'), + array('e'), ), - PhabricatorLiskDAO::chunkSQL($fragments, '', 2)); + PhabricatorLiskDAO::chunkSQL($fragments, 2)); $fragments = array( @@ -37,11 +39,11 @@ final class LiskChunkTestCase extends PhabricatorTestCase { $this->assertEqual( array( - 'a, a, a', - 'XX, a, a', - 'a, a', + array('a', 'a', 'a'), + array('XX', 'a', 'a'), + array('a', 'a'), ), - PhabricatorLiskDAO::chunkSQL($fragments, ', ', 8)); + PhabricatorLiskDAO::chunkSQL($fragments, 8)); $fragments = array( @@ -55,12 +57,12 @@ final class LiskChunkTestCase extends PhabricatorTestCase { $this->assertEqual( array( - 'xxxxxxxxxx', - 'yyyyyyyyyy', - 'a, b, c', - 'zzzzzzzzzz', + array('xxxxxxxxxx'), + array('yyyyyyyyyy'), + array('a', 'b', 'c'), + array('zzzzzzzzzz'), ), - PhabricatorLiskDAO::chunkSQL($fragments, ', ', 8)); + PhabricatorLiskDAO::chunkSQL($fragments, 8)); } } diff --git a/src/infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php b/src/infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php index d9070753f8..8008fb8e46 100644 --- a/src/infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php +++ b/src/infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php @@ -22,7 +22,7 @@ final class LiskIsolationTestDAO extends LiskDAO { 'resource!')); } - protected function getConnectionNamespace() { + protected function getDatabaseName() { return 'test'; } diff --git a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php index 19d7a98d42..e66ba784f7 100644 --- a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php +++ b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php @@ -265,7 +265,9 @@ final class PhabricatorStorageManagementAPI extends Phobject { } try { - queryfx($conn, '%Q', $query); + // NOTE: We're using the unsafe "%Z" conversion here. There's no + // avoiding it since we're executing raw text files full of SQL. + queryfx($conn, '%Z', $query); } catch (AphrontAccessDeniedQueryException $ex) { throw new PhutilProxyException( pht( diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index 5b9459cfb8..5bc83972dd 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -357,22 +357,76 @@ abstract class PhabricatorStorageManagementWorkflow } if ($adjust['charset']) { + switch ($adjust['charset']) { + case 'binary': + $charset_value = qsprintf($conn, 'binary'); + break; + case 'utf8': + $charset_value = qsprintf($conn, 'utf8'); + break; + case 'utf8mb4': + $charset_value = qsprintf($conn, 'utf8mb4'); + break; + default: + throw new Exception( + pht( + 'Unsupported character set "%s".', + $adjust['charset'])); + } + + switch ($adjust['collation']) { + case 'binary': + $collation_value = qsprintf($conn, 'binary'); + break; + case 'utf8_general_ci': + $collation_value = qsprintf($conn, 'utf8_general_ci'); + break; + case 'utf8mb4_bin': + $collation_value = qsprintf($conn, 'utf8mb4_bin'); + break; + case 'utf8mb4_unicode_ci': + $collation_value = qsprintf($conn, 'utf8mb4_unicode_ci'); + break; + default: + throw new Exception( + pht( + 'Unsupported collation set "%s".', + $adjust['collation'])); + } + $parts[] = qsprintf( $conn, 'CHARACTER SET %Q COLLATE %Q', - $adjust['charset'], - $adjust['collation']); + $charset_value, + $collation_value); } + if ($parts) { + $parts = qsprintf($conn, '%LJ', $parts); + } else { + $parts = qsprintf($conn, ''); + } + + if ($adjust['nullable']) { + $nullable = qsprintf($conn, 'NULL'); + } else { + $nullable = qsprintf($conn, 'NOT NULL'); + } + + // TODO: We're using "%Z" here for the column type, which is + // technically unsafe. It would be nice to be able to use "%Q" + // instead, but this requires a fair amount of legwork to + // enumerate all column types. + queryfx( $conn, - 'ALTER TABLE %T.%T MODIFY %T %Q %Q %Q', + 'ALTER TABLE %T.%T MODIFY %T %Z %Q %Q', $adjust['database'], $adjust['table'], $adjust['name'], $adjust['type'], - implode(' ', $parts), - $adjust['nullable'] ? 'NULL' : 'NOT NULL'); + $parts, + $nullable); } break; case 'key': @@ -395,7 +449,7 @@ abstract class PhabricatorStorageManagementWorkflow // Different keys need different creation syntax. Notable // special cases are primary keys and fulltext keys. if ($adjust['name'] == 'PRIMARY') { - $key_name = 'PRIMARY KEY'; + $key_name = qsprintf($conn, 'PRIMARY KEY'); } else if ($adjust['indexType'] == 'FULLTEXT') { $key_name = qsprintf($conn, 'FULLTEXT %T', $adjust['name']); } else { @@ -414,11 +468,11 @@ abstract class PhabricatorStorageManagementWorkflow queryfx( $conn, - 'ALTER TABLE %T.%T ADD %Q (%Q)', + 'ALTER TABLE %T.%T ADD %Q (%LK)', $adjust['database'], $adjust['table'], $key_name, - implode(', ', $adjust['columns'])); + $adjust['columns']); } break; default: diff --git a/src/view/form/control/AphrontFormSubmitControl.php b/src/view/form/control/AphrontFormSubmitControl.php index c2bf716848..7e948bd135 100644 --- a/src/view/form/control/AphrontFormSubmitControl.php +++ b/src/view/form/control/AphrontFormSubmitControl.php @@ -3,6 +3,7 @@ final class AphrontFormSubmitControl extends AphrontFormControl { private $buttons = array(); + private $sigils = array(); public function addCancelButton($href, $label = null) { if (!$label) { @@ -22,6 +23,11 @@ final class AphrontFormSubmitControl extends AphrontFormControl { return $this; } + public function addSigil($sigil) { + $this->sigils[] = $sigil; + return $this; + } + protected function getCustomControlClass() { return 'aphront-form-control-submit'; } @@ -29,11 +35,19 @@ final class AphrontFormSubmitControl extends AphrontFormControl { protected function renderInput() { $submit_button = null; if ($this->getValue()) { - $submit_button = phutil_tag( + + if ($this->sigils) { + $sigils = $this->sigils; + } else { + $sigils = null; + } + + $submit_button = javelin_tag( 'button', array( 'type' => 'submit', 'name' => '__submit__', + 'sigil' => $sigils, 'disabled' => $this->getDisabled() ? 'disabled' : null, ), $this->getValue()); diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php index 212b057376..1bfb74d886 100644 --- a/support/startup/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -64,6 +64,8 @@ final class PhabricatorStartup { * @task info */ public static function getMicrosecondsSinceStart() { + // This is the same as "phutil_microseconds_since()", but we may not have + // loaded libphutil yet. return (int)(1000000 * (microtime(true) - self::getStartTime())); } diff --git a/webroot/rsrc/css/aphront/tooltip.css b/webroot/rsrc/css/aphront/tooltip.css index e6ff46cbed..754e647d0a 100644 --- a/webroot/rsrc/css/aphront/tooltip.css +++ b/webroot/rsrc/css/aphront/tooltip.css @@ -5,6 +5,12 @@ .jx-tooltip-container { position: absolute; padding: 5px; + + /* In Chrome, moving the cursor into the empty space next to the "caret" on + the caret side of the tooltip can cause the tooltip to flicker rapidly + because the cursor hits the container. To stop this, prevent cursor + events on the container. See T8440. */ + pointer-events: none; } .jx-tooltip-appear { diff --git a/webroot/rsrc/css/phui/phui-hovercard.css b/webroot/rsrc/css/phui/phui-hovercard.css index 1f01362b6f..876b0b6e53 100644 --- a/webroot/rsrc/css/phui/phui-hovercard.css +++ b/webroot/rsrc/css/phui/phui-hovercard.css @@ -54,6 +54,8 @@ .phui-hovercard-body-item { margin: 4px 0 0 0; + overflow: hidden; + text-overflow: ellipsis; } .phui-hovercard-body-header { diff --git a/webroot/rsrc/externals/javelin/core/Event.js b/webroot/rsrc/externals/javelin/core/Event.js index d4e04be7e2..787a453c4d 100644 --- a/webroot/rsrc/externals/javelin/core/Event.js +++ b/webroot/rsrc/externals/javelin/core/Event.js @@ -133,6 +133,20 @@ JX.install('Event', { return r.which == 3 || r.button == 2; }, + + /** + * Get whether the mouse button associated with the mouse event is the + * left-side button in a browser-agnostic way. + * + * @return bool + * @task info + */ + isLeftButton: function() { + var r = this.getRawEvent(); + return (r.which == 1 || r.button == 0); + }, + + /** * Determine if a mouse event is a normal event (left mouse button, no * modifier keys). diff --git a/webroot/rsrc/externals/javelin/core/init.js b/webroot/rsrc/externals/javelin/core/init.js index fb10b0f4c3..61c9f13d6d 100644 --- a/webroot/rsrc/externals/javelin/core/init.js +++ b/webroot/rsrc/externals/javelin/core/init.js @@ -214,8 +214,13 @@ 'mouseup' ]; - if (window.localStorage) { - window_events.push('storage'); + try { + if (window.localStorage) { + window_events.push('storage'); + } + } catch (storage_exception) { + // See PHI985. In Firefox, accessing "window.localStorage" may throw an + // exception if cookies are disabled. } for (ii = 0; ii < window_events.length; ++ii) { diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index a95267c0a3..5ba43a7e6d 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1261,7 +1261,10 @@ JX.install('DiffChangesetList', { _onrangedown: function(e) { // NOTE: We're allowing "mousedown" from a touch event through so users // can leave inlines on a single line. - if (e.isRightButton()) { + + // See PHI985. We want to exclude both right-mouse and middle-mouse + // clicks from continuing. + if (!e.isLeftButton()) { return; } diff --git a/webroot/rsrc/js/application/slowvote/behavior-slowvote-embed.js b/webroot/rsrc/js/application/slowvote/behavior-slowvote-embed.js deleted file mode 100644 index 72e52e1f0c..0000000000 --- a/webroot/rsrc/js/application/slowvote/behavior-slowvote-embed.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @provides javelin-behavior-slowvote-embed - * @requires javelin-behavior - * javelin-request - * javelin-stratcom - * javelin-dom - */ -JX.behavior('slowvote-embed', function() { - JX.Stratcom.listen( - ['click'], - 'slowvote-option', - function(e) { - if (!e.isNormalMouseEvent()) { - return; - } - e.kill(); - - var pollID = e.getNodeData('slowvote-embed').pollID; - var voteURI = '/vote/' + pollID + '/'; - - var request = new JX.Request(voteURI, function(r) { - var updated_poll = JX.$H(r.contentHTML); - var root = JX.$('phabricator-standard-page'); - - var polls = JX.DOM.scry(root, 'div', 'slowvote-embed'); - - for(var i = 0; i < polls.length; i++) { - var data = JX.Stratcom.getData(polls[i]); - - if (data.pollID == pollID) { - JX.DOM.replace(polls[i], updated_poll); - } - - } - - }); - - request.addData({vote: e.getNodeData('slowvote-option').optionID}); - request.send(); - - }); - -}); diff --git a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js index 9cec374f6b..ab962592c7 100644 --- a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js +++ b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js @@ -43,6 +43,22 @@ JX.behavior('comment-actions', function(config) { return null; } + function redraw() { + // If any of the stacked actions specify that they change the label for + // the "Submit" button, update the button text. Otherwise, return it to + // the default text. + var button_text = config.defaultButtonText; + for (var k in rows) { + var action = action_map[k]; + if (action.buttonText) { + button_text = action.buttonText; + } + } + + var button_node = JX.DOM.find(form_node, 'button', 'submit-transactions'); + JX.DOM.setContent(button_node, button_text); + } + function remove_action(key) { var row = rows[key]; if (row) { @@ -50,6 +66,8 @@ JX.behavior('comment-actions', function(config) { row.option.disabled = false; delete rows[key]; } + + redraw(); } function serialize_actions() { @@ -90,6 +108,8 @@ JX.behavior('comment-actions', function(config) { control = add_row(option); } + + redraw(); } function onresponse(response) { @@ -209,6 +229,8 @@ JX.behavior('comment-actions', function(config) { place_node.parentNode.insertBefore(node, place_node); + redraw(); + force_preview(); return control;