From 8c7f114b4d905f2eb99be1ba8a7b9273b01e599a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 Jun 2020 06:35:10 -0700 Subject: [PATCH 01/80] Fix an issue where "Export Data" could fail if a user had a nonempty custom policy preference Summary: The "Export Data" workflow incorrectly uses the "Policy Favorites" setting to choose a default export format. This is just a copy/paste error; the correct setting exists and is unused. If the setting value is an array (as the "Policy Favorites" value often is), we try to use it as an array index. This generates a runtime exception after D21044. ``` [2020-06-16 06:32:12] EXCEPTION: (RuntimeException) Illegal offset type in isset or empty at [/src/error/PhutilErrorHandler.php:263] #0 <#2> PhutilErrorHandler::handleError(integer, string, string, integer, array) called at [/src/applications/search/controller/PhabricatorApplicationSearchController.php:460] ``` - Use the correct setting. - Make sure the value we read is a string. Test Plan: - Used "Export Data" with a nonempty, array-valued "Policy Favorites" setting. - Before: runtime exception. - After: clean export. - Used "Export Data" again, saw my selection from the first time persisted. Differential Revision: https://secure.phabricator.com/D21361 --- .../PhabricatorApplicationSearchController.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 98d125fd33..290623e149 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -964,8 +964,14 @@ final class PhabricatorApplicationSearchController private function readExportFormatPreference() { $viewer = $this->getViewer(); - $export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY; - return $viewer->getUserSetting($export_key); + $export_key = PhabricatorExportFormatSetting::SETTINGKEY; + $value = $viewer->getUserSetting($export_key); + + if (is_string($value)) { + return $value; + } + + return ''; } private function writeExportFormatPreference($value) { @@ -976,7 +982,7 @@ final class PhabricatorApplicationSearchController return; } - $export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY; + $export_key = PhabricatorExportFormatSetting::SETTINGKEY; $preferences = PhabricatorUserPreferences::loadUserPreferences($viewer); $editor = id(new PhabricatorUserPreferencesEditor()) From 9ce1271805e97a4d96d142fd6304652ede702c1f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 Jun 2020 08:43:25 -0700 Subject: [PATCH 02/80] Improve the quality of SSH error messages Summary: See PHI1784. Currently, users who pass an invalid SSH command to Phabricator's SSH handler get an unhelpful error message. Make it more helpful. Test Plan: Ran `./bin/ssh-exec` with no arguments (old, helpful error), invalid arguments (before: unhelpful error; after: helpful error), and valid arguments (old, helpful behavior). Differential Revision: https://secure.phabricator.com/D21362 --- scripts/ssh/ssh-exec.php | 51 ++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php index eea2db4712..387eb9d0d2 100755 --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -211,21 +211,29 @@ try { ->setUniqueMethod('getName') ->execute(); + $command_list = array_keys($workflows); + $command_list = implode(', ', $command_list); + + $error_lines = array(); + $error_lines[] = pht('Welcome to Phabricator.'); + $error_lines[] = pht( + 'You are logged in as %s.', + $user_name); + if (!$original_argv) { - throw new Exception( - pht( - "Welcome to Phabricator.\n\n". - "You are logged in as %s.\n\n". - "You haven't specified a command to run. This means you're requesting ". - "an interactive shell, but Phabricator does not provide an ". - "interactive shell over SSH.\n\n". - "Usually, you should run a command like `%s` or `%s` ". - "rather than connecting directly with SSH.\n\n". - "Supported commands are: %s.", - $user_name, - 'git clone', - 'hg push', - implode(', ', array_keys($workflows)))); + $error_lines[] = pht( + 'You have not specified a command to run. This means you are requesting '. + 'an interactive shell, but Phabricator does not provide interactive '. + 'shells over SSH.'); + $error_lines[] = pht( + '(Usually, you should run a command like "git clone" or "hg push" '. + 'instead of connecting directly with SSH.)'); + $error_lines[] = pht( + 'Supported commands are: %s.', + $command_list); + + $error_lines = implode("\n\n", $error_lines); + throw new PhutilArgumentUsageException($error_lines); } $log_argv = implode(' ', $original_argv); @@ -247,7 +255,20 @@ try { $parsed_args = new PhutilArgumentParser($parseable_argv); if (empty($workflows[$command])) { - throw new Exception(pht('Invalid command.')); + $error_lines[] = pht( + 'You have specified the command "%s", but that command is not '. + 'supported by Phabricator. As received by Phabricator, your entire '. + 'argument list was:', + $command); + + $error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv); + + $error_lines[] = pht( + 'Supported commands are: %s.', + $command_list); + + $error_lines = implode("\n\n", $error_lines); + throw new PhutilArgumentUsageException($error_lines); } $workflow = $parsed_args->parseWorkflows($workflows); From d91abf50f70a8a37ddca2863abec79c6062491fa Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 25 Jun 2020 17:45:48 -0700 Subject: [PATCH 03/80] Add "--background" and "--count" flags to "bin/webhook call" Summary: See PHI1794, which reports an issue where a large number of queued webhook calls led to connection exhaustion. To make this easier to reproduce and test, add "--count" and "--background" flags to "bin/webhook call". This primarily supports "bin/webook call ... --background --count 10000" to quickly fill the queue with a bunch of calls. Test Plan: Ran `bin/webhook call` in foreground and background modes, with and without counts. Saw appropriate console and queue behavior. Differential Revision: https://secure.phabricator.com/D21368 --- .../HeraldWebhookCallManagementWorkflow.php | 80 +++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/src/applications/herald/management/HeraldWebhookCallManagementWorkflow.php b/src/applications/herald/management/HeraldWebhookCallManagementWorkflow.php index 10249cca68..1b5b21482f 100644 --- a/src/applications/herald/management/HeraldWebhookCallManagementWorkflow.php +++ b/src/applications/herald/management/HeraldWebhookCallManagementWorkflow.php @@ -28,6 +28,17 @@ final class HeraldWebhookCallManagementWorkflow 'name' => 'secure', 'help' => pht('Set the "secure" flag on the request.'), ), + array( + 'name' => 'count', + 'param' => 'N', + 'help' => pht('Make a total of __N__ copies of the call.'), + ), + array( + 'name' => 'background', + 'help' => pht( + 'Instead of making calls in the foreground, add the tasks '. + 'to the daemon queue.'), + ), )); } @@ -41,6 +52,17 @@ final class HeraldWebhookCallManagementWorkflow 'Specify a webhook to call with "--id".')); } + $count = $args->getArg('count'); + if ($count === null) { + $count = 1; + } + + if ($count <= 0) { + throw new PhutilArgumentUsageException( + pht( + 'Specified "--count" must be larger than 0.')); + } + $hook = id(new HeraldWebhookQuery()) ->setViewer($viewer) ->withIDs(array($id)) @@ -69,6 +91,8 @@ final class HeraldWebhookCallManagementWorkflow $object = head($objects); } + $is_background = $args->getArg('background'); + $xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject($object); @@ -80,25 +104,49 @@ final class HeraldWebhookCallManagementWorkflow $application_phid = id(new PhabricatorHeraldApplication())->getPHID(); - $request = HeraldWebhookRequest::initializeNewWebhookRequest($hook) - ->setObjectPHID($object->getPHID()) - ->setIsTestAction(true) - ->setIsSilentAction((bool)$args->getArg('silent')) - ->setIsSecureAction((bool)$args->getArg('secure')) - ->setTriggerPHIDs(array($application_phid)) - ->setTransactionPHIDs(mpull($xactions, 'getPHID')) - ->save(); + if ($is_background) { + echo tsprintf( + "%s\n", + pht( + 'Queueing webhook calls...')); + $progress_bar = id(new PhutilConsoleProgressBar()) + ->setTotal($count); + } else { + echo tsprintf( + "%s\n", + pht( + 'Calling webhook...')); + PhabricatorWorker::setRunAllTasksInProcess(true); + } - PhabricatorWorker::setRunAllTasksInProcess(true); - $request->queueCall(); + for ($ii = 0; $ii < $count; $ii++) { + $request = HeraldWebhookRequest::initializeNewWebhookRequest($hook) + ->setObjectPHID($object->getPHID()) + ->setIsTestAction(true) + ->setIsSilentAction((bool)$args->getArg('silent')) + ->setIsSecureAction((bool)$args->getArg('secure')) + ->setTriggerPHIDs(array($application_phid)) + ->setTransactionPHIDs(mpull($xactions, 'getPHID')) + ->save(); - $request->reload(); + $request->queueCall(); - echo tsprintf( - "%s\n", - pht( - 'Success, got HTTP %s from webhook.', - $request->getErrorCode())); + if ($is_background) { + $progress_bar->update(1); + } else { + $request->reload(); + + echo tsprintf( + "%s\n", + pht( + 'Success, got HTTP %s from webhook.', + $request->getErrorCode())); + } + } + + if ($is_background) { + $progress_bar->done(); + } return 0; } From 22de618d3bd3c55a9330d11a9a862c503643f513 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 25 Jun 2020 17:46:12 -0700 Subject: [PATCH 04/80] When acquiring a GlobalLock, put good connections that just got unlucky back in the pool Summary: See PHI1794, which describes a connection exhaustion issue with a large number of webhook tasks in queue. The "GlobalLock" mechanism manages a separate connection pool from the main pool, and webhook workers immediately try to grab a webhook lock with a 0-second wait when they start. So far, this is fine. Prior to this change, good connections which fail to acqiure a lock are discarded. This can lead to connection exhaustion as the worker rapidly cycles through lock attempts: the connections will remain open for at least 60 seconds (since D16389) in an effort to avoid outbound port exhaustion, but they're effectively orphaned because they aren't part of the main pool and aren't part of the lock pool. We're basically leaking a connection every time we fail to lock. Failing to lock doesn't mean we need to discard the connection: it's a completely suitable connection for reuse. Instead of dropping it on the floor, put it into the lock pool. Test Plan: - Used "bin/webhook call ... --count 10000 --background" to queue a large number of webhook calls against a slow ("sleep(15);") webhook. - Used "bin/phd launch 32 taskmaster" to start taskmasters. - Observed MySQL connection behavior: - Before change: 2048 configured connections immediately exhausted. - After change: connections stable at ~160ish. - Ran queue for a while, saw expected single-threaded calls to webhook. Differential Revision: https://secure.phabricator.com/D21369 --- src/infrastructure/util/PhabricatorGlobalLock.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/infrastructure/util/PhabricatorGlobalLock.php b/src/infrastructure/util/PhabricatorGlobalLock.php index 221a75b37c..cc0641b106 100644 --- a/src/infrastructure/util/PhabricatorGlobalLock.php +++ b/src/infrastructure/util/PhabricatorGlobalLock.php @@ -144,6 +144,18 @@ final class PhabricatorGlobalLock extends PhutilLock { $ok = head($result); if (!$ok) { + + // See PHI1794. We failed to acquire the lock, but the connection itself + // is still good. We're done with it, so add it to the pool, just as we + // would if we were releasing the lock. + + // If we don't do this, we may establish a huge number of connections + // very rapidly if many workers try to acquire a lock at once. For + // example, this can happen if there are a large number of webhook tasks + // in the queue. + + self::$pool[] = $conn; + throw id(new PhutilLockException($lock_name)) ->setHint($this->newHint($lock_name, $wait)); } From a7b3ba5a6f4ee6744c6e80ae4aecf86261382e3a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 29 Jun 2020 16:12:28 -0700 Subject: [PATCH 05/80] When long monospaced character sequences appear in Remarkup tables, break rather than scrolling Summary: Ref PHI1798. If you put an SSH public key in a table cell with monospaced formatting and then print the table, the cell scrolls and not all of the content appears in your physical printed document. Generally, the current scrolling behavior for monospaced text seems never-desirable: I can't imagine any cases where we want the table cell to scroll. (There's more of an argument for complex cases where a table cell has, say, an embedded paste.) Add `line-break: anywhere` to break monospaced text inside these cells. Test Plan: In Safari, Firefox, and Chrome, viewed a ##|`MMMMM....`|## table. Saw scrolling before and wrapping/breaking after. Differential Revision: https://secure.phabricator.com/D21370 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index df11037696..d10f117847 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'ba768cdb', + 'core.pkg.css' => 'da792a0f', 'core.pkg.js' => '845355f4', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -114,7 +114,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd', 'rsrc/css/application/uiexample/example.css' => 'b4795059', 'rsrc/css/core/core.css' => '1b29ed61', - 'rsrc/css/core/remarkup.css' => 'c286eaef', + 'rsrc/css/core/remarkup.css' => '7d3ebc86', 'rsrc/css/core/syntax.css' => '548567f6', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', 'rsrc/css/diviner/diviner-shared.css' => '4bd263b0', @@ -798,7 +798,7 @@ return array( 'phabricator-object-selector-css' => 'ee77366f', 'phabricator-phtize' => '2f1db1ed', 'phabricator-prefab' => '5793d835', - 'phabricator-remarkup-css' => 'c286eaef', + 'phabricator-remarkup-css' => '7d3ebc86', 'phabricator-search-results-css' => '9ea70ace', 'phabricator-shaped-request' => '995f5102', 'phabricator-slowvote-css' => '1694baed', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 6e1c4b3627..1688da31f0 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -86,6 +86,7 @@ padding: 1px 4px; border-radius: 3px; white-space: pre-wrap; + line-break: anywhere; } /* NOTE: You can currently produce this with [[link | `name`]]. Restore the From 7d496f2c6d7b56193ebf495800e5a593a00c852d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 Jun 2020 10:48:55 -0700 Subject: [PATCH 06/80] Collapse repository URI normalization code into Arcanist Summary: Ref T13546. Companion change to D21372. Move URI normalization code to Arcanist to we can more-often resolve remote URIs correctly. Test Plan: Grepped for affected symbols. Maniphest Tasks: T13546 Differential Revision: https://secure.phabricator.com/D21373 --- src/__phutil_library_map__.php | 4 - .../PhabricatorRepositoryURINormalizer.php | 165 ------------------ ...ricatorRepositoryURINormalizerTestCase.php | 81 --------- .../PhabricatorRepositoryDiscoveryEngine.php | 6 +- .../query/PhabricatorRepositoryQuery.php | 7 +- .../storage/PhabricatorRepositoryURI.php | 36 +++- 6 files changed, 39 insertions(+), 260 deletions(-) delete mode 100644 src/applications/repository/data/PhabricatorRepositoryURINormalizer.php delete mode 100644 src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fa3eaabe7a..41ae62ed5d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4653,8 +4653,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php', 'PhabricatorRepositoryURI' => 'applications/repository/storage/PhabricatorRepositoryURI.php', 'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php', - 'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php', - 'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php', 'PhabricatorRepositoryURIPHIDType' => 'applications/repository/phid/PhabricatorRepositoryURIPHIDType.php', 'PhabricatorRepositoryURIQuery' => 'applications/repository/query/PhabricatorRepositoryURIQuery.php', 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', @@ -11407,8 +11405,6 @@ phutil_register_library_map(array( 'PhabricatorConduitResultInterface', ), 'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO', - 'PhabricatorRepositoryURINormalizer' => 'Phobject', - 'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', diff --git a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php deleted file mode 100644 index 935d78fea5..0000000000 --- a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php +++ /dev/null @@ -1,165 +0,0 @@ -getNormalizedPath() == $norm_b->getNormalizedPath()) { - * // URIs appear to point at the same repository. - * } else { - * // URIs are very unlikely to be the same repository. - * } - * - * Because a repository can be hosted at arbitrarily many arbitrary URIs, there - * is no way to completely prevent false negatives by only examining URIs - * (that is, repositories with totally different URIs could really be the same). - * However, normalization is relatively aggressive and false negatives should - * be rare: if normalization says two URIs are different repositories, they - * probably are. - * - * @task normal Normalizing URIs - */ -final class PhabricatorRepositoryURINormalizer extends Phobject { - - const TYPE_GIT = 'git'; - const TYPE_SVN = 'svn'; - const TYPE_MERCURIAL = 'hg'; - - private $type; - private $uri; - - public function __construct($type, $uri) { - switch ($type) { - case self::TYPE_GIT: - case self::TYPE_SVN: - case self::TYPE_MERCURIAL: - break; - default: - throw new Exception(pht('Unknown URI type "%s"!', $type)); - } - - $this->type = $type; - $this->uri = $uri; - } - - public static function getAllURITypes() { - return array( - self::TYPE_GIT, - self::TYPE_SVN, - self::TYPE_MERCURIAL, - ); - } - - -/* -( Normalizing URIs )--------------------------------------------------- */ - - - /** - * @task normal - */ - public function getPath() { - switch ($this->type) { - case self::TYPE_GIT: - $uri = new PhutilURI($this->uri); - return $uri->getPath(); - case self::TYPE_SVN: - case self::TYPE_MERCURIAL: - $uri = new PhutilURI($this->uri); - if ($uri->getProtocol()) { - return $uri->getPath(); - } - - return $this->uri; - } - } - - public function getNormalizedURI() { - return $this->getNormalizedDomain().'/'.$this->getNormalizedPath(); - } - - - /** - * @task normal - */ - public function getNormalizedPath() { - $path = $this->getPath(); - $path = trim($path, '/'); - - switch ($this->type) { - case self::TYPE_GIT: - $path = preg_replace('/\.git$/', '', $path); - break; - case self::TYPE_SVN: - case self::TYPE_MERCURIAL: - break; - } - - // If this is a Phabricator URI, strip it down to the callsign. We mutably - // allow you to clone repositories as "/diffusion/X/anything.git", for - // example. - - $matches = null; - if (preg_match('@^(diffusion/(?:[A-Z]+|\d+))@', $path, $matches)) { - $path = $matches[1]; - } - - return $path; - } - - public function getNormalizedDomain() { - $domain = null; - - $uri = new PhutilURI($this->uri); - $domain = $uri->getDomain(); - - if (!strlen($domain)) { - return ''; - } - - $domain = phutil_utf8_strtolower($domain); - - // See T13435. If the domain for a repository URI is same as the install - // base URI, store it as a "" token instead of the actual domain - // so that the index does not fall out of date if the install moves. - - $base_uri = PhabricatorEnv::getURI('/'); - $base_uri = new PhutilURI($base_uri); - $base_domain = $base_uri->getDomain(); - $base_domain = phutil_utf8_strtolower($base_domain); - if ($domain === $base_domain) { - return ''; - } - - // Likewise, store a token for the "SSH Host" domain so it can be changed - // without requiring an index rebuild. - - $ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host'); - if (strlen($ssh_host)) { - $ssh_host = phutil_utf8_strtolower($ssh_host); - if ($domain === $ssh_host) { - return ''; - } - } - - return $domain; - } - - -} diff --git a/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php b/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php deleted file mode 100644 index 8ab54a23a4..0000000000 --- a/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php +++ /dev/null @@ -1,81 +0,0 @@ - 'path', - 'https://user@domain.com/path.git' => 'path', - 'git@domain.com:path.git' => 'path', - 'ssh://user@gitserv002.com/path.git' => 'path', - 'ssh://htaft@domain.com/path.git' => 'path', - 'ssh://user@domain.com/bananas.git' => 'bananas', - 'git@domain.com:bananas.git' => 'bananas', - 'user@domain.com:path/repo' => 'path/repo', - 'user@domain.com:path/repo/' => 'path/repo', - 'file:///path/to/local/repo.git' => 'path/to/local/repo', - '/path/to/local/repo.git' => 'path/to/local/repo', - 'ssh://something.com/diffusion/X/anything.git' => 'diffusion/X', - 'ssh://something.com/diffusion/X/' => 'diffusion/X', - ); - - $type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; - - foreach ($cases as $input => $expect) { - $normal = new PhabricatorRepositoryURINormalizer($type_git, $input); - $this->assertEqual( - $expect, - $normal->getNormalizedPath(), - pht('Normalized Git path for "%s".', $input)); - } - } - - public function testDomainURINormalizer() { - $base_domain = 'base.phabricator.example.com'; - $ssh_domain = 'ssh.phabricator.example.com'; - - $env = PhabricatorEnv::beginScopedEnv(); - $env->overrideEnvConfig('phabricator.base-uri', 'http://'.$base_domain); - $env->overrideEnvConfig('diffusion.ssh-host', $ssh_domain); - - $cases = array( - '/' => '', - '/path/to/local/repo.git' => '', - 'ssh://user@domain.com/path.git' => 'domain.com', - 'ssh://user@DOMAIN.COM/path.git' => 'domain.com', - 'http://'.$base_domain.'/diffusion/X/' => '', - 'ssh://'.$ssh_domain.'/diffusion/X/' => '', - 'git@'.$ssh_domain.':bananas.git' => '', - ); - - $type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; - - foreach ($cases as $input => $expect) { - $normal = new PhabricatorRepositoryURINormalizer($type_git, $input); - - $this->assertEqual( - $expect, - $normal->getNormalizedDomain(), - pht('Normalized domain for "%s".', $input)); - } - } - - public function testSVNURINormalizer() { - $cases = array( - 'file:///path/to/repo' => 'path/to/repo', - 'file:///path/to/repo/' => 'path/to/repo', - ); - - $type_svn = PhabricatorRepositoryURINormalizer::TYPE_SVN; - - foreach ($cases as $input => $expect) { - $normal = new PhabricatorRepositoryURINormalizer($type_svn, $input); - $this->assertEqual( - $expect, - $normal->getNormalizedPath(), - pht('Normalized SVN path for "%s".', $input)); - } - } - -} diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index 2fc1abbf34..c3e4169b53 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -290,13 +290,13 @@ final class PhabricatorRepositoryDiscoveryEngine $remote_root = (string)($xml->entry[0]->repository[0]->root[0]); $expect_root = $repository->getSubversionPathURI(); - $normal_type_svn = PhabricatorRepositoryURINormalizer::TYPE_SVN; + $normal_type_svn = ArcanistRepositoryURINormalizer::TYPE_SVN; - $remote_normal = id(new PhabricatorRepositoryURINormalizer( + $remote_normal = id(new ArcanistRepositoryURINormalizer( $normal_type_svn, $remote_root))->getNormalizedPath(); - $expect_normal = id(new PhabricatorRepositoryURINormalizer( + $expect_normal = id(new ArcanistRepositoryURINormalizer( $normal_type_svn, $expect_root))->getNormalizedPath(); diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 21465393f7..9150c692b6 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -689,10 +689,13 @@ final class PhabricatorRepositoryQuery // or an `svn+ssh` URI, we could deduce how to normalize it. However, this // would be more complicated and it's not clear if it matters in practice. - $types = PhabricatorRepositoryURINormalizer::getAllURITypes(); + $domain_map = PhabricatorRepositoryURI::getURINormalizerDomainMap(); + + $types = ArcanistRepositoryURINormalizer::getAllURITypes(); foreach ($this->uris as $uri) { foreach ($types as $type) { - $normalized_uri = new PhabricatorRepositoryURINormalizer($type, $uri); + $normalized_uri = new ArcanistRepositoryURINormalizer($type, $uri); + $normalized_uri->setDomainMap($domain_map); $normalized_uris[] = $normalized_uri->getNormalizedURI(); } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 8b1de62dd6..98c08baa00 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -196,19 +196,22 @@ final class PhabricatorRepositoryURI $map = array( PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => - PhabricatorRepositoryURINormalizer::TYPE_GIT, + ArcanistRepositoryURINormalizer::TYPE_GIT, PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => - PhabricatorRepositoryURINormalizer::TYPE_SVN, + ArcanistRepositoryURINormalizer::TYPE_SVN, PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => - PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, + ArcanistRepositoryURINormalizer::TYPE_MERCURIAL, ); $type = $map[$vcs]; $display = (string)$this->getDisplayURI(); - $normal_uri = new PhabricatorRepositoryURINormalizer($type, $display); + $normalizer = new ArcanistRepositoryURINormalizer($type, $display); - return $normal_uri->getNormalizedURI(); + $domain_map = self::getURINormalizerDomainMap(); + $normalizer->setDomainMap($domain_map); + + return $normalizer->getNormalizedURI(); } public function getDisplayURI() { @@ -735,4 +738,27 @@ final class PhabricatorRepositoryURI return array(); } + public static function getURINormalizerDomainMap() { + $domain_map = array(); + + // See T13435. If the domain for a repository URI is same as the install + // base URI, store it as a "" token instead of the actual domain + // so that the index does not fall out of date if the install moves. + + $base_uri = PhabricatorEnv::getURI('/'); + $base_uri = new PhutilURI($base_uri); + $base_domain = $base_uri->getDomain(); + $domain_map[''] = $base_domain; + + // Likewise, store a token for the "SSH Host" domain so it can be changed + // without requiring an index rebuild. + + $ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host'); + if (strlen($ssh_host)) { + $domain_map[''] = $ssh_host; + } + + return $domain_map; + } + } From 205657ac76a210553acd6f60821e7ab945577d5e Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Jul 2020 04:03:53 -0700 Subject: [PATCH 07/80] Allow the Fact daemon to hibernate Summary: A handful of Phacility production shards have run into memory pressure issues recently. Although there's no smoking gun, and at least two other plausible contributors, one possible concern is that the Fact daemon was written before hibernation and can not currently hibernate. Even if there's no memory leak, this creates unnecessary memory pressure by holding the processes in memory. Allow the Fact daemon to hibernate, like other daemons do. Test Plan: Ran "bin/phd debug fact", saw the Fact daemon hibernate. Subscribers: yelirekim Differential Revision: https://secure.phabricator.com/D21389 --- .../fact/daemon/PhabricatorFactDaemon.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/applications/fact/daemon/PhabricatorFactDaemon.php b/src/applications/fact/daemon/PhabricatorFactDaemon.php index 29c7904547..4b4f7da368 100644 --- a/src/applications/fact/daemon/PhabricatorFactDaemon.php +++ b/src/applications/fact/daemon/PhabricatorFactDaemon.php @@ -6,7 +6,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon { protected function run() { $this->setEngines(PhabricatorFactEngine::loadAllEngines()); - while (!$this->shouldExit()) { + do { PhabricatorCaches::destroyRequestCache(); $iterators = $this->getAllApplicationIterators(); @@ -14,9 +14,14 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon { $this->processIteratorWithCursor($iterator_name, $iterator); } - $this->log(pht('Zzz...')); - $this->sleep(15); - } + $sleep_duration = 60; + + if ($this->shouldHibernate($sleep_duration)) { + break; + } + + $this->sleep($sleep_duration); + } while (!$this->shouldExit()); } public static function getAllApplicationIterators() { From 56838c0e3dc28a3df73e8ec3cb69792689717672 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 10:42:42 -0700 Subject: [PATCH 08/80] Fix an issue where querying for a large number of projects by slug could paginate incorrectly Summary: See PHI1809. This query may join the "slug" table, but each project may have multiple slugs, and the query does not "GROUP BY" when this join occurs. This may lead to partial result sets and unusual paging behavior. This could likely be caught categorically in `loadAllFromArray()`; I'll adjust this in a followup. Test Plan: A minimal reproduction case is something like: - Give project P slugs: a, b, c. - Give project Q slugs: d. - Query for slugs: a, b, c, d; with limit 2. - Order the query so P returns first. - Expect: P and Q. - Actual: P generates 3 raw rows and the final result is just P with no pagination cursor. Differential Revision: https://secure.phabricator.com/D21399 --- src/applications/project/query/PhabricatorProjectQuery.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 96efcfba4b..22e0b59c94 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -656,6 +656,11 @@ final class PhabricatorProjectQuery if ($this->memberPHIDs || $this->watcherPHIDs || $this->nameTokens) { return true; } + + if ($this->slugs) { + return true; + } + return parent::shouldGroupQueryResultRows(); } From 73c4240415e033571144e486ff67411f68dd2b19 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 8 Jul 2020 13:25:46 -0700 Subject: [PATCH 09/80] Add some additional patterns to the "filter Mercurial --debug output" list Summary: Modern Mercurial may emit some more patterns under "--debug". This whole list is gross and can likely now be eliminated by increasing the minimum required Mercurial version (as `arc` has), but just paper over it for now. Test Plan: Locally, saw some views return to functional behavior that weren't previously working on a modern version of Mercurial. The reproduction case is likely something in the vein of "repository is not writable by webserver, look at history view". Differential Revision: https://secure.phabricator.com/D21398 --- .../diffusion/protocol/DiffusionMercurialCommandEngine.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php index 39d89e3e4c..03705ad49d 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php @@ -68,10 +68,15 @@ final class DiffusionMercurialCommandEngine // http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html // // After Jan 2015, it may also fail to write to a revision branch cache. + // + // Separately, it may fail to write to a different branch cache, and may + // encounter issues reading the branch cache. $ignore = array( 'ignoring untrusted configuration option', "couldn't write revision branch cache:", + "couldn't write branch cache:", + 'invalid branchheads cache', ); foreach ($ignore as $key => $pattern) { From b21b73b8dd35301e083080a5ed7c7c83ae366ca3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 12:23:31 -0700 Subject: [PATCH 10/80] Expand Revision transaction API to allow actions to vary more broadly based on the viewer and revision state Summary: See PHI1810. Build toward support for "Request Review" by non-authors on drafts, to forcefully pull a revision out of draft. Currently, some action strings can't vary based on revision state or the current viewer, so this "pull out of draft" action would have to either: say "Request Review"; or be a totally separate action. Neither seem great, so allow the labels and messages to vary based on the viewer and revision state. Test Plan: Grepped for affected symbols, see followup changes. Differential Revision: https://secure.phabricator.com/D21401 --- .../DifferentialRevisionAbandonTransaction.php | 7 +++++-- .../DifferentialRevisionAcceptTransaction.php | 7 +++++-- .../DifferentialRevisionActionTransaction.php | 18 ++++++++++++------ .../DifferentialRevisionCloseTransaction.php | 7 +++++-- ...fferentialRevisionCommandeerTransaction.php | 7 +++++-- ...ferentialRevisionPlanChangesTransaction.php | 7 +++++-- .../DifferentialRevisionReclaimTransaction.php | 7 +++++-- .../DifferentialRevisionRejectTransaction.php | 7 +++++-- .../DifferentialRevisionReopenTransaction.php | 7 +++++-- ...rentialRevisionRequestReviewTransaction.php | 10 +++++++--- .../DifferentialRevisionResignTransaction.php | 7 +++++-- 11 files changed, 64 insertions(+), 27 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php index c2baef034f..c793663b89 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionAbandonTransaction const TRANSACTIONTYPE = 'differential.revision.abandon'; const ACTIONKEY = 'abandon'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Abandon Revision'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('This revision will be abandoned and closed.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index 3d2df14899..12596d8693 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionAcceptTransaction const TRANSACTIONTYPE = 'differential.revision.accept'; const ACTIONKEY = 'accept'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Accept Revision'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('These changes will be approved.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index 338fde99b2..241678487a 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -17,7 +17,9 @@ abstract class DifferentialRevisionActionTransaction } abstract protected function validateAction($object, PhabricatorUser $viewer); - abstract protected function getRevisionActionLabel(); + abstract protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer); protected function validateOptionValue($object, $actor, array $value) { return null; @@ -53,12 +55,14 @@ abstract class DifferentialRevisionActionTransaction } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return null; } protected function getRevisionActionSubmitButtonText( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return null; } @@ -105,17 +109,19 @@ abstract class DifferentialRevisionActionTransaction ->setValue(true); if ($this->isActionAvailable($revision, $viewer)) { - $label = $this->getRevisionActionLabel(); + $label = $this->getRevisionActionLabel($revision, $viewer); if ($label !== null) { $field->setCommentActionLabel($label); - $description = $this->getRevisionActionDescription($revision); + $description = $this->getRevisionActionDescription($revision, $viewer); $field->setActionDescription($description); $group_key = $this->getRevisionActionGroupKey(); $field->setCommentActionGroupKey($group_key); - $button_text = $this->getRevisionActionSubmitButtonText($revision); + $button_text = $this->getRevisionActionSubmitButtonText( + $revision, + $viewer); $field->setActionSubmitButtonText($button_text); // Currently, every revision action conflicts with every other diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index 1a6017a02f..b1ad04ba77 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionCloseTransaction const TRANSACTIONTYPE = 'differential.revision.close'; const ACTIONKEY = 'close'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Close Revision'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('This revision will be closed.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php index a296597bc7..88f6a7258b 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionCommandeerTransaction const TRANSACTIONTYPE = 'differential.revision.commandeer'; const ACTIONKEY = 'commandeer'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Commandeer Revision'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('You will take control of this revision and become its author.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index f4fb0a3eb1..144152526d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionPlanChangesTransaction const TRANSACTIONTYPE = 'differential.revision.plan'; const ACTIONKEY = 'plan-changes'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Plan Changes'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht( 'This revision will be removed from review queues until it is revised.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php index e023dda0a1..6f9d7b79bf 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionReclaimTransaction const TRANSACTIONTYPE = 'differential.revision.reclaim'; const ACTIONKEY = 'reclaim'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Reclaim Revision'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('This revision will be reclaimed and reopened.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php index 0001ce09ca..cd3e815d26 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionRejectTransaction const TRANSACTIONTYPE = 'differential.revision.reject'; const ACTIONKEY = 'reject'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Request Changes'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('This revision will be returned to the author for updates.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index a2d25287bf..2e7a18fb22 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionReopenTransaction const TRANSACTIONTYPE = 'differential.revision.reopen'; const ACTIONKEY = 'reopen'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Reopen Revision'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('This revision will be reopened for review.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 169e41dec5..026b57b55c 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionRequestReviewTransaction const TRANSACTIONTYPE = 'differential.revision.request'; const ACTIONKEY = 'request-review'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Request Review'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { if ($revision->isDraft()) { return pht('This revision will be submitted to reviewers for feedback.'); } else { @@ -20,7 +23,8 @@ final class DifferentialRevisionRequestReviewTransaction } protected function getRevisionActionSubmitButtonText( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { // See PHI975. When the action stack will promote the revision out of // draft, change the button text from "Submit Quietly". diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index a2b4fd4337..faab748cdd 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -6,12 +6,15 @@ final class DifferentialRevisionResignTransaction const TRANSACTIONTYPE = 'differential.revision.resign'; const ACTIONKEY = 'resign'; - protected function getRevisionActionLabel() { + protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('Resign as Reviewer'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return pht('You will resign as a reviewer for this change.'); } From 6e85b521fe8691944ed2760cb13775ab244edb9d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 13:30:01 -0700 Subject: [PATCH 11/80] Don't raise the "Subscribers Won't Be Notified" draft warning if you aren't adding any non-you subscribers Summary: Currently, adding subscribers to a draft revision raises a warning that they won't get an email/notification. This warning has some false positives: - it triggers on any subscriber change, including removing subscribers; and - it triggers if you're only adding yourself as a subscriber. Narrow the scope of the warning so it is raised only if you're adding a subscriber other than yourself. Test Plan: - Added a non-self subscriber, got the warning as before. - Added self as a subscriber, no warning (previously: warning). - Removed a subscriber, no warning (previously: warning). Differential Revision: https://secure.phabricator.com/D21402 --- ...habricatorApplicationTransactionEditor.php | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index adcc6c7e49..0c5e642e30 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -4968,8 +4968,7 @@ abstract class PhabricatorApplicationTransactionEditor private function hasWarnings($object, $xaction) { // TODO: For the moment, this is a very un-modular hack to support - // exactly one type of warning (mentioning users on a draft revision) - // that we want to show. See PHI433. + // a small number of warnings related to draft revisions. See PHI433. if (!($object instanceof DifferentialRevision)) { return false; @@ -4991,8 +4990,21 @@ abstract class PhabricatorApplicationTransactionEditor return false; } - // NOTE: This will currently warn even if you're only removing - // subscribers. + // We're only going to raise a warning if the transaction adds subscribers + // other than the acting user. (This implementation is clumsy because the + // code runs before a lot of normalization occurs.) + + $old = $this->getTransactionOldValue($object, $xaction); + $new = $this->getPHIDTransactionNewValue($xaction, $old); + $old = array_fuse($old); + $new = array_fuse($new); + $add = array_diff_key($new, $old); + + unset($add[$this->getActingAsPHID()]); + + if (!$add) { + return false; + } return true; } From 1d4d860cb5a0f09cd136495c120a44e02885d5c5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 13:30:05 -0700 Subject: [PATCH 12/80] Allow non-authors to "Request Review" of draft revisions Summary: See PHI1810. In situations where: - An author submits an urgent change for review. - The author pings reviewers to ask them to look at it. ...the reviewers may not be able to move the review forward if the review is currently a "Draft". They can only "Commandeer" or ask the author to "Request Review" as ways forward. Although I'm hesitant to support review actions (particularly, "Accept") on draft revisions, I think there's no harm in allowing reviewers to skip tests and promote the revision out of draft as an explicit action. Additionally, lightly specialize some of the transaction strings to distinguish between "request review from draft" and other state transitions. Test Plan: - As an author, used "Request Review" to promote a draft and to return a change to reviewers for consideration. These behaviors are unchanged, except "promote a draft" has different timeline text. - As a non-author, used "Begin Review" to promote a draft. Differential Revision: https://secure.phabricator.com/D21403 --- .../DifferentialRevisionActionTransaction.php | 11 ++ ...entialRevisionRequestReviewTransaction.php | 120 +++++++++++++++--- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index 241678487a..b1f51e77e7 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -66,6 +66,12 @@ abstract class DifferentialRevisionActionTransaction return null; } + protected function getRevisionActionMetadata( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + return array(); + } + public static function loadAllActions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) @@ -150,6 +156,11 @@ abstract class DifferentialRevisionActionTransaction $field->setOptions($options); $field->setValue($value); } + + $metadata = $this->getRevisionActionMetadata($revision, $viewer); + foreach ($metadata as $metadata_key => $metadata_value) { + $field->setMetadataValue($metadata_key, $metadata_value); + } } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 026b57b55c..32a3ab4a73 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -6,9 +6,25 @@ final class DifferentialRevisionRequestReviewTransaction const TRANSACTIONTYPE = 'differential.revision.request'; const ACTIONKEY = 'request-review'; + const SOURCE_HARBORMASTER = 'harbormaster'; + const SOURCE_AUTHOR = 'author'; + const SOURCE_VIEWER = 'viewer'; + protected function getRevisionActionLabel( DifferentialRevision $revision, PhabricatorUser $viewer) { + + // See PHI1810. Allow non-authors to "Request Review" on draft revisions + // to promote them out of the draft state. This smoothes over the workflow + // where an author asks for review of an urgent change but has not used + // "Request Review" to skip builds. + + if ($revision->isDraft()) { + if (!$this->isViewerRevisionAuthor($revision, $viewer)) { + return pht('Begin Review Now'); + } + } + return pht('Request Review'); } @@ -16,12 +32,34 @@ final class DifferentialRevisionRequestReviewTransaction DifferentialRevision $revision, PhabricatorUser $viewer) { if ($revision->isDraft()) { - return pht('This revision will be submitted to reviewers for feedback.'); + if (!$this->isViewerRevisionAuthor($revision, $viewer)) { + return pht( + 'This revision will be moved out of the draft state so you can '. + 'review it immediately.'); + } else { + return pht( + 'This revision will be submitted to reviewers for feedback.'); + } } else { return pht('This revision will be returned to reviewers for feedback.'); } } + protected function getRevisionActionMetadata( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + $map = array(); + + if ($revision->isDraft()) { + $action_source = $this->getActorSourceType( + $revision, + $viewer); + $map['promotion.source'] = $action_source; + } + + return $map; + } + protected function getRevisionActionSubmitButtonText( DifferentialRevision $revision, PhabricatorUser $viewer) { @@ -76,29 +114,43 @@ final class DifferentialRevisionRequestReviewTransaction 'revisions.')); } - // When revisions automatically promote out of "Draft" after builds finish, - // the viewer may be acting as the Harbormaster application. - if (!$viewer->isOmnipotent()) { - if (!$this->isViewerRevisionAuthor($object, $viewer)) { - throw new Exception( - pht( - 'You can not request review of this revision because you are not '. - 'the author of the revision.')); - } - } + $this->getActorSourceType($object, $viewer); } public function getTitle() { - return pht( - '%s requested review of this revision.', - $this->renderAuthor()); + $source = $this->getDraftPromotionSource(); + + switch ($source) { + case self::SOURCE_HARBORMASTER: + case self::SOURCE_VIEWER: + case self::SOURCE_AUTHOR: + return pht( + '%s published this revision for review.', + $this->renderAuthor()); + default: + return pht( + '%s requested review of this revision.', + $this->renderAuthor()); + } } public function getTitleForFeed() { - return pht( - '%s requested review of %s.', - $this->renderAuthor(), - $this->renderObject()); + $source = $this->getDraftPromotionSource(); + + switch ($source) { + case self::SOURCE_HARBORMASTER: + case self::SOURCE_VIEWER: + case self::SOURCE_AUTHOR: + return pht( + '%s published %s for review.', + $this->renderAuthor(), + $this->renderObject()); + default: + return pht( + '%s requested review of %s.', + $this->renderAuthor(), + $this->renderObject()); + } } public function getTransactionTypeForConduit($xaction) { @@ -109,4 +161,36 @@ final class DifferentialRevisionRequestReviewTransaction return array(); } + private function getDraftPromotionSource() { + return $this->getMetadataValue('promotion.source'); + } + + private function getActorSourceType( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + + $is_harbormaster = $viewer->isOmnipotent(); + $is_author = $this->isViewerRevisionAuthor($revision, $viewer); + $is_draft = $revision->isDraft(); + + if ($is_harbormaster) { + // When revisions automatically promote out of "Draft" after builds + // finish, the viewer may be acting as the Harbormaster application. + $source = self::SOURCE_HARBORMASTER; + } else if ($is_author) { + $source = self::SOURCE_AUTHOR; + } else if ($is_draft) { + // Non-authors are allowed to "Request Review" on draft revisions, to + // force them into review immediately. + $source = self::SOURCE_VIEWER; + } else { + throw new Exception( + pht( + 'You can not request review of this revision because you are not '. + 'the author of the revision and it is not currently a draft.')); + } + + return $source; + } + } From 37ffb71c4d5efdb25955105f627849690af0ba4f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 17 Jul 2020 14:56:39 -0700 Subject: [PATCH 13/80] In source views, wrap display tabs in "user-select: all" to improve cursor selection behavior Summary: Ref T2495. See PHI1814. Currently, Phabricator replaces tabs with spaces when rendering diffs. This may or may not be the best behavior in the long term, but it gives us more control over expansion of tabs than using tab literals. However, one downside is that you can use your mouse cursor to select "half a tab", and can't use your mouse cursor to distinguish between tabs and spaces. Although you probably shouldn't be doing this, this behavior is less accurate/correct than selecting the entire block as a single unit. A specific correctness issue with this behavior is that the entire block is copied to the clipboard as a tab literal if you select any of it, so two different visual selection ranges can produce the same clipboard content. This particular behavior can be improved with "user-select: all", to instruct browsers to select the entire element as a single logical element. Now, selecting part of the tab selects the whole thing, as though it were really a tab literal. (Some future change might abandon this approach and opt to use real tab literals with "tab-size" CSS, but we lose some ability to control alignment behavior if we do that and it doesn't have any obvious advantages over this approach other than cursor selection behavior.) Test Plan: - In Safari and Firefox, dragged text to select a whitespace-expanded tab literal. Saw browsers select the whole sequence as though it were a single tab. - In Chorme, this also mostly works, but there's some glitchiness and flickering. I think this is still a net improvement, it's just not as smooth as Safari and Firefox. Maniphest Tasks: T2495 Differential Revision: https://secure.phabricator.com/D21419 --- resources/celerity/map.php | 12 ++++++------ .../parser/DifferentialChangesetParser.php | 7 +++++++ .../__tests__/DifferentialTabReplacementTestCase.php | 4 ++-- webroot/rsrc/css/core/syntax.css | 5 +++++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d10f117847..77242a1384 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'da792a0f', + 'core.pkg.css' => '2e175364', 'core.pkg.js' => '845355f4', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -115,7 +115,7 @@ return array( 'rsrc/css/application/uiexample/example.css' => 'b4795059', 'rsrc/css/core/core.css' => '1b29ed61', 'rsrc/css/core/remarkup.css' => '7d3ebc86', - 'rsrc/css/core/syntax.css' => '548567f6', + 'rsrc/css/core/syntax.css' => '98fdb17e', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', 'rsrc/css/diviner/diviner-shared.css' => '4bd263b0', 'rsrc/css/font/font-awesome.css' => '3883938a', @@ -909,7 +909,7 @@ return array( 'sprite-login-css' => '18b368a6', 'sprite-tokens-css' => 'f1896dc5', 'syntax-default-css' => '055fc231', - 'syntax-highlighting-css' => '548567f6', + 'syntax-highlighting-css' => '98fdb17e', 'tokens-css' => 'ce5a50bd', 'trigger-rule' => '41b7b4f6', 'trigger-rule-control' => '5faf27b9', @@ -1422,9 +1422,6 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), - '548567f6' => array( - 'syntax-default-css', - ), '55a24e84' => array( 'javelin-install', 'javelin-dom', @@ -1803,6 +1800,9 @@ return array( 'javelin-request', 'javelin-util', ), + '98fdb17e' => array( + 'syntax-default-css', + ), '995f5102' => array( 'javelin-install', 'javelin-util', diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index b7ea61b7a9..45da82118f 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1600,6 +1600,13 @@ final class DifferentialChangesetParser extends Phobject { 'span', array( 'data-copy-text' => "\t", + + // See PHI1814. Mark this as a single logical tab for the purposes + // of text selection behavior: when the user drags their mouse over + // the character sequence, we'd like the whole thing to select as + // a single unit. + + 'class' => 'logical-tab', ), str_repeat(' ', $ii)); $tag = phutil_string_cast($tag); diff --git a/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php b/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php index d63978cb61..494170c668 100644 --- a/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php +++ b/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php @@ -4,8 +4,8 @@ final class DifferentialTabReplacementTestCase extends PhabricatorTestCase { public function testTabReplacement() { - $tab1 = " "; - $tab2 = " "; + $tab1 = " "; + $tab2 = " "; $cat = "\xF0\x9F\x90\xB1"; diff --git a/webroot/rsrc/css/core/syntax.css b/webroot/rsrc/css/core/syntax.css index 78aa83dbdc..822e1777d6 100644 --- a/webroot/rsrc/css/core/syntax.css +++ b/webroot/rsrc/css/core/syntax.css @@ -39,3 +39,8 @@ span.crossreference-item { color: #ffffff; cursor: default; } + +.logical-tab { + user-select: all; + -webkit-user-select: all; +} From 0ed5569e9f7b4ed7220ac559144fdb42488a68e9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 17 Jul 2020 19:42:45 -0700 Subject: [PATCH 14/80] Likely, fix a warning when rendering modified coverage Summary: See PHI1819. This structure may have `null` elements. Test Plan: Will confirm user reproduction case. Differential Revision: https://secure.phabricator.com/D21420 --- .../differential/parser/DifferentialChangesetParser.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index 45da82118f..1cc1c3ad9d 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1327,6 +1327,10 @@ final class DifferentialChangesetParser extends Phobject { $not_covered = 0; foreach ($this->new as $k => $new) { + if ($new === null) { + continue; + } + if (!$new['line']) { continue; } From 8f9ba4852861e7628a4e8052afd982efe2817d67 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Jul 2020 11:54:06 -0700 Subject: [PATCH 15/80] Fix an issue with destruction of Revision and Diff objects with viewstates Summary: See . These queries aren't actually constructed properly, and destroying a revision or diff with viewstates currently fails. Test Plan: Used `bin/remove destroy Dxxx` to destroy a revision with viewstates (this also destroys the associated diffs). Differential Revision: https://secure.phabricator.com/D21421 --- src/applications/differential/storage/DifferentialDiff.php | 3 ++- src/applications/differential/storage/DifferentialRevision.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 747407ce75..f5cb91c56b 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -737,9 +737,10 @@ final class DifferentialDiff $prop->delete(); } - $viewstates = id(new DifferentialViewStateQuery()) + $viewstate_query = id(new DifferentialViewStateQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($this->getPHID())); + $viewstates = new PhabricatorQueryIterator($viewstate_query); foreach ($viewstates as $viewstate) { $viewstate->delete(); } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 2ab9d59821..15cf219f7b 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -1033,9 +1033,10 @@ final class DifferentialRevision extends DifferentialDAO $dummy_path->getTableName(), $this->getID()); - $viewstates = id(new DifferentialViewStateQuery()) + $viewstate_query = id(new DifferentialViewStateQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($this->getPHID())); + $viewstates = new PhabricatorQueryIterator($viewstate_query); foreach ($viewstates as $viewstate) { $viewstate->delete(); } From fcb75d0503c6d3e21461cdee91c01022bb6b0bab Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 23 Jul 2020 07:35:57 -0700 Subject: [PATCH 16/80] Fix an issue where prose diffing may fail after hitting the PCRE backtracking limit Summary: Fixes T13554. For certain prose diff inputs and PCRE backtracking limits, this regular expression may back track too often and fail. A characteristic input is "x x x x ...", i.e. many sequences where `(.*?)\s*\z` looks like it may be able to match but actually can not. I think writing an expression which has all the behavior we'd like without this backtracking issue isn't trivial (at least, I don't think I know how to do it offhand); just use a strategy based on "trim()" insetad, which avoids any PCRE complexities here. Test Plan: Locally, this passes the "x x x ..." test which the previous code failed. I'm not including that test because it won't reproduce across values of "pcre.backtrac_limit", PCRE versions, etc. Maniphest Tasks: T13554 Differential Revision: https://secure.phabricator.com/D21422 --- .../prose/PhutilProseDifferenceEngine.php | 51 +++++++++++++------ .../__tests__/PhutilProseDiffTestCase.php | 33 ++++++++++++ 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/infrastructure/diff/prose/PhutilProseDifferenceEngine.php b/src/infrastructure/diff/prose/PhutilProseDifferenceEngine.php index a5494aa05f..b8bee73961 100644 --- a/src/infrastructure/diff/prose/PhutilProseDifferenceEngine.php +++ b/src/infrastructure/diff/prose/PhutilProseDifferenceEngine.php @@ -142,22 +142,9 @@ final class PhutilProseDifferenceEngine extends Phobject { } if ($level < 2) { - // Split pieces into separate text and whitespace sections: make one - // piece out of all the whitespace at the beginning, one piece out of - // all the actual text in the middle, and one piece out of all the - // whitespace at the end. - - $matches = null; - preg_match('/^(\s*)(.*?)(\s*)\z/s', $result, $matches); - - if (strlen($matches[1])) { - $results[] = $matches[1]; - } - if (strlen($matches[2])) { - $results[] = $matches[2]; - } - if (strlen($matches[3])) { - $results[] = $matches[3]; + $trimmed_pieces = $this->trimApart($result); + foreach ($trimmed_pieces as $trimmed_piece) { + $results[] = $trimmed_piece; } } else { $results[] = $result; @@ -272,4 +259,36 @@ final class PhutilProseDifferenceEngine extends Phobject { return $blocks; } + public static function trimApart($input) { + // Split pieces into separate text and whitespace sections: make one + // piece out of all the whitespace at the beginning, one piece out of + // all the actual text in the middle, and one piece out of all the + // whitespace at the end. + + $parts = array(); + + $length = strlen($input); + + $corpus = ltrim($input); + $l_length = strlen($corpus); + if ($l_length !== $length) { + $parts[] = substr($input, 0, $length - $l_length); + } + + $corpus = rtrim($corpus); + $lr_length = strlen($corpus); + + if ($lr_length) { + $parts[] = $corpus; + } + + if ($lr_length !== $l_length) { + // NOTE: This will be a negative value; we're slicing from the end of + // the input string. + $parts[] = substr($input, $lr_length - $l_length); + } + + return $parts; + } + } diff --git a/src/infrastructure/diff/prose/__tests__/PhutilProseDiffTestCase.php b/src/infrastructure/diff/prose/__tests__/PhutilProseDiffTestCase.php index 823fef650a..d2e11cac5b 100644 --- a/src/infrastructure/diff/prose/__tests__/PhutilProseDiffTestCase.php +++ b/src/infrastructure/diff/prose/__tests__/PhutilProseDiffTestCase.php @@ -3,6 +3,39 @@ final class PhutilProseDiffTestCase extends PhabricatorTestCase { + public function testTrimApart() { + $map = array( + '' => array(), + 'a' => array('a'), + ' a ' => array( + ' ', + 'a', + ' ', + ), + ' a' => array( + ' ', + 'a', + ), + 'a ' => array( + 'a', + ' ', + ), + ' a b ' => array( + ' ', + 'a b', + ' ', + ), + ); + + foreach ($map as $input => $expect) { + $actual = PhutilProseDifferenceEngine::trimApart($input); + $this->assertEqual( + $expect, + $actual, + pht('Trim Apart: %s', $input)); + } + } + public function testProseDiffsDistance() { $this->assertProseParts( '', From 5f0535934d72f0c05ae56c653dd8cc7dd29e3ba4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 23 Jul 2020 09:27:14 -0700 Subject: [PATCH 17/80] Manage PIDs more carefully in DaemonHandle Summary: Ref T13555. Although these callsites may not actually impact anything, it's possible for an active handle to have no PID (e.g., if the subprocess failed to start). Handle these cases more carefully. Test Plan: Started daemons, saw them run fine. See also next change. Maniphest Tasks: T13555 Differential Revision: https://secure.phabricator.com/D21424 --- .../daemon/PhutilDaemonHandle.php | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/infrastructure/daemon/PhutilDaemonHandle.php b/src/infrastructure/daemon/PhutilDaemonHandle.php index 5030f5330c..25c517b8cf 100644 --- a/src/infrastructure/daemon/PhutilDaemonHandle.php +++ b/src/infrastructure/daemon/PhutilDaemonHandle.php @@ -327,13 +327,14 @@ final class PhutilDaemonHandle extends Phobject { private function annihilateProcessGroup() { $pid = $this->getPID(); - - $pgid = posix_getpgid($pid); - if ($pid && $pgid) { - posix_kill(-$pgid, SIGTERM); - sleep($this->getKillDelay()); - posix_kill(-$pgid, SIGKILL); - $this->pid = null; + if ($pid) { + $pgid = posix_getpgid($pid); + if ($pgid) { + posix_kill(-$pgid, SIGTERM); + sleep($this->getKillDelay()); + posix_kill(-$pgid, SIGKILL); + $this->pid = null; + } } } @@ -440,7 +441,10 @@ final class PhutilDaemonHandle extends Phobject { // naturally be restarted after it exits, as though it had exited after an // unhandled exception. - posix_kill($this->getPID(), SIGINT); + $pid = $this->getPID(); + if ($pid) { + posix_kill($pid, SIGINT); + } } public function didReceiveGracefulSignal($signo) { @@ -461,7 +465,10 @@ final class PhutilDaemonHandle extends Phobject { $this->logMessage('DONE', $sigmsg, $signo); - posix_kill($this->getPID(), SIGINT); + $pid = $this->getPID(); + if ($pid) { + posix_kill($pid, SIGINT); + } } public function didReceiveTerminateSignal($signo) { From 78d1b62bb8e9b5d90b005626a1635718c5273ce3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 23 Jul 2020 09:11:30 -0700 Subject: [PATCH 18/80] Streamline handling of Futures and PIDs in daemons Summary: Ref T13555. Currently, the daemon future may resolve into a failure state immediately inside "start()", and not have a valid PID when we read it. Instead, read PIDs from the current active future in all cases, using "hasPID()" to test for the presence of a valid PID. Since we don't query the PID immediately, we no longer need to explicitly start the future. Also fix an issue where the same future could be added to the overseer pool more than once if it threw on "resolve()". In general: - Before we "resolve()" a future, detach it from the DaemonHandle: we're always done with it. - Catch exceptions on resolution and treat them the same way as subprocess resolution errors. These aren't common, but are possible in the general case. - Have DaemonHandle add futures to the future pool directly when they're created. Test Plan: - Ran daemons with intentional subprocess creation failures, saw clean recovery. - Ran daemons with intentional resolution exceptions, saw clean recovery. Maniphest Tasks: T13555 Differential Revision: https://secure.phabricator.com/D21425 --- .../daemon/PhutilDaemonHandle.php | 67 +++++++++++++------ .../daemon/PhutilDaemonOverseer.php | 9 +-- .../daemon/PhutilDaemonPool.php | 12 ---- 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/infrastructure/daemon/PhutilDaemonHandle.php b/src/infrastructure/daemon/PhutilDaemonHandle.php index 25c517b8cf..428a64a056 100644 --- a/src/infrastructure/daemon/PhutilDaemonHandle.php +++ b/src/infrastructure/daemon/PhutilDaemonHandle.php @@ -16,7 +16,6 @@ final class PhutilDaemonHandle extends Phobject { private $restartAt; private $busyEpoch; - private $pid; private $daemonID; private $deadline; private $heartbeat; @@ -104,7 +103,7 @@ final class PhutilDaemonHandle extends Phobject { } public function isRunning() { - return (bool)$this->future; + return (bool)$this->getFuture(); } public function isHibernating() { @@ -134,10 +133,6 @@ final class PhutilDaemonHandle extends Phobject { return (!$this->shouldRestart && !$this->isRunning()); } - public function getFuture() { - return $this->future; - } - public function update() { if (!$this->isRunning()) { if (!$this->shouldRestart) { @@ -152,11 +147,19 @@ final class PhutilDaemonHandle extends Phobject { $this->startDaemonProcess(); } - $future = $this->future; + $future = $this->getFuture(); $result = null; - if ($future->isReady()) { - $result = $future->resolve(); + $caught = null; + if ($future->canResolve()) { + $this->future = null; + try { + $result = $future->resolve(); + } catch (Exception $ex) { + $caught = $ex; + } catch (Throwable $ex) { + $caught = $ex; + } } list($stdout, $stderr) = $future->read(); @@ -173,16 +176,22 @@ final class PhutilDaemonHandle extends Phobject { } } - if ($result !== null) { - list($err) = $result; + if ($result !== null || $caught !== null) { - if ($err) { - $this->logMessage('FAIL', pht('Process exited with error %s.', $err)); + if ($caught) { + $message = pht( + 'Process failed with exception: %s', + $caught->getMessage()); + $this->logMessage('FAIL', $message); } else { - $this->logMessage('DONE', pht('Process exited normally.')); - } + list($err) = $result; - $this->future = null; + if ($err) { + $this->logMessage('FAIL', pht('Process exited with error %s.', $err)); + } else { + $this->logMessage('DONE', pht('Process exited normally.')); + } + } if ($this->shouldShutdown) { $this->restartAt = null; @@ -244,8 +253,22 @@ final class PhutilDaemonHandle extends Phobject { return $this->daemonID; } - public function getPID() { - return $this->pid; + private function getFuture() { + return $this->future; + } + + private function getPID() { + $future = $this->getFuture(); + + if (!$future) { + return null; + } + + if (!$future->hasPID()) { + return null; + } + + return $future->getPID(); } private function getCaptureBufferSize() { @@ -346,10 +369,12 @@ final class PhutilDaemonHandle extends Phobject { $this->stdoutBuffer = ''; $this->hibernating = false; - $this->future = $this->newExecFuture(); - $this->future->start(); + $future = $this->newExecFuture(); + $this->future = $future; - $this->pid = $this->future->getPID(); + $pool = $this->getDaemonPool(); + $overseer = $pool->getOverseer(); + $overseer->addFutureToPool($future); } private function didReadStdout($data) { diff --git a/src/infrastructure/daemon/PhutilDaemonOverseer.php b/src/infrastructure/daemon/PhutilDaemonOverseer.php index 77f6f6a20a..974a9cf3ed 100644 --- a/src/infrastructure/daemon/PhutilDaemonOverseer.php +++ b/src/infrastructure/daemon/PhutilDaemonOverseer.php @@ -181,10 +181,6 @@ EOHELP } } - foreach ($pool->getFutures() as $future) { - $future_pool->addFuture($future); - } - if ($pool->getDaemons()) { $running_pools = true; } @@ -210,6 +206,11 @@ EOHELP exit($this->err); } + public function addFutureToPool(Future $future) { + $this->getFuturePool()->addFuture($future); + return $this; + } + private function getFuturePool() { if (!$this->futurePool) { $pool = new FuturePool(); diff --git a/src/infrastructure/daemon/PhutilDaemonPool.php b/src/infrastructure/daemon/PhutilDaemonPool.php index 50b22289a8..9cdc15228e 100644 --- a/src/infrastructure/daemon/PhutilDaemonPool.php +++ b/src/infrastructure/daemon/PhutilDaemonPool.php @@ -111,18 +111,6 @@ final class PhutilDaemonPool extends Phobject { return $this->daemons; } - public function getFutures() { - $futures = array(); - foreach ($this->getDaemons() as $daemon) { - $future = $daemon->getFuture(); - if ($future) { - $futures[] = $future; - } - } - - return $futures; - } - public function didReceiveSignal($signal, $signo) { switch ($signal) { case PhutilDaemonOverseer::SIGNAL_GRACEFUL: From a27c83757d9fd73b1d9f79b0ae4268a636f35b6f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 23 Jul 2020 12:17:56 -0700 Subject: [PATCH 19/80] Remove ancient "phd.trace" and "phd.verbose" configuration options Summary: Ref T13556. These options are very old and effectively obsoleted by "bin/phd debug [--trace]". I haven't used either option diagnostically in many years, and they aren't mentioned in the documentation. Remove them to simplify configuration, and because "phd.trace" doesn't work anyway and likely hasn't for a long time -- it has specific issues with TTY detection (see T13556). Test Plan: Grepped for "phd.trace" and "phd.verbose". Ran "bin/phd debug [--trace]" and saw verbose/trace output. Maniphest Tasks: T13556 Differential Revision: https://secure.phabricator.com/D21426 --- .../check/PhabricatorDaemonsSetupCheck.php | 21 ++++++++---- .../PhabricatorExtraConfigSetupCheck.php | 6 ++++ .../option/PhabricatorPHDConfigOptions.php | 32 ------------------- .../PhabricatorDaemonManagementWorkflow.php | 4 +-- .../workers/PhabricatorTaskmasterDaemon.php | 2 +- 5 files changed, 23 insertions(+), 42 deletions(-) diff --git a/src/applications/config/check/PhabricatorDaemonsSetupCheck.php b/src/applications/config/check/PhabricatorDaemonsSetupCheck.php index 331a4f0908..7ef44dc384 100644 --- a/src/applications/config/check/PhabricatorDaemonsSetupCheck.php +++ b/src/applications/config/check/PhabricatorDaemonsSetupCheck.php @@ -8,14 +8,21 @@ final class PhabricatorDaemonsSetupCheck extends PhabricatorSetupCheck { protected function executeChecks() { - $task_daemon = id(new PhabricatorDaemonLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) - ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) - ->setLimit(1) - ->execute(); + try { + $task_daemons = id(new PhabricatorDaemonLogQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) + ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) + ->setLimit(1) + ->execute(); - if (!$task_daemon) { + $no_daemons = !$task_daemons; + } catch (Exception $ex) { + // Just skip this warning if the query fails for some reason. + $no_daemons = false; + } + + if ($no_daemons) { $doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd'); $summary = pht( diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index d863c928b7..1de39f3468 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -322,6 +322,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'directly supported. Prefixes and other strings may be customized with '. '"translation.override".'); + $phd_reason = pht( + 'Use "bin/phd debug ..." to get a detailed daemon execution log.'); + $ancient_config += array( 'phid.external-loaders' => pht( @@ -539,6 +542,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'phd.pid-directory' => pht( 'Phabricator daemons no longer use PID files.'), + + 'phd.trace' => $phd_reason, + 'phd.verbose' => $phd_reason, ); return $ancient_config; diff --git a/src/applications/config/option/PhabricatorPHDConfigOptions.php b/src/applications/config/option/PhabricatorPHDConfigOptions.php index 7a1d39e617..259660d562 100644 --- a/src/applications/config/option/PhabricatorPHDConfigOptions.php +++ b/src/applications/config/option/PhabricatorPHDConfigOptions.php @@ -43,22 +43,6 @@ final class PhabricatorPHDConfigOptions "configuration changes are picked up by the daemons ". "automatically, but pool sizes can not be changed without a ". "restart.")), - $this->newOption('phd.verbose', 'bool', false) - ->setLocked(true) - ->setBoolOptions( - array( - pht('Verbose mode'), - pht('Normal mode'), - )) - ->setSummary(pht("Launch daemons in 'verbose' mode by default.")) - ->setDescription( - pht( - "Launch daemons in 'verbose' mode by default. This creates a lot ". - "of output, but can help debug issues. Daemons launched in debug ". - "mode with '%s' are always launched in verbose mode. ". - "See also '%s'.", - 'phd debug', - 'phd.trace')), $this->newOption('phd.user', 'string', null) ->setLocked(true) ->setSummary(pht('System user to run daemons as.')) @@ -68,22 +52,6 @@ final class PhabricatorPHDConfigOptions 'user will own the working copies of any repositories that '. 'Phabricator imports or manages. This option is new and '. 'experimental.')), - $this->newOption('phd.trace', 'bool', false) - ->setLocked(true) - ->setBoolOptions( - array( - pht('Trace mode'), - pht('Normal mode'), - )) - ->setSummary(pht("Launch daemons in 'trace' mode by default.")) - ->setDescription( - pht( - "Launch daemons in 'trace' mode by default. This creates an ". - "ENORMOUS amount of output, but can help debug issues. Daemons ". - "launched in debug mode with '%s' are always launched in ". - "trace mode. See also '%s'.", - 'phd debug', - 'phd.verbose')), $this->newOption('phd.garbage-collection', 'wild', array()) ->setLocked(true) ->setLockedMessage( diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php index 05d94e218d..b9645323c2 100644 --- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php @@ -116,11 +116,11 @@ abstract class PhabricatorDaemonManagementWorkflow $trace = PhutilArgumentParser::isTraceModeEnabled(); $flags = array(); - if ($trace || PhabricatorEnv::getEnvConfig('phd.trace')) { + if ($trace) { $flags[] = '--trace'; } - if ($debug || PhabricatorEnv::getEnvConfig('phd.verbose')) { + if ($debug) { $flags[] = '--verbose'; } diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index c2276843db..12b06131d8 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -24,7 +24,7 @@ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { if ($ex) { if ($ex instanceof PhabricatorWorkerPermanentFailureException) { // NOTE: Make sure these reach the daemon log, even when not - // running in "phd.verbose" mode. See T12803 for discussion. + // running in verbose mode. See T12803 for discussion. $log_exception = new PhutilProxyException( pht( 'Task "%s" encountered a permanent failure and was '. From 017ef1927c3e1172ea9ab21ba31f6593418db4b7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 24 Jul 2020 13:35:12 -0700 Subject: [PATCH 20/80] Revert use of "user-select: all" to modify tab selection behavior Summary: Reverts D21419. See PHI1814. Previously, I used "user-select: all" to group sequences of spaces for selection. However, this has a side effect: the sequence is now selected with a single click. I didn't read the docuementation on the CSS property thoroughly and missed this in testing, since I was focused on drag-selection behavior. This behavior is enough of a net negative that I think we're in a worse state overall; revert it. Test Plan: Straight revert. Differential Revision: https://secure.phabricator.com/D21429 --- resources/celerity/map.php | 12 ++++++------ .../parser/DifferentialChangesetParser.php | 7 ------- .../__tests__/DifferentialTabReplacementTestCase.php | 4 ++-- webroot/rsrc/css/core/syntax.css | 5 ----- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 77242a1384..d10f117847 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '2e175364', + 'core.pkg.css' => 'da792a0f', 'core.pkg.js' => '845355f4', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -115,7 +115,7 @@ return array( 'rsrc/css/application/uiexample/example.css' => 'b4795059', 'rsrc/css/core/core.css' => '1b29ed61', 'rsrc/css/core/remarkup.css' => '7d3ebc86', - 'rsrc/css/core/syntax.css' => '98fdb17e', + 'rsrc/css/core/syntax.css' => '548567f6', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', 'rsrc/css/diviner/diviner-shared.css' => '4bd263b0', 'rsrc/css/font/font-awesome.css' => '3883938a', @@ -909,7 +909,7 @@ return array( 'sprite-login-css' => '18b368a6', 'sprite-tokens-css' => 'f1896dc5', 'syntax-default-css' => '055fc231', - 'syntax-highlighting-css' => '98fdb17e', + 'syntax-highlighting-css' => '548567f6', 'tokens-css' => 'ce5a50bd', 'trigger-rule' => '41b7b4f6', 'trigger-rule-control' => '5faf27b9', @@ -1422,6 +1422,9 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), + '548567f6' => array( + 'syntax-default-css', + ), '55a24e84' => array( 'javelin-install', 'javelin-dom', @@ -1800,9 +1803,6 @@ return array( 'javelin-request', 'javelin-util', ), - '98fdb17e' => array( - 'syntax-default-css', - ), '995f5102' => array( 'javelin-install', 'javelin-util', diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index 1cc1c3ad9d..3652d18a9a 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1604,13 +1604,6 @@ final class DifferentialChangesetParser extends Phobject { 'span', array( 'data-copy-text' => "\t", - - // See PHI1814. Mark this as a single logical tab for the purposes - // of text selection behavior: when the user drags their mouse over - // the character sequence, we'd like the whole thing to select as - // a single unit. - - 'class' => 'logical-tab', ), str_repeat(' ', $ii)); $tag = phutil_string_cast($tag); diff --git a/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php b/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php index 494170c668..d63978cb61 100644 --- a/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php +++ b/src/applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php @@ -4,8 +4,8 @@ final class DifferentialTabReplacementTestCase extends PhabricatorTestCase { public function testTabReplacement() { - $tab1 = " "; - $tab2 = " "; + $tab1 = " "; + $tab2 = " "; $cat = "\xF0\x9F\x90\xB1"; diff --git a/webroot/rsrc/css/core/syntax.css b/webroot/rsrc/css/core/syntax.css index 822e1777d6..78aa83dbdc 100644 --- a/webroot/rsrc/css/core/syntax.css +++ b/webroot/rsrc/css/core/syntax.css @@ -39,8 +39,3 @@ span.crossreference-item { color: #ffffff; cursor: default; } - -.logical-tab { - user-select: all; - -webkit-user-select: all; -} From c1eeacd8500b6f3e449b47d7f7bf8dfad7f95818 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Jul 2020 12:37:34 -0700 Subject: [PATCH 21/80] Move "Wait for Previous Commits to Build" out of prototype Summary: Although I'm not entirely thrilled about doing flow control like this (as an actual action in a build plan), I believe this build step works correctly and there's no fancy replacement mechanism on the immediate horizon, and this didn't send us down a slippery slope of Turing-complete builds encoded without real structure or context. Just kick it out of prototype. (Other approaches which might be better in the long run are things like "this is a top-level behavior on the build plan itself" and/or "build plans are written in a DSL, not a Javascript UI".) Test Plan: Added a new build step, saw this as an option in the "Flow Control" section. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D21432 --- .../step/HarbormasterWaitForPreviousBuildStepImplementation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index eadf4c94f5..cec25c6ad3 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -14,7 +14,7 @@ final class HarbormasterWaitForPreviousBuildStepImplementation } public function getBuildStepGroupKey() { - return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + return HarbormasterControlBuildStepGroup::GROUPKEY; } public function execute( From 98e0440d459b7a64b02d7976372aa2fca499f8bd Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Aug 2020 09:46:51 -0700 Subject: [PATCH 22/80] In 1-up source diffs, retain the "No newline at end of file" on "\" lines Summary: See PHI1839. Currently, the "No newline at end of file" text is dropped in the 1-up diff view for changes that affect a file with no trailing newline. Track it through the construction of diff primitivies more carefully. Test Plan: {F7695760} Differential Revision: https://secure.phabricator.com/D21433 --- .../render/DifferentialChangesetOneUpRenderer.php | 8 ++++++-- .../differential/render/DifferentialChangesetRenderer.php | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index 186924e359..bcdbf4dd46 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -83,7 +83,9 @@ final class DifferentialChangesetOneUpRenderer $cells = array(); if ($is_old) { if ($p['htype']) { - if (empty($p['oline'])) { + if ($p['htype'] === '\\') { + $class = 'comment'; + } else if (empty($p['oline'])) { $class = 'left old old-full'; } else { $class = 'left old'; @@ -129,7 +131,9 @@ final class DifferentialChangesetOneUpRenderer $cells[] = $no_coverage; } else { if ($p['htype']) { - if (empty($p['oline'])) { + if ($p['htype'] === '\\') { + $class = 'comment'; + } else if (empty($p['oline'])) { $class = 'right new new-full'; } else { $class = 'right new'; diff --git a/src/applications/differential/render/DifferentialChangesetRenderer.php b/src/applications/differential/render/DifferentialChangesetRenderer.php index d493c0e886..dcae3b979b 100644 --- a/src/applications/differential/render/DifferentialChangesetRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetRenderer.php @@ -505,6 +505,8 @@ abstract class DifferentialChangesetRenderer extends Phobject { $ospec['htype'] = $old[$ii]['type']; if (isset($old_render[$ii])) { $ospec['render'] = $old_render[$ii]; + } else if ($ospec['htype'] === '\\') { + $ospec['render'] = $old[$ii]['text']; } } @@ -514,6 +516,8 @@ abstract class DifferentialChangesetRenderer extends Phobject { $nspec['htype'] = $new[$ii]['type']; if (isset($new_render[$ii])) { $nspec['render'] = $new_render[$ii]; + } else if ($nspec['htype'] === '\\') { + $nspec['render'] = $new[$ii]['text']; } } From 2db1955159f78ec731243b8532a1b3603eb28508 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Aug 2020 12:15:17 -0700 Subject: [PATCH 23/80] In Jupyter notebooks, read strings stored in the raw as either "string" or "list" more consistently Summary: Ref PHI1835. Generally, Jupyter notebooks in the wild may store source and markdown content as either a single string or a list of strings. Make the renderer read these formats more consistently. In particular, this fixes rendering of code blocks stored as a single string. This also fixes an issue where cell labels were double-rendered in diff views. Test Plan: Created a notebook with a code block represented on disk as a single string, rendered a diff from it. {F7696071} Differential Revision: https://secure.phabricator.com/D21434 --- .../PhabricatorJupyterDocumentEngine.php | 80 ++++++++----------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/src/applications/files/document/PhabricatorJupyterDocumentEngine.php b/src/applications/files/document/PhabricatorJupyterDocumentEngine.php index 753cdf3921..20474b047b 100644 --- a/src/applications/files/document/PhabricatorJupyterDocumentEngine.php +++ b/src/applications/files/document/PhabricatorJupyterDocumentEngine.php @@ -85,15 +85,8 @@ final class PhabricatorJupyterDocumentEngine if ($utype === $vtype) { switch ($utype) { case 'markdown': - $usource = idx($ucell, 'source'); - if (is_array($usource)) { - $usource = implode('', $usource); - } - - $vsource = idx($vcell, 'source'); - if (is_array($vsource)) { - $vsource = implode('', $vsource); - } + $usource = $this->readString($ucell, 'source'); + $vsource = $this->readString($vcell, 'source'); $diff = id(new PhutilProseDifferenceEngine()) ->getDiff($usource, $vsource); @@ -117,8 +110,6 @@ final class PhabricatorJupyterDocumentEngine $vsource = idx($vcell, 'raw'); $udisplay = idx($ucell, 'display'); $vdisplay = idx($vcell, 'display'); - $ulabel = idx($ucell, 'label'); - $vlabel = idx($vcell, 'label'); $intraline_segments = ArcanistDiffUtils::generateIntralineDiff( $usource, @@ -142,15 +133,15 @@ final class PhabricatorJupyterDocumentEngine $vdisplay, $v_segments); - $u_content = $this->newCodeLineCell($ucell, $usource); - $v_content = $this->newCodeLineCell($vcell, $vsource); + list($u_label, $u_content) = $this->newCodeLineCell($ucell, $usource); + list($v_label, $v_content) = $this->newCodeLineCell($vcell, $vsource); $classes = array( 'jupyter-cell-flush', ); - $u_content = $this->newJupyterCell($ulabel, $u_content, $classes); - $v_content = $this->newJupyterCell($vlabel, $v_content, $classes); + $u_content = $this->newJupyterCell($u_label, $u_content, $classes); + $v_content = $this->newJupyterCell($v_label, $v_content, $classes); $u_content = $this->newCellContainer($u_content); $v_content = $this->newCellContainer($v_content); @@ -259,10 +250,7 @@ final class PhabricatorJupyterDocumentEngine $hash_input = $cell['raw']; break; case 'markdown': - $hash_input = $cell['source']; - if (is_array($hash_input)) { - $hash_input = implode('', $cell['source']); - } + $hash_input = $this->readString($cell, 'source'); break; default: $hash_input = serialize($cell); @@ -334,7 +322,6 @@ final class PhabricatorJupyterDocumentEngine 'be rendered as a Jupyter notebook.')); } - $nbformat = idx($data, 'nbformat'); if (!strlen($nbformat)) { throw new Exception( @@ -376,10 +363,7 @@ final class PhabricatorJupyterDocumentEngine foreach ($cells as $cell) { $cell_type = idx($cell, 'cell_type'); if ($cell_type === 'markdown') { - $source = $cell['source']; - if (is_array($source)) { - $source = implode('', $source); - } + $source = $this->readString($cell, 'source'); // Attempt to split contiguous blocks of markdown into smaller // pieces. @@ -404,11 +388,7 @@ final class PhabricatorJupyterDocumentEngine $label = $this->newCellLabel($cell); - $lines = idx($cell, 'source'); - if (!is_array($lines)) { - $lines = array(); - } - + $lines = $this->readStringList($cell, 'source'); $content = $this->highlightLines($lines); $count = count($lines); @@ -526,10 +506,7 @@ final class PhabricatorJupyterDocumentEngine } private function newMarkdownCell(array $cell) { - $content = idx($cell, 'source'); - if (!is_array($content)) { - $content = array(); - } + $content = $this->readStringList($cell, 'source'); // TODO: This should ideally highlight as Markdown, but the "md" // highlighter in Pygments is painfully slow and not terribly useful. @@ -549,11 +526,7 @@ final class PhabricatorJupyterDocumentEngine private function newCodeCell(array $cell) { $label = $this->newCellLabel($cell); - $content = idx($cell, 'source'); - if (!is_array($content)) { - $content = array(); - } - + $content = $this->readStringList($cell, 'source'); $content = $this->highlightLines($content); $outputs = array(); @@ -660,11 +633,7 @@ final class PhabricatorJupyterDocumentEngine continue; } - $raw_data = $data[$image_format]; - if (!is_array($raw_data)) { - $raw_data = array($raw_data); - } - $raw_data = implode('', $raw_data); + $raw_data = $this->readString($data, $image_format); $content = phutil_tag( 'img', @@ -695,11 +664,7 @@ final class PhabricatorJupyterDocumentEngine break; case 'stream': default: - $content = idx($output, 'text'); - if (!is_array($content)) { - $content = array(); - } - $content = implode('', $content); + $content = $this->readString($output, 'text'); break; } @@ -761,4 +726,23 @@ final class PhabricatorJupyterDocumentEngine return true; } + private function readString(array $src, $key) { + $list = $this->readStringList($src, $key); + return implode('', $list); + } + + private function readStringList(array $src, $key) { + $list = idx($src, $key); + + if (is_array($list)) { + $list = $list; + } else if (is_string($list)) { + $list = array($list); + } else { + $list = array(); + } + + return $list; + } + } From dbdfac1e072ef7d77513bf41cac2ee1a7d8678f3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Aug 2020 12:56:04 -0700 Subject: [PATCH 24/80] Recover inline comments which are "adjusted" off the end of a diff Summary: See PHI1834. Currently, the inline adjustment engine can sometime "adjust" an inline off the end of a diff. If it does, we lay it out on an invalid display line here and never render it. Instead, make sure that layout never puts a comment on an invalid line, so the UI is robust against questionable decisions by the adjustment engine: no adjustment should be able to accidentally discard an inline. Test Plan: - Created a two diff revision, where Diffs 1 and 2 have "alphabet.txt" with A-Z on one line each. The file is unchanged across diffs; some other file is changed. - Added a comment to lines P-Z of Diff 1. - Before: comment is adjusted out of range on Diff 2 and not shown in the UI. - After: comment is still adjusted out of range internally, but now corrected into the display range and shown. Differential Revision: https://secure.phabricator.com/D21435 --- .../parser/DifferentialChangesetParser.php | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index 3652d18a9a..ad62b2a1ef 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1053,14 +1053,30 @@ final class DifferentialChangesetParser extends Phobject { $this->comments = id(new PHUIDiffInlineThreader()) ->reorderAndThreadCommments($this->comments); + $old_max_display = 1; + foreach ($this->old as $old) { + if (isset($old['line'])) { + $old_max_display = $old['line']; + } + } + + $new_max_display = 1; + foreach ($this->new as $new) { + if (isset($new['line'])) { + $new_max_display = $new['line']; + } + } + foreach ($this->comments as $comment) { - $final = $comment->getLineNumber() + - $comment->getLineLength(); - $final = max(1, $final); + $display_line = $comment->getLineNumber() + $comment->getLineLength(); + $display_line = max(1, $display_line); + if ($this->isCommentOnRightSideWhenDisplayed($comment)) { - $new_comments[$final][] = $comment; + $display_line = min($new_max_display, $display_line); + $new_comments[$display_line][] = $comment; } else { - $old_comments[$final][] = $comment; + $display_line = min($old_max_display, $display_line); + $old_comments[$display_line][] = $comment; } } } From ce0dc9a2ba2ca86e68f0e726df58294c4716b23d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Aug 2020 13:06:57 -0700 Subject: [PATCH 25/80] Correct an apparent off-by-one error when adjusting inlines across revision changes Summary: See PHI1834. It's not obvious why this "+1" is present in the code, but it causes inlines to be adjusted incorrectly when a file is not modified across changes. See D21435. Remove it, which appears to produce accurate adjustment behavior. Test Plan: - See D21435 for instructions to build a change, where a file with lines "A-Z" is unmodified across Diff 1 and Diff 2. - Left inlines on lines 14, 17-19, and 16-26 (end of the file) on Diff 1. - Before: saw inlines incorrectly adjusted to lines 15, 18, and 17 on Diff 2. Before D21435, the last inline was culled by the rendering engine. - After: saw inlines correctly adjusted to lines 14, 17, and 16 (the same lines as the original), render properly, and highlight the correct lines when hovered. Differential Revision: https://secure.phabricator.com/D21436 --- .../diff/engine/PhabricatorInlineCommentAdjustmentEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php b/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php index 095a44c221..f1888cc363 100644 --- a/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php +++ b/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php @@ -358,7 +358,7 @@ final class PhabricatorInlineCommentAdjustmentEngine list($tail_deleted, $tail_offset, $tail_line) = $tail_info; if ($head_offset !== false) { - $inline->setLineNumber($head_line + 1 + $head_offset); + $inline->setLineNumber($head_line + $head_offset); } else { $inline->setLineNumber($head_line); $inline->setLineLength($tail_line - $head_line); From 79375c6c53985f447a87b9770d088e31e93f5dd7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Aug 2020 13:30:11 -0700 Subject: [PATCH 26/80] Make "Quote" work properly in Pholio Summary: See . Pholio is currently missing a couple of configuration calls to make the "Quote" action work. Moving to EditEngine is the "real" fix, but this fix is trivial and should make "Quote" work properly with no negative effects. Test Plan: Viewed a mock, used "quote" to quote a comment. Differential Revision: https://secure.phabricator.com/D21437 --- .../pholio/controller/PholioMockViewController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index e24889875a..f4695d120c 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -61,6 +61,8 @@ final class PholioMockViewController extends PholioController { new PholioTransactionQuery()); $timeline->setMock($mock); + $timeline->setQuoteRef($mock->getMonogram()); + $curtain = $this->buildCurtainView($mock); $details = $this->buildDescriptionView($mock); @@ -80,6 +82,7 @@ final class PholioMockViewController extends PholioController { ->appendChild($mock_view); $add_comment = $this->buildAddCommentView($mock, $comment_form_id); + $add_comment->setTransactionTimeline($timeline); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($mock->getMonogram(), $mock->getURI()); From 545417597322e3c362456a02c37f711ade0a202b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Aug 2020 10:50:10 -0700 Subject: [PATCH 27/80] Coerce Chrome into breaking monospaced text when printing tables to PDFs Summary: See T13564. In Chrome only, printing tables with a cell containing an unbroken monospaced text element fails to wrap/break the cell. Adding "overflow-wrap" appears to fix this without making anything worse. Try this until new problems arise. Test Plan: Printed such a table to PDF in Chrome, got wrapping with all content visible in the PDF. Differential Revision: https://secure.phabricator.com/D21439 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d10f117847..d3deb5381d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'da792a0f', + 'core.pkg.css' => '9b2e2e20', 'core.pkg.js' => '845355f4', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -114,7 +114,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd', 'rsrc/css/application/uiexample/example.css' => 'b4795059', 'rsrc/css/core/core.css' => '1b29ed61', - 'rsrc/css/core/remarkup.css' => '7d3ebc86', + 'rsrc/css/core/remarkup.css' => '94c3d777', 'rsrc/css/core/syntax.css' => '548567f6', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', 'rsrc/css/diviner/diviner-shared.css' => '4bd263b0', @@ -798,7 +798,7 @@ return array( 'phabricator-object-selector-css' => 'ee77366f', 'phabricator-phtize' => '2f1db1ed', 'phabricator-prefab' => '5793d835', - 'phabricator-remarkup-css' => '7d3ebc86', + 'phabricator-remarkup-css' => '94c3d777', 'phabricator-search-results-css' => '9ea70ace', 'phabricator-shaped-request' => '995f5102', 'phabricator-slowvote-css' => '1694baed', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 1688da31f0..5bee3e373d 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -86,7 +86,11 @@ padding: 1px 4px; border-radius: 3px; white-space: pre-wrap; + + /* See T13564. This is a narrow control for PDF printing behavior in + Chrome. */ line-break: anywhere; + overflow-wrap: anywhere; } /* NOTE: You can currently produce this with [[link | `name`]]. Restore the From 81e4e5b7f9c9554b7617ba60ebc28987b416ee38 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 16:43:16 -0700 Subject: [PATCH 28/80] Remove construction of "author" information from "LastModified" payload in Diffusion Summary: Ref T13552. When viewing a directory in Diffusion, we make an Ajax call to get the last commit for each path. This call currently pulls author information, since an older version of this UI showed author information. The current UI does not show author information, so this parameter is unused. Delete the code which builds it. Test Plan: Grepped for `'author'` and references to the "pull-lastmodified" behavior. This behavior is invoked in only one place, which never generates an author placeholder. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21404 --- .../DiffusionLastModifiedController.php | 22 +------------------ .../view/DiffusionBrowseTableView.php | 1 - 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index 1a31d3a2ba..92f5b50a8a 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -53,14 +53,6 @@ final class DiffusionLastModifiedController extends DiffusionController { } } - $phids = array(); - foreach ($commits as $commit) { - $phids[] = $commit->getCommitterDisplayPHID(); - $phids[] = $commit->getAuthorDisplayPHID(); - } - $phids = array_filter($phids); - $handles = $this->loadViewerHandles($phids); - $branch = $drequest->loadBranch(); if ($branch && $commits) { $lint_query = id(new DiffusionLintCountQuery()) @@ -83,7 +75,6 @@ final class DiffusionLastModifiedController extends DiffusionController { $output[$path] = $this->renderColumns( $prequest, - $handles, $commit, idx($lint, $path)); } @@ -93,11 +84,9 @@ final class DiffusionLastModifiedController extends DiffusionController { private function renderColumns( DiffusionRequest $drequest, - array $handles, PhabricatorRepositoryCommit $commit = null, $lint = null) { - assert_instances_of($handles, 'PhabricatorObjectHandle'); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); if ($commit) { $epoch = $commit->getEpoch(); @@ -110,13 +99,6 @@ final class DiffusionLastModifiedController extends DiffusionController { $date = ''; } - $author = $commit->renderAuthor($viewer, $handles); - $committer = $commit->renderCommitter($viewer, $handles); - - if ($author != $committer) { - $author = hsprintf('%s/%s', $author, $committer); - } - $data = $commit->getCommitData(); $details = DiffusionView::linkDetail( $drequest->getRepository(), @@ -124,11 +106,9 @@ final class DiffusionLastModifiedController extends DiffusionController { $data->getSummary()); $details = AphrontTableView::renderSingleDisplayLine($details); - $return = array( 'commit' => $modified, 'date' => $date, - 'author' => $author, 'details' => $details, ); diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index ffbfe8986f..57af6a0bdf 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -76,7 +76,6 @@ final class DiffusionBrowseTableView extends DiffusionView { $dict = array( 'lint' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(), - 'author' => celerity_generate_unique_node_id(), 'details' => celerity_generate_unique_node_id(), ); From 7fd6bf26a9497372dbe30acdf7f95ad7a919d2c7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 16:44:19 -0700 Subject: [PATCH 29/80] Modernize "Author" and "Committer" rendering for commits Summary: Ref T13552. Give "Commit" objects a more modern, identity-aware way to render author and committer information. This uses handles in a more modern way and gives us a single read callsite for raw author and committer names. Test Plan: - Grepped for callers to the old methods, found none. (There are a lot of "renderAuthor()" callers in transactions, but this call takes no parameters.) - Viewed some commits, saw sensible lists of authors and committers. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21405 --- .../audit/view/PhabricatorAuditListView.php | 18 +-- .../controller/DiffusionCommitController.php | 51 ++++++--- .../storage/PhabricatorRepositoryCommit.php | 105 ++++++++++-------- 3 files changed, 98 insertions(+), 76 deletions(-) diff --git a/src/applications/audit/view/PhabricatorAuditListView.php b/src/applications/audit/view/PhabricatorAuditListView.php index fb56e7cd55..5a1df86432 100644 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ b/src/applications/audit/view/PhabricatorAuditListView.php @@ -90,11 +90,6 @@ final class PhabricatorAuditListView extends AphrontView { foreach ($commit->getAudits() as $audit) { $phids[] = $audit->getAuditorPHID(); } - - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $phids[] = $author_phid; - } } $handles = $viewer->loadHandles($phids); @@ -126,21 +121,18 @@ final class PhabricatorAuditListView extends AphrontView { $status_color = $status->getColor(); $status_icon = $status->getIcon(); - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $author_name = $handles[$author_phid]->renderLink(); - } else { - $author_name = $commit->getCommitData()->getAuthorName(); - } - $item = id(new PHUIObjectItemView()) ->setObjectName($commit_name) ->setHeader($commit_desc) ->setHref($commit_link) ->setDisabled($commit->isUnreachable()) - ->addByline(pht('Author: %s', $author_name)) ->addIcon('none', $committed); + $author_name = $commit->newCommitAuthorView($viewer); + if ($author_name) { + $item->addByline(pht('Author: %s', $author_name)); + } + if ($show_drafts) { if ($commit->getHasDraft($viewer)) { $item->addAttribute($draft_icon); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index e069fb7779..69f09ed8bf 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -625,33 +625,50 @@ final class DiffusionCommitController extends DiffusionController { } } - $author_epoch = $data->getCommitDetail('authorEpoch'); + $provenance_list = new PHUIStatusListView(); - $committed_info = id(new PHUIStatusItemView()) - ->setNote(phabricator_datetime($commit->getEpoch(), $viewer)) - ->setTarget($commit->renderAnyCommitter($viewer, $handles)); + $author_view = $commit->newCommitAuthorView($viewer); + if ($author_view) { + $author_date = $data->getCommitDetail('authorEpoch'); + $author_date = phabricator_datetime($author_date, $viewer); - $committed_list = new PHUIStatusListView(); - $committed_list->addItem($committed_info); - $view->addProperty( - pht('Committed'), - $committed_list); + $provenance_list->addItem( + id(new PHUIStatusItemView()) + ->setTarget($author_view) + ->setNote(pht('Authored on %s', $author_date))); + } + + if (!$commit->isAuthorSameAsCommitter()) { + $committer_view = $commit->newCommitCommitterView($viewer); + if ($committer_view) { + $committer_date = $commit->getEpoch(); + $committer_date = phabricator_datetime($committer_date, $viewer); + + $provenance_list->addItem( + id(new PHUIStatusItemView()) + ->setTarget($committer_view) + ->setNote(pht('Committed on %s', $committer_date))); + } + } if ($push_logs) { $pushed_list = new PHUIStatusListView(); foreach ($push_logs as $push_log) { - $pushed_item = id(new PHUIStatusItemView()) - ->setTarget($handles[$push_log->getPusherPHID()]->renderLink()) - ->setNote(phabricator_datetime($push_log->getEpoch(), $viewer)); - $pushed_list->addItem($pushed_item); - } + $pusher_date = $push_log->getEpoch(); + $pusher_date = phabricator_datetime($pusher_date, $viewer); - $view->addProperty( - pht('Pushed'), - $pushed_list); + $pusher_view = $handles[$push_log->getPusherPHID()]->renderLink(); + + $provenance_list->addItem( + id(new PHUIStatusItemView()) + ->setTarget($pusher_view) + ->setNote(pht('Pushed on %s', $pusher_date))); + } } + $view->addProperty(pht('Provenance'), $provenance_list); + $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { $view->addProperty( diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index b5c2fde82a..bfc01ae97c 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -377,52 +377,6 @@ final class PhabricatorRepositoryCommit return $repository->formatCommitName($identifier, $local = true); } - /** - * Make a strong effort to find a way to render this commit's committer. - * This currently attempts to use @{PhabricatorRepositoryIdentity}, and - * falls back to examining the commit detail information. After we force - * the migration to using identities, update this method to remove the - * fallback. See T12164 for details. - */ - public function renderAnyCommitter(PhabricatorUser $viewer, $handles) { - $committer = $this->renderCommitter($viewer, $handles); - if ($committer) { - return $committer; - } - - return $this->renderAuthor($viewer, $handles); - } - - public function renderCommitter(PhabricatorUser $viewer, $handles) { - $committer_phid = $this->getCommitterDisplayPHID(); - if ($committer_phid) { - return $handles[$committer_phid]->renderLink(); - } - - $data = $this->getCommitData(); - $committer_name = $data->getCommitDetail('committer'); - if (strlen($committer_name)) { - return DiffusionView::renderName($committer_name); - } - - return null; - } - - public function renderAuthor(PhabricatorUser $viewer, $handles) { - $author_phid = $this->getAuthorDisplayPHID(); - if ($author_phid) { - return $handles[$author_phid]->renderLink(); - } - - $data = $this->getCommitData(); - $author_name = $data->getAuthorName(); - if (strlen($author_name)) { - return DiffusionView::renderName($author_name); - } - - return null; - } - public function loadIdentities(PhabricatorUser $viewer) { if ($this->authorIdentity !== self::ATTACHABLE) { return $this; @@ -511,6 +465,65 @@ final class PhabricatorRepositoryCommit return (bool)$this->isPartiallyImported(self::IMPORTED_CLOSEABLE); } + public function newCommitAuthorView(PhabricatorUser $viewer) { + $author_phid = $this->getAuthorDisplayPHID(); + if ($author_phid) { + $handles = $viewer->loadHandles(array($author_phid)); + return $handles[$author_phid]->renderLink(); + } + + $author = $this->getRawAuthorStringForDisplay(); + if (strlen($author)) { + return DiffusionView::renderName($author); + } + + return null; + } + + public function newCommitCommitterView(PhabricatorUser $viewer) { + $committer_phid = $this->getCommitterDisplayPHID(); + if ($committer_phid) { + $handles = $viewer->loadHandles(array($committer_phid)); + return $handles[$committer_phid]->renderLink(); + } + + $committer = $this->getRawCommitterStringForDisplay(); + if (strlen($committer)) { + return DiffusionView::renderName($committer); + } + + return null; + } + + public function isAuthorSameAsCommitter() { + $author_phid = $this->getAuthorDisplayPHID(); + $committer_phid = $this->getCommitterDisplayPHID(); + + if ($author_phid && $committer_phid) { + return ($author_phid === $committer_phid); + } + + if ($author_phid || $committer_phid) { + return false; + } + + $author = $this->getRawAuthorStringForDisplay(); + $committer = $this->getRawCommitterStringForDisplay(); + + return ($author === $committer); + } + + private function getRawAuthorStringForDisplay() { + $data = $this->getCommitData(); + return $data->getAuthorName(); + } + + private function getRawCommitterStringForDisplay() { + $data = $this->getCommitData(); + return $data->getCommitDetail('committer'); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { From 60e9f641907ae1d4b728d62cd55004196f9a774c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 16:53:39 -0700 Subject: [PATCH 30/80] Remove the "authored" subheader from commits Summary: Ref T13552. I'm trying to reduce the number of direct callers to commit authorship metadata. This header seems low-value enough to simply remove; this information is shown more clearly and prominently in the "Provenance" UI. In particular, commits have multiple dates (authored, committed, pushed) but this header shows only one. It currently shows the author identity and the commit date, which isn't entirely correct. And it potentially uses an "Identity" as a timeline actor, which is conceptually fine but not entirely firm ground. Test Plan: Viewed a commit, saw no more subheader. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21406 --- .../controller/DiffusionCommitController.php | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 69f09ed8bf..fc6f7bc051 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -183,7 +183,6 @@ final class DiffusionCommitController extends DiffusionController { } $curtain = $this->buildCurtain($commit, $repository); - $subheader = $this->buildSubheaderView($commit, $commit_data); $details = $this->buildPropertyListView( $commit, $commit_data, @@ -483,7 +482,6 @@ final class DiffusionCommitController extends DiffusionController { $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn( array( @@ -760,52 +758,6 @@ final class DiffusionCommitController extends DiffusionController { return $view; } - private function buildSubheaderView( - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - if ($repository->isSVN()) { - return null; - } - - $author_phid = $commit->getAuthorDisplayPHID(); - $author_name = $data->getAuthorName(); - $author_epoch = $data->getCommitDetail('authorEpoch'); - $date = null; - if ($author_epoch !== null) { - $date = phabricator_datetime($author_epoch, $viewer); - } - - if ($author_phid) { - $handles = $viewer->loadHandles(array($author_phid)); - $image_uri = $handles[$author_phid]->getImageURI(); - $image_href = $handles[$author_phid]->getURI(); - $author = $handles[$author_phid]->renderLink(); - } else if (strlen($author_name)) { - $author = $author_name; - $image_uri = null; - $image_href = null; - } else { - return null; - } - - $author = phutil_tag('strong', array(), $author); - if ($date) { - $content = pht('Authored by %s on %s.', $author, $date); - } else { - $content = pht('Authored by %s.', $author); - } - - return id(new PHUIHeadThingView()) - ->setImage($image_uri) - ->setImageHref($image_href) - ->setContent($content); - } - private function buildComments(PhabricatorRepositoryCommit $commit) { $timeline = $this->buildTransactionTimeline( $commit, From c8a279957dc1eafd38d56c3676d32b56c835a84e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 10 Jul 2020 09:24:44 -0700 Subject: [PATCH 31/80] Remove "DiffusionTagTableView" Summary: Ref T13552. This older class has no callers; tag and branch listings were replaced with an "ObjectList" view. Test Plan: Grepped for "DiffusionTagTableView", got no hits. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21407 --- src/__phutil_library_map__.php | 2 - .../diffusion/view/DiffusionTagTableView.php | 140 ------------------ 2 files changed, 142 deletions(-) delete mode 100644 src/applications/diffusion/view/DiffusionTagTableView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 41ae62ed5d..62836d54f2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1063,7 +1063,6 @@ phutil_register_library_map(array( 'DiffusionSyncLogSearchEngine' => 'applications/diffusion/query/DiffusionSyncLogSearchEngine.php', 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', - 'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php', 'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php', 'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php', @@ -7157,7 +7156,6 @@ phutil_register_library_map(array( 'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', - 'DiffusionTagTableView' => 'DiffusionView', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', diff --git a/src/applications/diffusion/view/DiffusionTagTableView.php b/src/applications/diffusion/view/DiffusionTagTableView.php deleted file mode 100644 index 59a06353ab..0000000000 --- a/src/applications/diffusion/view/DiffusionTagTableView.php +++ /dev/null @@ -1,140 +0,0 @@ -tags = $tags; - return $this; - } - - public function setCommits(array $commits) { - $this->commits = mpull($commits, null, 'getCommitIdentifier'); - return $this; - } - - public function setHandles(array $handles) { - $this->handles = $handles; - return $this; - } - - public function getRequiredHandlePHIDs() { - return array_filter(mpull($this->commits, 'getAuthorPHID')); - } - - public function render() { - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - $viewer = $this->getViewer(); - - $buildables = $this->loadBuildables($this->commits); - $has_builds = false; - - $rows = array(); - foreach ($this->tags as $tag) { - $commit = idx($this->commits, $tag->getCommitIdentifier()); - - $tag_link = phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'browse', - 'commit' => $tag->getName(), - )), - ), - $tag->getName()); - - $commit_link = phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $tag->getCommitIdentifier(), - )), - ), - $repository->formatCommitName( - $tag->getCommitIdentifier())); - - $author = null; - if ($commit && $commit->getAuthorPHID()) { - $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); - } else if ($commit && $commit->getCommitData()) { - $author = self::renderName($commit->getCommitData()->getAuthorName()); - } else { - $author = self::renderName($tag->getAuthor()); - } - - $description = null; - if ($tag->getType() == 'git/tag') { - // In Git, a tag may be a "real" tag, or just a reference to a commit. - // If it's a real tag, use the message on the tag, since this may be - // unique data which isn't otherwise available. - $description = $tag->getDescription(); - } else { - if ($commit) { - $description = $commit->getSummary(); - } else { - $description = $tag->getDescription(); - } - } - - $build = null; - if ($commit) { - $buildable = idx($buildables, $commit->getPHID()); - if ($buildable) { - $build = $this->renderBuildable($buildable); - $has_builds = true; - } - } - - $history = $this->linkTagHistory($tag->getName()); - - $rows[] = array( - $history, - $tag_link, - $commit_link, - $build, - $author, - $description, - $viewer->formatShortDateTime($tag->getEpoch()), - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders( - array( - null, - pht('Tag'), - pht('Commit'), - null, - pht('Author'), - pht('Description'), - pht('Created'), - )) - ->setColumnClasses( - array( - 'nudgeright', - 'pri', - '', - '', - '', - 'wide', - 'right', - )) - ->setColumnVisibility( - array( - true, - true, - true, - $has_builds, - )); - - return $table->render(); - } - -} From 9afc5c628757317814a1afdaa389eb2c4cb920df Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 10 Jul 2020 10:43:16 -0700 Subject: [PATCH 32/80] Remove "Recent Commits" from repository landing page Summary: Ref T13552. Currently, the repository landing page has a panel with recent commits. This is accessible by clicking "History" and usually below the fold, so it's not clearly useful. Since I'm consolidating this code anyway to fix an issue with the import pipeline, just get rid of this history view. Test Plan: Viewed a repository landing page, no longer saw a history panel. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21408 --- .../DiffusionRepositoryController.php | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index f00521257e..ae785cff9d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -299,16 +299,10 @@ final class DiffusionRepositoryController extends DiffusionController { $handles, $browse_pager); - $content[] = $this->buildHistoryTable( - $history_results, - $history, - $history_exception); - if ($readme) { $content[] = $readme; } - try { $branch_button = $this->buildBranchList($drequest); $this->branchButton = $branch_button; @@ -428,51 +422,6 @@ final class DiffusionRepositoryController extends DiffusionController { return null; } - private function buildHistoryTable( - $history_results, - $history, - $history_exception) { - - $request = $this->getRequest(); - $viewer = $request->getUser(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - if ($history_exception) { - if ($repository->isImporting()) { - return $this->renderStatusMessage( - pht('Still Importing...'), - pht( - 'This repository is still importing. History is not yet '. - 'available.')); - } else { - return $this->renderStatusMessage( - pht('Unable to Retrieve History'), - $history_exception->getMessage()); - } - } - - $history_table = id(new DiffusionHistoryTableView()) - ->setUser($viewer) - ->setDiffusionRequest($drequest) - ->setHistory($history) - ->setIsHead(true); - - if ($history_results) { - $history_table->setParents($history_results['parents']); - } - - $panel = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view'); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Commits')); - $panel->setHeader($header); - $panel->setTable($history_table); - - return $panel; - } - private function buildBranchList(DiffusionRequest $drequest) { $viewer = $this->getViewer(); From c6de7c66a3c6cee3c81f8adaf216a8552daf39d4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 10 Jul 2020 10:52:38 -0700 Subject: [PATCH 33/80] Remove the "Graph" view as a dedicated repository view Summary: Ref T13552. Currently, Diffusion has two effectively identical history views, the "Graph" view and the "History" view. These arose out of product uncertainty about the importance of the graph, but I think we can just put the graph on the "object item list" view and merge these views. Test Plan: Looked at repositories in Diffusion, no longer saw a "Graph" tab. Grepped for "graph"-related symbols. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21409 --- src/__phutil_library_map__.php | 2 - .../PhabricatorDiffusionApplication.php | 1 - .../controller/DiffusionController.php | 14 --- .../controller/DiffusionGraphController.php | 110 ------------------ .../storage/PhabricatorRepository.php | 2 - 5 files changed, 129 deletions(-) delete mode 100644 src/applications/diffusion/controller/DiffusionGraphController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 62836d54f2..7259d2b1d9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -861,7 +861,6 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolCapabilities' => 'applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php', 'DiffusionGitWireProtocolRef' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRef.php', 'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php', - 'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', @@ -6955,7 +6954,6 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolCapabilities' => 'Phobject', 'DiffusionGitWireProtocolRef' => 'Phobject', 'DiffusionGitWireProtocolRefList' => 'Phobject', - 'DiffusionGraphController' => 'DiffusionController', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryListView' => 'DiffusionHistoryView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index a28aab3946..e0e74486da 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -52,7 +52,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'change/(?P.*)' => 'DiffusionChangeController', 'clone/' => 'DiffusionCloneController', 'history/(?P.*)' => 'DiffusionHistoryController', - 'graph/(?P.*)' => 'DiffusionGraphController', 'browse/(?P.*)' => 'DiffusionBrowseController', 'document/(?P.*)' => 'DiffusionDocumentController', diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 5f4c304ebc..b3595c1b72 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -210,9 +210,6 @@ abstract class DiffusionController extends PhabricatorController { case 'history': $view_name = pht('History'); break; - case 'graph': - $view_name = pht('Graph'); - break; case 'browse': $view_name = pht('Browse'); break; @@ -553,17 +550,6 @@ abstract class DiffusionController extends PhabricatorController { ))) ->setSelected($key == 'history')); - $view->addMenuItem( - id(new PHUIListItemView()) - ->setKey('graph') - ->setName(pht('Graph')) - ->setIcon('fa-code-fork') - ->setHref($drequest->generateURI( - array( - 'action' => 'graph', - ))) - ->setSelected($key == 'graph')); - return $view; } diff --git a/src/applications/diffusion/controller/DiffusionGraphController.php b/src/applications/diffusion/controller/DiffusionGraphController.php deleted file mode 100644 index 4536e29eaf..0000000000 --- a/src/applications/diffusion/controller/DiffusionGraphController.php +++ /dev/null @@ -1,110 +0,0 @@ -loadDiffusionContext(); - if ($response) { - return $response; - } - require_celerity_resource('diffusion-css'); - - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $pager = id(new PHUIPagerView()) - ->readFromRequest($request); - - $params = array( - 'commit' => $drequest->getCommit(), - 'path' => $drequest->getPath(), - 'offset' => $pager->getOffset(), - 'limit' => $pager->getPageSize() + 1, - ); - - $history_results = $this->callConduitWithDiffusionRequest( - 'diffusion.historyquery', - $params); - $history = DiffusionPathChange::newFromConduit( - $history_results['pathChanges']); - - $history = $pager->sliceResults($history); - - $graph = id(new DiffusionHistoryTableView()) - ->setViewer($viewer) - ->setDiffusionRequest($drequest) - ->setHistory($history); - - $show_graph = !strlen($drequest->getPath()); - if ($show_graph) { - $graph->setParents($history_results['parents']); - $graph->setIsHead(!$pager->getOffset()); - $graph->setIsTail(!$pager->getHasMorePages()); - } - - $header = $this->buildHeader($drequest); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'graph', - )); - $crumbs->setBorder(true); - - $title = array( - pht('Graph'), - $repository->getDisplayName(), - ); - - $graph_view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('History Graph')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($graph) - ->addClass('diffusion-mobile-view') - ->setPager($pager); - - $tabs = $this->buildTabsView('graph'); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setTabs($tabs) - ->setFooter($graph_view); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function buildHeader(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - $repository = $drequest->getRepository(); - - $no_path = !strlen($drequest->getPath()); - if ($no_path) { - $header_text = pht('Graph'); - } else { - $header_text = $this->renderPathLinks($drequest, $mode = 'history'); - } - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setHeader($header_text) - ->setHeaderIcon('fa-code-fork'); - - if (!$repository->isSVN()) { - $branch_tag = $this->renderBranchTag($drequest); - $header->addTag($branch_tag); - } - - return $header; - - } - -} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index d568c755c5..05009c0d91 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -682,7 +682,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $action = idx($params, 'action'); switch ($action) { case 'history': - case 'graph': case 'clone': case 'blame': case 'browse': @@ -763,7 +762,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO switch ($action) { case 'change': case 'history': - case 'graph': case 'blame': case 'browse': case 'document': From 46695c76ebfec97a95cc95d7652a61f66afff6b9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 10 Jul 2020 11:15:41 -0700 Subject: [PATCH 34/80] Introduce "DiffusionCommitGraphView", which unifies "HistoryListView" and "HistoryTableView" Summary: Ref T13552. Currently, commit lists are sometimes rendered as an object list and sometimes rendered as a table. There are two separate views for table rendering. Add a fourth view ("list, with a graph") with the eventual intent of unifying all the other views. For now, this only replaces "HistoryListView" -- and needs some more work to really be a convincing replacement. Test Plan: - Looked at "History" in Diffusion, saw an ugly view with all the information we want. - Grepped for "HistoryListView", no hits. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21410 --- src/__phutil_library_map__.php | 4 +- .../controller/DiffusionHistoryController.php | 25 +- .../view/DiffusionCommitGraphView.php | 415 ++++++++++++++++++ .../view/DiffusionHistoryListView.php | 172 -------- 4 files changed, 440 insertions(+), 176 deletions(-) create mode 100644 src/applications/diffusion/view/DiffusionCommitGraphView.php delete mode 100644 src/applications/diffusion/view/DiffusionHistoryListView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7259d2b1d9..738a71a3e1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -770,6 +770,7 @@ phutil_register_library_map(array( 'DiffusionCommitEditEngine' => 'applications/diffusion/editor/DiffusionCommitEditEngine.php', 'DiffusionCommitFerretEngine' => 'applications/repository/search/DiffusionCommitFerretEngine.php', 'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php', + 'DiffusionCommitGraphView' => 'applications/diffusion/view/DiffusionCommitGraphView.php', 'DiffusionCommitHasPackageEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasPackageEdgeType.php', 'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php', 'DiffusionCommitHasRevisionRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php', @@ -862,7 +863,6 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolRef' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRef.php', 'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', - 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php', @@ -6860,6 +6860,7 @@ phutil_register_library_map(array( 'DiffusionCommitEditEngine' => 'PhabricatorEditEngine', 'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine', 'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine', + 'DiffusionCommitGraphView' => 'DiffusionView', 'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasRevisionRelationship' => 'DiffusionCommitRelationship', @@ -6955,7 +6956,6 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolRef' => 'Phobject', 'DiffusionGitWireProtocolRefList' => 'Phobject', 'DiffusionHistoryController' => 'DiffusionController', - 'DiffusionHistoryListView' => 'DiffusionHistoryView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionHistoryTableView' => 'DiffusionHistoryView', 'DiffusionHistoryView' => 'DiffusionView', diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index f0fec0dd2d..01b9d92d9a 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -35,10 +35,31 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); - $history_list = id(new DiffusionHistoryListView()) + $identifiers = array(); + foreach ($history as $item) { + $identifiers[] = $item->getCommitIdentifier(); + } + + if ($identifiers) { + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withIdentifiers($identifiers) + ->needCommitData(true) + ->needIdentities(true) + ->execute(); + } else { + $commits = array(); + } + + $history_list = id(new DiffusionCommitGraphView()) ->setViewer($viewer) + ->setParents($history_results['parents']) + ->setIsHead(!$pager->getOffset()) + ->setIsTail(!$pager->getHasMorePages()) ->setDiffusionRequest($drequest) - ->setHistory($history); + ->setHistory($history) + ->setCommits($commits); $header = $this->buildHeader($drequest); diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php new file mode 100644 index 0000000000..c3d17db3cb --- /dev/null +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -0,0 +1,415 @@ +history = $history; + return $this; + } + + public function getHistory() { + return $this->history; + } + + public function setCommits(array $commits) { + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); + $this->commits = $commits; + $this->commitMap = mpull($commits, null, 'getCommitIdentifier'); + return $this; + } + + public function getCommits() { + return $this->commits; + } + + public function setParents(array $parents) { + $this->parents = $parents; + return $this; + } + + public function getParents() { + return $this->parents; + } + + public function setIsHead($is_head) { + $this->isHead = $is_head; + return $this; + } + + public function getIsHead() { + return $this->isHead; + } + + public function setIsTail($is_tail) { + $this->isTail = $is_tail; + return $this; + } + + public function getIsTail() { + return $this->isTail; + } + + public function setFilterParents($filter_parents) { + $this->filterParents = $filter_parents; + return $this; + } + + public function getFilterParents() { + return $this->filterParents; + } + + private function getRepository() { + $drequest = $this->getDiffusionRequest(); + + if (!$drequest) { + return null; + } + + return $drequest->getRepository(); + } + + public function render() { + $viewer = $this->getUser(); + + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + require_celerity_resource('diffusion-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $show_builds = $this->shouldShowBuilds(); + $show_revisions = $this->shouldShowRevisions(); + + $items = $this->newHistoryItems(); + + $rows = array(); + $last_date = null; + foreach ($items as $item) { + $content = array(); + + $item_epoch = $item['epoch']; + $item_hash = $item['hash']; + $commit = $item['commit']; + + $item_date = phabricator_date($item_epoch, $viewer); + if ($item_date !== $last_date) { + $last_date = $item_date; + $content[] = $item_date; + } + + $commit_description = $this->getCommitDescription($commit); + $commit_link = $this->getCommitURI($commit, $item_hash); + + $short_hash = $this->getCommitObjectName($commit, $item_hash); + $is_disabled = $this->getCommitIsDisabled($commit); + + $author_view = $this->getCommitAuthorView($commit); + + $item_view = id(new PHUIObjectItemView()) + ->setHeader($commit_description) + ->setObjectName($short_hash) + ->setHref($commit_link) + ->setDisabled($is_disabled); + + if ($author_view !== null) { + $item_view->addAttribute($author_view); + } + + $browse_button = $this->newBrowseButton($item_hash); + + $build_view = null; + if ($show_builds) { + $build_view = $this->newBuildView($item_hash); + } + + $item_view->setSideColumn( + array( + $build_view, + $browse_button, + )); + + $revision_view = null; + if ($show_revisions) { + $revision_view = $this->newRevisionView($item_hash); + } + + if ($revision_view !== null) { + $item_view->addAttribute($revision_view); + } + + $view = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addItem($item_view); + + $content[] = $view; + + $rows[] = array( + $content, + ); + } + + $graph = $this->newGraphView(); + if ($graph) { + $idx = 0; + foreach ($rows as $key => $row) { + array_unshift($row, $graph[$idx++]); + $rows[$key] = $row; + } + } + + foreach ($rows as $key => $row) { + $cells = array(); + foreach ($row as $cell) { + $cells[] = phutil_tag('td', array(), $cell); + } + $rows[$key] = phutil_tag('tr', array(), $cells); + } + + $table = phutil_tag('table', array(), $rows); + + return $table; + } + + private function newGraphView() { + if (!$this->getParents()) { + return null; + } + + $parents = $this->getParents(); + + // If we're filtering parents, remove relationships which point to + // commits that are not part of the visible graph. Otherwise, we get + // a big tree of nonsense when viewing release branches like "stable" + // versus "master". + if ($this->getFilterParents()) { + foreach ($parents as $key => $nodes) { + foreach ($nodes as $nkey => $node) { + if (empty($parents[$node])) { + unset($parents[$key][$nkey]); + } + } + } + } + + return id(new PHUIDiffGraphView()) + ->setIsHead($this->getIsHead()) + ->setIsTail($this->getIsTail()) + ->renderGraph($parents); + } + + private function shouldShowBuilds() { + $viewer = $this->getViewer(); + + $show_builds = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorHarbormasterApplication', + $this->getUser()); + + return $show_builds; + } + + private function shouldShowRevisions() { + $viewer = $this->getViewer(); + + $show_revisions = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + + return $show_revisions; + } + + private function newHistoryItems() { + $items = array(); + + $commits = $this->getCommits(); + $commit_map = mpull($commits, null, 'getCommitIdentifier'); + + $history = $this->getHistory(); + if ($history !== null) { + foreach ($history as $history_item) { + $commit_hash = $history_item->getCommitIdentifier(); + + $items[] = array( + 'epoch' => $history_item->getEpoch(), + 'hash' => $commit_hash, + 'commit' => idx($commit_map, $commit_hash), + ); + } + } else { + foreach ($commits as $commit) { + $items[] = array( + 'epoch' => $commit->getEpoch(), + 'hash' => $commit->getCommitIdentifier(), + 'commit' => $commit, + ); + } + } + + return $items; + } + + private function getCommitDescription($commit) { + if (!$commit) { + return phutil_tag('em', array(), pht("Discovering\xE2\x80\xA6")); + } + + // We can show details once the message and change have been imported. + $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE | + PhabricatorRepositoryCommit::IMPORTED_CHANGE; + if (!$commit->isPartiallyImported($partial_import)) { + return phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); + } + + return $commit->getCommitData()->getSummary(); + } + + private function getCommitURI($commit, $hash) { + $repository = $this->getRepository(); + + if ($repository) { + return $repository->getCommitURI($hash); + } + + return $commit->getURI(); + } + + private function getCommitObjectName($commit, $hash) { + $repository = $this->getRepository(); + + if ($repository) { + return $repository->formatCommitName( + $hash, + $is_local = true); + } + + return $commit->getDisplayName(); + } + + private function getCommitIsDisabled($commit) { + if (!$commit) { + return true; + } + + if ($commit->isUnreachable()) { + return true; + } + + return false; + } + + private function getCommitAuthorView($commit) { + if (!$commit) { + return null; + } + + $viewer = $this->getViewer(); + + return $commit->newCommitAuthorView($viewer); + } + + private function newBrowseButton($hash) { + $commit = $this->getCommit($hash); + + return $this->linkBrowse( + '/', + array( + 'commit' => $hash, + ), + $as_button = true); + } + + private function getCommit($hash) { + $commit_map = $this->getCommitMap(); + return idx($commit_map, $hash); + } + + private function getCommitMap() { + return $this->commitMap; + } + + private function newBuildView($hash) { + $commit = $this->getCommit($hash); + if (!$commit) { + return null; + } + + $buildable = $this->getBuildable($commit); + if (!$buildable) { + return null; + } + + return $this->renderBuildable($buildable, 'button'); + } + + private function getBuildable(PhabricatorRepositoryCommit $commit) { + $buildable_map = $this->getBuildableMap(); + return idx($buildable_map, $commit->getPHID()); + } + + private function getBuildableMap() { + if ($this->buildableMap === null) { + $commits = $this->getCommits(); + $buildables = $this->loadBuildables($commits); + $this->buildableMap = $buildables; + } + + return $this->buildableMap; + } + + private function newRevisionView($hash) { + $commit = $this->getCommit($hash); + if (!$commit) { + return null; + } + + $revisions = $this->getRevisions($commit); + if (!$revisions) { + return null; + } + + $revision = head($revisions); + + return id(new PHUITagView()) + ->setName($revision->getMonogram()) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_BLUE) + ->setHref($revision->getURI()) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + } + + private function getRevisions(PhabricatorRepositoryCommit $commit) { + $revision_map = $this->getRevisionMap(); + return idx($revision_map, $commit->getPHID(), array()); + } + + private function getRevisionMap() { + if ($this->revisionMap === null) { + $this->revisionMap = $this->newRevisionMap(); + } + + return $this->revisionMap; + } + + private function newRevisionMap() { + $viewer = $this->getViewer(); + $commits = $this->getCommits(); + + return DiffusionCommitRevisionQuery::loadRevisionMapForCommits( + $viewer, + $commits); + } + +} diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php deleted file mode 100644 index cc7bade925..0000000000 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ /dev/null @@ -1,172 +0,0 @@ -getDiffusionRequest(); - $viewer = $this->getUser(); - $repository = $drequest->getRepository(); - - require_celerity_resource('diffusion-css'); - Javelin::initBehavior('phabricator-tooltips'); - - $buildables = $this->loadBuildables( - mpull($this->getHistory(), 'getCommit')); - - $show_revisions = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorDifferentialApplication', - $viewer); - - $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); - - $show_builds = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorHarbormasterApplication', - $this->getUser()); - - $cur_date = null; - $view = array(); - foreach ($this->getHistory() as $history) { - $epoch = $history->getEpoch(); - $new_date = phabricator_date($history->getEpoch(), $viewer); - if ($cur_date !== $new_date) { - $date = ucfirst( - phabricator_relative_date($history->getEpoch(), $viewer)); - $header = id(new PHUIHeaderView()) - ->setHeader($date); - $list = id(new PHUIObjectItemListView()) - ->setFlush(true) - ->addClass('diffusion-history-list'); - - $view[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view') - ->setObjectList($list); - } - - if ($epoch) { - $committed = $viewer->formatShortDateTime($epoch); - } else { - $committed = null; - } - - $data = $history->getCommitData(); - $author_phid = $committer = $committer_phid = null; - if ($data) { - $author_phid = $data->getCommitDetail('authorPHID'); - $committer_phid = $data->getCommitDetail('committerPHID'); - $committer = $data->getCommitDetail('committer'); - } - - if ($author_phid && isset($handles[$author_phid])) { - $author_name = $handles[$author_phid]->renderLink(); - $author_image = $handles[$author_phid]->getImageURI(); - } else { - $author_name = self::renderName($history->getAuthorName()); - $author_image = - celerity_get_resource_uri('/rsrc/image/people/user0.png'); - } - - $different_committer = false; - if ($committer_phid) { - $different_committer = ($committer_phid != $author_phid); - } else if ($committer != '') { - $different_committer = ($committer != $history->getAuthorName()); - } - if ($different_committer) { - if ($committer_phid && isset($handles[$committer_phid])) { - $committer = $handles[$committer_phid]->renderLink(); - } else { - $committer = self::renderName($committer); - } - $author_name = hsprintf('%s / %s', $author_name, $committer); - } - - // We can show details once the message and change have been imported. - $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE | - PhabricatorRepositoryCommit::IMPORTED_CHANGE; - - $commit = $history->getCommit(); - if ($commit && $commit->isPartiallyImported($partial_import) && $data) { - $commit_desc = $history->getSummary(); - } else { - $commit_desc = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); - } - - $browse_button = $this->linkBrowse( - $history->getPath(), - array( - 'commit' => $history->getCommitIdentifier(), - 'branch' => $drequest->getBranch(), - 'type' => $history->getFileType(), - ), - true); - - $diff_tag = null; - if ($show_revisions && $commit) { - $revisions = $this->getRevisionsForCommit($commit); - if ($revisions) { - $revision = head($revisions); - $diff_tag = id(new PHUITagView()) - ->setName($revision->getMonogram()) - ->setType(PHUITagView::TYPE_SHADE) - ->setColor(PHUITagView::COLOR_BLUE) - ->setHref($revision->getURI()) - ->setBorder(PHUITagView::BORDER_NONE) - ->setSlimShady(true); - } - } - - $build_view = null; - if ($show_builds) { - $buildable = idx($buildables, $commit->getPHID()); - if ($buildable !== null) { - $build_view = $this->renderBuildable($buildable, 'button'); - } - } - - $message = null; - $commit_link = $repository->getCommitURI( - $history->getCommitIdentifier()); - - $commit_name = $repository->formatCommitName( - $history->getCommitIdentifier(), $local = true); - - $committed = phabricator_datetime($commit->getEpoch(), $viewer); - $author_name = phutil_tag( - 'strong', - array( - 'class' => 'diffusion-history-author-name', - ), - $author_name); - $authored = pht('%s on %s.', $author_name, $committed); - - $commit_tag = id(new PHUITagView()) - ->setName($commit_name) - ->setType(PHUITagView::TYPE_SHADE) - ->setColor(PHUITagView::COLOR_INDIGO) - ->setBorder(PHUITagView::BORDER_NONE) - ->setSlimShady(true); - - $item = id(new PHUIObjectItemView()) - ->setHeader($commit_desc) - ->setHref($commit_link) - ->setDisabled($commit->isUnreachable()) - ->setDescription($message) - ->setImageURI($author_image) - ->addAttribute(array($commit_tag, ' ', $diff_tag)) // For Copy Pasta - ->addAttribute($authored) - ->setSideColumn(array( - $build_view, - $browse_button, - )); - - $list->addItem($item); - $cur_date = $new_date; - } - - - return $view; - } - -} From 9fa25253844e75fb9bf66779c3e37f7753062de5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 10 Jul 2020 11:41:59 -0700 Subject: [PATCH 35/80] Improve rendering of history graph in "CommitGraphView" Summary: Ref T13552. In the new combined "table/list" graph view, tidy up the graph rendering. Test Plan: {F7633504} Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21411 --- resources/celerity/map.php | 20 +++--- .../view/DiffusionCommitGraphView.php | 64 +++++++++++-------- .../diff/view/PHUIDiffGraphView.php | 1 + .../css/application/diffusion/diffusion.css | 14 ++++ .../diffusion/behavior-commit-graph.js | 10 ++- 5 files changed, 71 insertions(+), 38 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d3deb5381d..2feb1457f0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -15,7 +15,7 @@ return array( 'differential.pkg.css' => '5c459f92', 'differential.pkg.js' => '218fda21', 'diffusion.pkg.css' => '42c75c37', - 'diffusion.pkg.js' => 'a98c0bf7', + 'diffusion.pkg.js' => '8ee48a4b', 'maniphest.pkg.css' => '35995d6d', 'maniphest.pkg.js' => 'c9308721', 'rsrc/audio/basic/alert.mp3' => '17889334', @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b', 'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c', - 'rsrc/css/application/diffusion/diffusion.css' => 'b54c77b0', + 'rsrc/css/application/diffusion/diffusion.css' => 'a54bb336', 'rsrc/css/application/feed/feed.css' => 'd8b6e3f8', 'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4', 'rsrc/css/application/flag/flag.css' => '2b77be8d', @@ -390,7 +390,7 @@ return array( 'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ef836bf2', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '3be6ef4f', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', @@ -567,7 +567,7 @@ return array( 'differential-revision-history-css' => '8aa3eac5', 'differential-revision-list-css' => '93d2df7d', 'differential-table-of-contents-css' => 'bba788b9', - 'diffusion-css' => 'b54c77b0', + 'diffusion-css' => 'a54bb336', 'diffusion-icons-css' => '23b31a1b', 'diffusion-readme-css' => 'b68a76e4', 'diffusion-repository-css' => 'b89e8c6c', @@ -615,7 +615,7 @@ return array( 'javelin-behavior-differential-diff-radios' => '925fe8cd', 'javelin-behavior-differential-populate' => 'b86ef6c2', 'javelin-behavior-diffusion-commit-branches' => '4b671572', - 'javelin-behavior-diffusion-commit-graph' => 'ef836bf2', + 'javelin-behavior-diffusion-commit-graph' => '3be6ef4f', 'javelin-behavior-diffusion-locate-file' => '87428eb2', 'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123', 'javelin-behavior-document-engine' => '243d6c22', @@ -1249,6 +1249,11 @@ return array( 'javelin-behavior', 'phabricator-prefab', ), + '3be6ef4f' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '3dc5ad43' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2173,11 +2178,6 @@ return array( 'ee77366f' => array( 'aphront-dialog-view-css', ), - 'ef836bf2' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), 'f340a484' => array( 'javelin-install', 'javelin-dom', diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index c3d17db3cb..246f3867d6 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -82,13 +82,9 @@ final class DiffusionCommitGraphView } public function render() { - $viewer = $this->getUser(); - - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); + $viewer = $this->getViewer(); require_celerity_resource('diffusion-css'); - Javelin::initBehavior('phabricator-tooltips'); $show_builds = $this->shouldShowBuilds(); $show_revisions = $this->shouldShowRevisions(); @@ -107,13 +103,18 @@ final class DiffusionCommitGraphView $item_date = phabricator_date($item_epoch, $viewer); if ($item_date !== $last_date) { $last_date = $item_date; - $content[] = $item_date; + $content[] = phutil_tag( + 'div', + array( + 'class' => 'diffusion-commit-graph-date-header', + ), + $item_date); } $commit_description = $this->getCommitDescription($commit); - $commit_link = $this->getCommitURI($commit, $item_hash); + $commit_link = $this->getCommitURI($item_hash); - $short_hash = $this->getCommitObjectName($commit, $item_hash); + $short_hash = $this->getCommitObjectName($item_hash); $is_disabled = $this->getCommitIsDisabled($commit); $author_view = $this->getCommitAuthorView($commit); @@ -156,29 +157,38 @@ final class DiffusionCommitGraphView $content[] = $view; - $rows[] = array( - $content, - ); + $rows[] = $content; } $graph = $this->newGraphView(); - if ($graph) { - $idx = 0; - foreach ($rows as $key => $row) { - array_unshift($row, $graph[$idx++]); - $rows[$key] = $row; - } - } - - foreach ($rows as $key => $row) { + foreach ($rows as $idx => $row) { $cells = array(); - foreach ($row as $cell) { - $cells[] = phutil_tag('td', array(), $cell); + + if ($graph) { + $cells[] = phutil_tag( + 'td', + array( + 'class' => 'diffusion-commit-graph-path-cell', + ), + $graph[$idx]); } - $rows[$key] = phutil_tag('tr', array(), $cells); + + $cells[] = phutil_tag( + 'td', + array( + 'class' => 'diffusion-commit-graph-content-cell', + ), + $row); + + $rows[$idx] = phutil_tag('tr', array(), $cells); } - $table = phutil_tag('table', array(), $rows); + $table = phutil_tag( + 'table', + array( + 'class' => 'diffusion-commit-graph-table', + ), + $rows); return $table; } @@ -275,17 +285,18 @@ final class DiffusionCommitGraphView return $commit->getCommitData()->getSummary(); } - private function getCommitURI($commit, $hash) { + private function getCommitURI($hash) { $repository = $this->getRepository(); if ($repository) { return $repository->getCommitURI($hash); } + $commit = $this->getCommit($hash); return $commit->getURI(); } - private function getCommitObjectName($commit, $hash) { + private function getCommitObjectName($hash) { $repository = $this->getRepository(); if ($repository) { @@ -294,6 +305,7 @@ final class DiffusionCommitGraphView $is_local = true); } + $commit = $this->getCommit($hash); return $commit->getDisplayName(); } diff --git a/src/infrastructure/diff/view/PHUIDiffGraphView.php b/src/infrastructure/diff/view/PHUIDiffGraphView.php index 85741faab7..589526feb6 100644 --- a/src/infrastructure/diff/view/PHUIDiffGraphView.php +++ b/src/infrastructure/diff/view/PHUIDiffGraphView.php @@ -205,6 +205,7 @@ final class PHUIDiffGraphView extends Phobject { 'diffusion-commit-graph', array( 'count' => $count, + 'autoheight' => true, )); return $graph; diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index 20f269dd3d..a516ed8781 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -237,3 +237,17 @@ border-right: none; border-color: {$thinblueborder}; } + +.diffusion-commit-graph-table td.diffusion-commit-graph-path-cell { + padding: 0 4px 0 0; +} + +.diffusion-commit-graph-table td.diffusion-commit-graph-path-cell > canvas { + display: block; +} + +.diffusion-commit-graph-date-header { + color: {$darkgreytext}; + font-weight: bold; + padding: 4px 0; +} diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index 5c4591b542..c0aa9bcb58 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -61,7 +61,13 @@ JX.behavior('diffusion-commit-graph', function(config) { return (col * cell) + (cell / 2); }; - var h = 34; + var h; + if (config.autoheight) { + h = JX.Vector.getDim(nodes[ii].parentNode).y; + } else { + h = 34; + } + var w = cell * config.count; var canvas = JX.$N('canvas', {width: w, height: h}); @@ -147,7 +153,7 @@ JX.behavior('diffusion-commit-graph', function(config) { } } - JX.DOM.setContent(nodes[ii], canvas); + JX.DOM.replace(nodes[ii], canvas); } From cd09ba5e19d798f7ba27ba92890e140d152acea1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 11 Jul 2020 09:24:35 -0700 Subject: [PATCH 36/80] Replace "DiffusionCommitListView" with "DiffusionCommitGraphView" Summary: Ref T13552. This older view mostly duplicates other code and has only two callsites: - The "Commits" section of user profile pages. - The "Ambiguous Hash" page when you visit a commit hash page which is an ambiguous prefix of two or more commit hashes. Replace both with "DiffusionCommitGraphView". Test Plan: - Visited profile page, clicked "Commits". - Visited an ambiguous hash page (`rPbd3c23`). Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21412 --- resources/celerity/map.php | 4 +- src/__phutil_library_map__.php | 2 - .../controller/DiffusionCommitController.php | 5 +- .../controller/DiffusionHistoryController.php | 21 ++- .../view/DiffusionCommitGraphView.php | 21 ++- .../view/DiffusionCommitListView.php | 177 ------------------ ...bricatorPeopleProfileCommitsController.php | 6 +- .../css/application/diffusion/diffusion.css | 8 - 8 files changed, 39 insertions(+), 205 deletions(-) delete mode 100644 src/applications/diffusion/view/DiffusionCommitListView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2feb1457f0..d69ccafee9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b', 'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c', - 'rsrc/css/application/diffusion/diffusion.css' => 'a54bb336', + 'rsrc/css/application/diffusion/diffusion.css' => 'db8bbf58', 'rsrc/css/application/feed/feed.css' => 'd8b6e3f8', 'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4', 'rsrc/css/application/flag/flag.css' => '2b77be8d', @@ -567,7 +567,7 @@ return array( 'differential-revision-history-css' => '8aa3eac5', 'differential-revision-list-css' => '93d2df7d', 'differential-table-of-contents-css' => 'bba788b9', - 'diffusion-css' => 'a54bb336', + 'diffusion-css' => 'db8bbf58', 'diffusion-icons-css' => '23b31a1b', 'diffusion-readme-css' => 'b68a76e4', 'diffusion-repository-css' => 'b89e8c6c', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 738a71a3e1..60677143d5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -783,7 +783,6 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php', 'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php', 'DiffusionCommitListController' => 'applications/diffusion/controller/DiffusionCommitListController.php', - 'DiffusionCommitListView' => 'applications/diffusion/view/DiffusionCommitListView.php', 'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php', 'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php', 'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php', @@ -6873,7 +6872,6 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitHookRejectException' => 'Exception', 'DiffusionCommitListController' => 'DiffusionController', - 'DiffusionCommitListView' => 'AphrontView', 'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField', diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index fc6f7bc051..4cd8f276fc 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -90,10 +90,9 @@ final class DiffusionCommitController extends DiffusionController { ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->appendChild($warning_message); - $list = id(new DiffusionCommitListView()) + $list = id(new DiffusionCommitGraphView()) ->setViewer($viewer) - ->setCommits($commits) - ->setNoDataString(pht('No recent commits.')); + ->setCommits($commits); $crumbs->addTextCrumb(pht('Ambiguous Commit')); diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 01b9d92d9a..f4cd57d09e 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -54,13 +54,28 @@ final class DiffusionHistoryController extends DiffusionController { $history_list = id(new DiffusionCommitGraphView()) ->setViewer($viewer) - ->setParents($history_results['parents']) - ->setIsHead(!$pager->getOffset()) - ->setIsTail(!$pager->getHasMorePages()) ->setDiffusionRequest($drequest) ->setHistory($history) ->setCommits($commits); + // NOTE: If we have a path (like "src/"), many nodes in the graph are + // likely to be missing (since the path wasn't touched by those commits). + + // If we draw the graph, commits will often appear to be unrelated because + // intermediate nodes are omitted. Just drop the graph. + + // The ideal behavior would be to load the entire graph and then connect + // ancestors appropriately, but this would currrently be prohibitively + // expensive in the general case. + + $show_graph = !strlen($drequest->getPath()); + if ($show_graph) { + $history_list + ->setParents($history_results['parents']) + ->setIsHead(!$pager->getOffset()) + ->setIsTail(!$pager->getHasMorePages()); + } + $header = $this->buildHeader($drequest); $crumbs = $this->buildCrumbs( diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index 246f3867d6..1c8e88f164 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -332,14 +332,21 @@ final class DiffusionCommitGraphView } private function newBrowseButton($hash) { - $commit = $this->getCommit($hash); + $repository = $this->getRepository(); - return $this->linkBrowse( - '/', - array( - 'commit' => $hash, - ), - $as_button = true); + if ($repository) { + $drequest = $this->getDiffusionRequest(); + + return $this->linkBrowse( + $drequest->getPath(), + array( + 'commit' => $hash, + 'branch' => $drequest->getBranch(), + ), + $as_button = true); + } + + return null; } private function getCommit($hash) { diff --git a/src/applications/diffusion/view/DiffusionCommitListView.php b/src/applications/diffusion/view/DiffusionCommitListView.php deleted file mode 100644 index d4f20d0014..0000000000 --- a/src/applications/diffusion/view/DiffusionCommitListView.php +++ /dev/null @@ -1,177 +0,0 @@ -noDataString = $no_data_string; - return $this; - } - - public function setHeader($header) { - $this->header = $header; - return $this; - } - - public function setCommits(array $commits) { - assert_instances_of($commits, 'PhabricatorRepositoryCommit'); - $this->commits = mpull($commits, null, 'getPHID'); - return $this; - } - - public function getCommits() { - return $this->commits; - } - - public function setHandles(array $handles) { - assert_instances_of($handles, 'PhabricatorObjectHandle'); - $this->handles = $handles; - return $this; - } - - private function getRequiredHandlePHIDs() { - $phids = array(); - foreach ($this->history as $item) { - $data = $item->getCommitData(); - if ($data) { - if ($data->getCommitDetail('authorPHID')) { - $phids[$data->getCommitDetail('authorPHID')] = true; - } - if ($data->getCommitDetail('committerPHID')) { - $phids[$data->getCommitDetail('committerPHID')] = true; - } - } - } - return array_keys($phids); - } - - private function getCommitDescription($phid) { - if ($this->commits === null) { - return pht('(Unknown Commit)'); - } - - $commit = idx($this->commits, $phid); - if (!$commit) { - return pht('(Unknown Commit)'); - } - - $summary = $commit->getCommitData()->getSummary(); - if (strlen($summary)) { - return $summary; - } - - // No summary, so either this is still importing or just has an empty - // commit message. - - if (!$commit->isImported()) { - return pht('(Importing Commit...)'); - } else { - return pht('(Untitled Commit)'); - } - } - - public function render() { - require_celerity_resource('diffusion-css'); - return $this->buildList(); - } - - public function buildList() { - $viewer = $this->getViewer(); - $rowc = array(); - - $phids = array(); - foreach ($this->getCommits() as $commit) { - $phids[] = $commit->getPHID(); - - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $phids[] = $author_phid; - } - } - - $handles = $viewer->loadHandles($phids); - - $cur_date = 0; - $view = array(); - foreach ($this->commits as $commit) { - $new_date = phabricator_date($commit->getEpoch(), $viewer); - if ($cur_date !== $new_date) { - $date = ucfirst( - phabricator_relative_date($commit->getEpoch(), $viewer)); - $header = id(new PHUIHeaderView()) - ->setHeader($date); - $list = id(new PHUIObjectItemListView()) - ->setFlush(true) - ->addClass('diffusion-history-list'); - - $view[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - } - - $commit_phid = $commit->getPHID(); - $commit_handle = $handles[$commit_phid]; - $committed = null; - - $commit_name = $commit_handle->getName(); - $commit_link = $commit_handle->getURI(); - $commit_desc = $this->getCommitDescription($commit_phid); - $committed = phabricator_datetime($commit->getEpoch(), $viewer); - - $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $engine->setConfig('viewer', $viewer); - $commit_data = $commit->getCommitData(); - $message = $commit_data->getCommitMessage(); - $message = $engine->markupText($message); - $message = phutil_tag_div( - 'diffusion-history-message phabricator-remarkup', $message); - - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $author_name = $handles[$author_phid]->renderLink(); - $author_image_uri = $handles[$author_phid]->getImageURI(); - } else { - $author_name = $commit->getCommitData()->getAuthorName(); - $author_image_uri = - celerity_get_resource_uri('/rsrc/image/people/user0.png'); - } - - $commit_tag = id(new PHUITagView()) - ->setName($commit_name) - ->setType(PHUITagView::TYPE_SHADE) - ->setColor(PHUITagView::COLOR_INDIGO) - ->setBorder(PHUITagView::BORDER_NONE) - ->setSlimShady(true); - - $item = id(new PHUIObjectItemView()) - ->setHeader($commit_desc) - ->setHref($commit_link) - ->setDisabled($commit->isUnreachable()) - ->setDescription($message) - ->setImageURI($author_image_uri) - ->addByline(pht('Author: %s', $author_name)) - ->addIcon('none', $committed) - ->addAttribute($commit_tag); - - $list->addItem($item); - $cur_date = $new_date; - } - - if (!$view) { - $list = id(new PHUIObjectItemListView()) - ->setFlush(true) - ->setNoDataString($this->noDataString); - - $view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Commits')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - } - - return $view; - } - -} diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php index 430e11311e..cf887b7cda 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php @@ -58,13 +58,13 @@ final class PhabricatorPeopleProfileCommitsController ->setViewer($viewer) ->withAuthorPHIDs(array($user->getPHID())) ->needCommitData(true) + ->needIdentities(true) ->setLimit(100) ->execute(); - $list = id(new DiffusionCommitListView()) + $list = id(new DiffusionCommitGraphView()) ->setViewer($viewer) - ->setCommits($commits) - ->setNoDataString(pht('No recent commits.')); + ->setCommits($commits); return $list; } diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index a516ed8781..6423d0fff0 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -93,14 +93,6 @@ font-size: {$biggerfontsize}; } -.diffusion-history-message { - background-color: {$bluebackground}; - padding: 16px; - margin: 4px 0; - border-radius: 5px; - color: {$darkbluetext}; -} - .diffusion-history-list .phui-oi-attribute { font-size: {$smallerfontsize}; letter-spacing: 0.01em; From 7087c0439a7ff60c3ff4a6dea08c57fe27fdda22 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Jul 2020 09:10:59 -0700 Subject: [PATCH 37/80] Move the view of merged changes to "DiffusionCommitGraphView" Summary: Ref T13552. When viewing a merge commit, merged changes are currently shown inline. Update this view to use the new "GraphView" rendering pipeline. Test Plan: - Viewed a merge commit, saw merges. - Viewed history, profile page, etc. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21413 --- .../controller/DiffusionCommitController.php | 6 +- .../controller/DiffusionHistoryController.php | 20 +- .../view/DiffusionCommitGraphView.php | 176 +++++++++++++----- 3 files changed, 135 insertions(+), 67 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 4cd8f276fc..8d52b7facf 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -811,15 +811,15 @@ final class DiffusionCommitController extends DiffusionController { new PhutilNumber($limit))); } - $history_table = id(new DiffusionHistoryTableView()) - ->setUser($viewer) + $commit_list = id(new DiffusionCommitGraphView()) + ->setViewer($viewer) ->setDiffusionRequest($drequest) ->setHistory($merges); $panel = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Merged Changes')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($history_table); + ->setObjectList($commit_list->newObjectItemListView()); if ($caption) { $panel->setInfoView($caption); } diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index f4cd57d09e..fb35d9b6ad 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -35,28 +35,10 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); - $identifiers = array(); - foreach ($history as $item) { - $identifiers[] = $item->getCommitIdentifier(); - } - - if ($identifiers) { - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withRepositoryPHIDs(array($repository->getPHID())) - ->withIdentifiers($identifiers) - ->needCommitData(true) - ->needIdentities(true) - ->execute(); - } else { - $commits = array(); - } - $history_list = id(new DiffusionCommitGraphView()) ->setViewer($viewer) ->setDiffusionRequest($drequest) - ->setHistory($history) - ->setCommits($commits); + ->setHistory($history); // NOTE: If we have a path (like "src/"), many nodes in the graph are // likely to be missing (since the path wasn't touched by those commits). diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index 1c8e88f164..66cf8c2a18 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -4,13 +4,13 @@ final class DiffusionCommitGraphView extends DiffusionView { private $history; - private $commits = array(); + private $commits; private $isHead; private $isTail; private $parents; private $filterParents; - private $commitMap = array(); + private $commitMap; private $buildableMap; private $revisionMap; @@ -27,7 +27,6 @@ final class DiffusionCommitGraphView public function setCommits(array $commits) { assert_instances_of($commits, 'PhabricatorRepositoryCommit'); $this->commits = $commits; - $this->commitMap = mpull($commits, null, 'getCommitIdentifier'); return $this; } @@ -81,40 +80,35 @@ final class DiffusionCommitGraphView return $drequest->getRepository(); } - public function render() { - $viewer = $this->getViewer(); + public function newObjectItemListView() { + $list_view = id(new PHUIObjectItemListView()); + $item_views = $this->newObjectItemViews(); + foreach ($item_views as $item_view) { + $list_view->addItem($item_view); + } + + return $list_view; + } + + private function newObjectItemViews() { require_celerity_resource('diffusion-css'); $show_builds = $this->shouldShowBuilds(); $show_revisions = $this->shouldShowRevisions(); - $items = $this->newHistoryItems(); + $views = array(); - $rows = array(); - $last_date = null; - foreach ($items as $item) { + $items = $this->newHistoryItems(); + foreach ($items as $hash => $item) { $content = array(); - $item_epoch = $item['epoch']; - $item_hash = $item['hash']; $commit = $item['commit']; - $item_date = phabricator_date($item_epoch, $viewer); - if ($item_date !== $last_date) { - $last_date = $item_date; - $content[] = phutil_tag( - 'div', - array( - 'class' => 'diffusion-commit-graph-date-header', - ), - $item_date); - } - $commit_description = $this->getCommitDescription($commit); - $commit_link = $this->getCommitURI($item_hash); + $commit_link = $this->getCommitURI($hash); - $short_hash = $this->getCommitObjectName($item_hash); + $short_hash = $this->getCommitObjectName($hash); $is_disabled = $this->getCommitIsDisabled($commit); $author_view = $this->getCommitAuthorView($commit); @@ -129,11 +123,11 @@ final class DiffusionCommitGraphView $item_view->addAttribute($author_view); } - $browse_button = $this->newBrowseButton($item_hash); + $browse_button = $this->newBrowseButton($hash); $build_view = null; if ($show_builds) { - $build_view = $this->newBuildView($item_hash); + $build_view = $this->newBuildView($hash); } $item_view->setSideColumn( @@ -144,22 +138,63 @@ final class DiffusionCommitGraphView $revision_view = null; if ($show_revisions) { - $revision_view = $this->newRevisionView($item_hash); + $revision_view = $this->newRevisionView($hash); } if ($revision_view !== null) { $item_view->addAttribute($revision_view); } - $view = id(new PHUIObjectItemListView()) - ->setFlush(true) - ->addItem($item_view); - - $content[] = $view; - - $rows[] = $content; + $views[$hash] = $item_view; } + return $views; + } + + private function newObjectItemRows() { + $viewer = $this->getViewer(); + + $items = $this->newHistoryItems(); + $views = $this->newObjectItemViews(); + + $last_date = null; + $rows = array(); + foreach ($items as $hash => $item) { + $item_epoch = $item['epoch']; + $item_date = phabricator_date($item_epoch, $viewer); + if ($item_date !== $last_date) { + $last_date = $item_date; + $date_view = phutil_tag( + 'div', + array( + 'class' => 'diffusion-commit-graph-date-header', + ), + $item_date); + } else { + $date_view = null; + } + + $item_view = idx($views, $hash); + if ($item_view) { + $list_view = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addItem($item_view); + } else { + $list_view = null; + } + + $rows[] = array( + $date_view, + $list_view, + ); + } + + return $rows; + } + + public function render() { + $rows = $this->newObjectItemRows(); + $graph = $this->newGraphView(); foreach ($rows as $idx => $row) { $cells = array(); @@ -243,25 +278,25 @@ final class DiffusionCommitGraphView private function newHistoryItems() { $items = array(); - $commits = $this->getCommits(); - $commit_map = mpull($commits, null, 'getCommitIdentifier'); - $history = $this->getHistory(); if ($history !== null) { foreach ($history as $history_item) { $commit_hash = $history_item->getCommitIdentifier(); - $items[] = array( + $items[$commit_hash] = array( 'epoch' => $history_item->getEpoch(), 'hash' => $commit_hash, - 'commit' => idx($commit_map, $commit_hash), + 'commit' => $this->getCommit($commit_hash), ); } } else { + $commits = $this->getCommitMap(); foreach ($commits as $commit) { - $items[] = array( + $commit_hash = $commit->getCommitIdentifier(); + + $items[$commit_hash] = array( 'epoch' => $commit->getEpoch(), - 'hash' => $commit->getCommitIdentifier(), + 'hash' => $commit_hash, 'commit' => $commit, ); } @@ -293,7 +328,11 @@ final class DiffusionCommitGraphView } $commit = $this->getCommit($hash); - return $commit->getURI(); + if ($commit) { + return $commit->getURI(); + } + + return null; } private function getCommitObjectName($hash) { @@ -306,7 +345,11 @@ final class DiffusionCommitGraphView } $commit = $this->getCommit($hash); - return $commit->getDisplayName(); + if ($commit) { + return $commit->getDisplayName(); + } + + return null; } private function getCommitIsDisabled($commit) { @@ -355,6 +398,11 @@ final class DiffusionCommitGraphView } private function getCommitMap() { + if ($this->commitMap === null) { + $commit_list = $this->newCommitList(); + $this->commitMap = mpull($commit_list, null, 'getCommitIdentifier'); + } + return $this->commitMap; } @@ -379,7 +427,7 @@ final class DiffusionCommitGraphView private function getBuildableMap() { if ($this->buildableMap === null) { - $commits = $this->getCommits(); + $commits = $this->getCommitMap(); $buildables = $this->loadBuildables($commits); $this->buildableMap = $buildables; } @@ -424,11 +472,49 @@ final class DiffusionCommitGraphView private function newRevisionMap() { $viewer = $this->getViewer(); - $commits = $this->getCommits(); + $commits = $this->getCommitMap(); return DiffusionCommitRevisionQuery::loadRevisionMapForCommits( $viewer, $commits); } + private function newCommitList() { + $commits = $this->getCommits(); + if ($commits !== null) { + return $commits; + } + + $repository = $this->getRepository(); + if (!$repository) { + return array(); + } + + $history = $this->getHistory(); + if ($history === null) { + return array(); + } + + $identifiers = array(); + foreach ($history as $item) { + $identifiers[] = $item->getCommitIdentifier(); + } + + if (!$identifiers) { + return array(); + } + + $viewer = $this->getViewer(); + + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withIdentifiers($identifiers) + ->needCommitData(true) + ->needIdentities(true) + ->execute(); + + return $commits; + } + } From 2b0632b442a2761cb8f294cbca1292aa73778ddc Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Jul 2020 09:23:21 -0700 Subject: [PATCH 38/80] Remove "DiffusionHistoryTableView" and "DiffusionHistoryView" Summary: Ref T13552. Currently, the "Browse" page shows a snippet of unmerged changes if you're looking at a non-default branch. Remove this for consistency with the simplified main "Browse" page. This is reachable via "Compare". Update the "Compare" page to use the new "CommitGraphView". Test Plan: - Looked at the "Browse" page of "stable". - Looked at the "Compare" page for "stable vs master". Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21414 --- src/__phutil_library_map__.php | 4 - .../controller/DiffusionBrowseController.php | 63 ------ .../controller/DiffusionCompareController.php | 16 +- .../view/DiffusionHistoryTableView.php | 208 ------------------ .../diffusion/view/DiffusionHistoryView.php | 117 ---------- 5 files changed, 3 insertions(+), 405 deletions(-) delete mode 100644 src/applications/diffusion/view/DiffusionHistoryTableView.php delete mode 100644 src/applications/diffusion/view/DiffusionHistoryView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 60677143d5..97ea8abf05 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -863,8 +863,6 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', - 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', - 'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php', 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', 'DiffusionIdentityAssigneeDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php', 'DiffusionIdentityAssigneeEditField' => 'applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php', @@ -6955,8 +6953,6 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolRefList' => 'Phobject', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionHistoryTableView' => 'DiffusionHistoryView', - 'DiffusionHistoryView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index e4e7a0c030..76efa41866 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -292,7 +292,6 @@ final class DiffusionBrowseController extends DiffusionController { $empty_result = null; $browse_panel = null; - $branch_panel = null; if (!$results->isValidResults()) { $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); @@ -328,12 +327,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setTable($browse_table) ->addClass('diffusion-mobile-view') ->setPager($pager); - - $path = $drequest->getPath(); - $is_branch = (!strlen($path) && $repository->supportsBranchComparison()); - if ($is_branch) { - $branch_panel = $this->buildBranchTable(); - } } $open_revisions = $this->buildOpenRevisions(); @@ -359,7 +352,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setFooter( array( $bar, - $branch_panel, $empty_result, $browse_panel, $open_revisions, @@ -1074,59 +1066,4 @@ final class DiffusionBrowseController extends DiffusionController { return $file; } - private function buildBranchTable() { - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $branch = $drequest->getBranch(); - $default_branch = $repository->getDefaultBranch(); - - if ($branch === $default_branch) { - return null; - } - - $pager = id(new PHUIPagerView()) - ->setPageSize(10); - - try { - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.historyquery', - array( - 'commit' => $branch, - 'against' => $default_branch, - 'path' => $drequest->getPath(), - 'offset' => $pager->getOffset(), - 'limit' => $pager->getPageSize() + 1, - )); - } catch (Exception $ex) { - return null; - } - - $history = DiffusionPathChange::newFromConduit($results['pathChanges']); - $history = $pager->sliceResults($history); - - if (!$history) { - return null; - } - - $history_table = id(new DiffusionHistoryTableView()) - ->setViewer($viewer) - ->setDiffusionRequest($drequest) - ->setHistory($history) - ->setParents($results['parents']) - ->setFilterParents(true) - ->setIsHead(true) - ->setIsTail(!$pager->getHasMorePages()); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('%s vs %s', $branch, $default_branch)); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view') - ->setTable($history_table); - } - } diff --git a/src/applications/diffusion/controller/DiffusionCompareController.php b/src/applications/diffusion/controller/DiffusionCompareController.php index 7dc3fb14c5..26d888dfb2 100644 --- a/src/applications/diffusion/controller/DiffusionCompareController.php +++ b/src/applications/diffusion/controller/DiffusionCompareController.php @@ -285,7 +285,6 @@ final class DiffusionCompareController extends DiffusionController { $request = $this->getRequest(); $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); if (!$history) { return $this->renderStatusMessage( @@ -296,8 +295,8 @@ final class DiffusionCompareController extends DiffusionController { phutil_tag('strong', array(), $against_ref))); } - $history_table = id(new DiffusionHistoryTableView()) - ->setUser($viewer) + $history_view = id(new DiffusionCommitGraphView()) + ->setViewer($viewer) ->setDiffusionRequest($drequest) ->setHistory($history) ->setParents($results['parents']) @@ -305,15 +304,6 @@ final class DiffusionCompareController extends DiffusionController { ->setIsHead(!$pager->getOffset()) ->setIsTail(!$pager->getHasMorePages()); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Commits')); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($history_table) - ->addClass('diffusion-mobile-view') - ->setPager($pager); - + return $history_view; } } diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php deleted file mode 100644 index bd4fd5490a..0000000000 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ /dev/null @@ -1,208 +0,0 @@ -getDiffusionRequest(); - - $viewer = $this->getUser(); - - $buildables = $this->loadBuildables( - mpull($this->getHistory(), 'getCommit')); - $has_any_build = false; - - $show_revisions = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorDifferentialApplication', - $viewer); - - $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); - - $graph = null; - if ($this->getParents()) { - $parents = $this->getParents(); - - // If we're filtering parents, remove relationships which point to - // commits that are not part of the visible graph. Otherwise, we get - // a big tree of nonsense when viewing release branches like "stable" - // versus "master". - if ($this->getFilterParents()) { - foreach ($parents as $key => $nodes) { - foreach ($nodes as $nkey => $node) { - if (empty($parents[$node])) { - unset($parents[$key][$nkey]); - } - } - } - } - - $graph = id(new PHUIDiffGraphView()) - ->setIsHead($this->getIsHead()) - ->setIsTail($this->getIsTail()) - ->renderGraph($parents); - } - - $show_builds = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorHarbormasterApplication', - $this->getUser()); - - $rows = array(); - $ii = 0; - foreach ($this->getHistory() as $history) { - $epoch = $history->getEpoch(); - - if ($epoch) { - $committed = $viewer->formatShortDateTime($epoch); - } else { - $committed = null; - } - - $data = $history->getCommitData(); - $author_phid = $committer = $committer_phid = null; - if ($data) { - $author_phid = $data->getCommitDetail('authorPHID'); - $committer_phid = $data->getCommitDetail('committerPHID'); - $committer = $data->getCommitDetail('committer'); - } - - if ($author_phid && isset($handles[$author_phid])) { - $author = $handles[$author_phid]->renderLink(); - } else { - $author = self::renderName($history->getAuthorName()); - } - - $different_committer = false; - if ($committer_phid) { - $different_committer = ($committer_phid != $author_phid); - } else if ($committer != '') { - $different_committer = ($committer != $history->getAuthorName()); - } - if ($different_committer) { - if ($committer_phid && isset($handles[$committer_phid])) { - $committer = $handles[$committer_phid]->renderLink(); - } else { - $committer = self::renderName($committer); - } - $author = hsprintf('%s/%s', $author, $committer); - } - - // We can show details once the message and change have been imported. - $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE | - PhabricatorRepositoryCommit::IMPORTED_CHANGE; - - $commit = $history->getCommit(); - if ($commit && $commit->isPartiallyImported($partial_import) && $data) { - $summary = AphrontTableView::renderSingleDisplayLine( - $history->getSummary()); - } else { - $summary = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); - } - - $build = null; - if ($show_builds) { - $buildable = idx($buildables, $commit->getPHID()); - if ($buildable !== null) { - $build = $this->renderBuildable($buildable); - $has_any_build = true; - } - } - - $browse = $this->linkBrowse( - $history->getPath(), - array( - 'commit' => $history->getCommitIdentifier(), - 'branch' => $drequest->getBranch(), - 'type' => $history->getFileType(), - )); - - $status = $commit->getAuditStatusObject(); - $icon = $status->getIcon(); - $color = $status->getColor(); - $name = $status->getName(); - - $audit_view = id(new PHUIIconView()) - ->setIcon($icon, $color) - ->addSigil('has-tooltip') - ->setMetadata( - array( - 'tip' => $name, - )); - - $revision_link = null; - if ($commit) { - $revisions = $this->getRevisionsForCommit($commit); - if ($revisions) { - $revision = head($revisions); - $revision_link = phutil_tag( - 'a', - array( - 'href' => $revision->getURI(), - ), - $revision->getMonogram()); - } - } - - $rows[] = array( - $graph ? $graph[$ii++] : null, - $browse, - self::linkCommit( - $drequest->getRepository(), - $history->getCommitIdentifier()), - $build, - $audit_view, - $revision_link, - $author, - $summary, - $committed, - ); - } - - $view = new AphrontTableView($rows); - $view->setHeaders( - array( - null, - null, - pht('Commit'), - null, - null, - null, - pht('Author'), - pht('Details'), - pht('Committed'), - )); - $view->setColumnClasses( - array( - 'threads', - 'nudgeright', - '', - 'icon', - 'icon', - '', - '', - 'wide', - 'right', - )); - $view->setColumnVisibility( - array( - $graph ? true : false, - true, - true, - $has_any_build, - true, - $show_revisions, - )); - $view->setDeviceVisibility( - array( - $graph ? true : false, - true, - true, - true, - true, - true, - false, - true, - false, - )); - return $view->render(); - } - -} diff --git a/src/applications/diffusion/view/DiffusionHistoryView.php b/src/applications/diffusion/view/DiffusionHistoryView.php deleted file mode 100644 index 9fd760b2cc..0000000000 --- a/src/applications/diffusion/view/DiffusionHistoryView.php +++ /dev/null @@ -1,117 +0,0 @@ -history = $history; - return $this; - } - - public function getHistory() { - return $this->history; - } - - public function setHandles(array $handles) { - assert_instances_of($handles, 'PhabricatorObjectHandle'); - $this->handles = $handles; - return $this; - } - - public function getRequiredHandlePHIDs() { - $phids = array(); - foreach ($this->history as $item) { - $data = $item->getCommitData(); - if ($data) { - if ($data->getCommitDetail('authorPHID')) { - $phids[$data->getCommitDetail('authorPHID')] = true; - } - if ($data->getCommitDetail('committerPHID')) { - $phids[$data->getCommitDetail('committerPHID')] = true; - } - } - } - return array_keys($phids); - } - - public function setParents(array $parents) { - $this->parents = $parents; - return $this; - } - - public function getParents() { - return $this->parents; - } - - public function setIsHead($is_head) { - $this->isHead = $is_head; - return $this; - } - - public function getIsHead() { - return $this->isHead; - } - - public function setIsTail($is_tail) { - $this->isTail = $is_tail; - return $this; - } - - public function getIsTail() { - return $this->isTail; - } - - public function setFilterParents($filter_parents) { - $this->filterParents = $filter_parents; - return $this; - } - - public function getFilterParents() { - return $this->filterParents; - } - - public function render() {} - - final protected function getRevisionsForCommit( - PhabricatorRepositoryCommit $commit) { - - if ($this->revisionMap === null) { - $this->revisionMap = $this->newRevisionMap(); - } - - return idx($this->revisionMap, $commit->getPHID(), array()); - } - - private function newRevisionMap() { - $history = $this->history; - - $commits = array(); - foreach ($history as $item) { - $commit = $item->getCommit(); - if ($commit) { - - // NOTE: The "commit" objects in the history list may be undiscovered, - // and thus not yet have PHIDs. Only load data for commits with PHIDs. - if (!$commit->getPHID()) { - continue; - } - - $commits[] = $commit; - } - } - - return DiffusionCommitRevisionQuery::loadRevisionMapForCommits( - $this->getViewer(), - $commits); - } - -} From 57ee6649aa3f77d894eafdbaca6ec82d4376d620 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Jul 2020 10:39:09 -0700 Subject: [PATCH 39/80] Remove "PhabricatorAuditListView" Summary: Ref T13552. Remove yet another way to render a list of commits, and unify it with "CommitGraphView". Test Plan: - Viewed commit search results. - Viewed owners package detail page. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21415 --- src/__phutil_library_map__.php | 2 - .../query/PhabricatorCommitSearchEngine.php | 37 ++-- .../audit/view/PhabricatorAuditListView.php | 171 ------------------ .../PhabricatorOwnersDetailController.php | 20 +- 4 files changed, 28 insertions(+), 202 deletions(-) delete mode 100644 src/applications/audit/view/PhabricatorAuditListView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 97ea8abf05..9828fca099 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2274,7 +2274,6 @@ phutil_register_library_map(array( 'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php', 'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php', 'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php', - 'PhabricatorAuditListView' => 'applications/audit/view/PhabricatorAuditListView.php', 'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php', 'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php', 'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php', @@ -8586,7 +8585,6 @@ phutil_register_library_map(array( 'PhabricatorAuditController' => 'PhabricatorController', 'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuditInlineComment' => 'PhabricatorInlineComment', - 'PhabricatorAuditListView' => 'AphrontView', 'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow', 'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index 35db1270ce..326cb5c9b6 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -221,9 +221,8 @@ final class PhabricatorCommitSearchEngine $bucket = $this->getResultBucket($query); - $template = id(new PhabricatorAuditListView()) - ->setViewer($viewer) - ->setShowDrafts(true); + $template = id(new DiffusionCommitGraphView()) + ->setViewer($viewer); $views = array(); if ($bucket) { @@ -235,37 +234,31 @@ final class PhabricatorCommitSearchEngine foreach ($groups as $group) { // Don't show groups in Dashboard Panels if ($group->getObjects() || !$this->isPanelContext()) { - $views[] = id(clone $template) + $item_list = id(clone $template) + ->setCommits($group->getObjects()) + ->newObjectItemListView(); + + $views[] = $item_list ->setHeader($group->getName()) - ->setNoDataString($group->getNoDataString()) - ->setCommits($group->getObjects()); + ->setNoDataString($group->getNoDataString()); } } } catch (Exception $ex) { $this->addError($ex->getMessage()); } - } else { - $views[] = id(clone $template) - ->setCommits($commits) - ->setNoDataString(pht('No commits found.')); } if (!$views) { - $views[] = id(new PhabricatorAuditListView()) - ->setViewer($viewer) + $item_list = id(clone $template) + ->setCommits($commits) + ->newObjectItemListView(); + + $views[] = $item_list ->setNoDataString(pht('No commits found.')); } - if (count($views) == 1) { - $list = head($views)->buildList(); - } else { - $list = $views; - } - - $result = new PhabricatorApplicationSearchResultView(); - $result->setContent($list); - - return $result; + return id(new PhabricatorApplicationSearchResultView()) + ->setContent($views); } protected function getNewUserBody() { diff --git a/src/applications/audit/view/PhabricatorAuditListView.php b/src/applications/audit/view/PhabricatorAuditListView.php deleted file mode 100644 index 5a1df86432..0000000000 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ /dev/null @@ -1,171 +0,0 @@ -noDataString = $no_data_string; - return $this; - } - - public function getNoDataString() { - return $this->noDataString; - } - - public function setHeader($header) { - $this->header = $header; - return $this; - } - - public function getHeader() { - return $this->header; - } - - public function setShowDrafts($show_drafts) { - $this->showDrafts = $show_drafts; - return $this; - } - - public function getShowDrafts() { - return $this->showDrafts; - } - - /** - * These commits should have both commit data and audit requests attached. - */ - public function setCommits(array $commits) { - assert_instances_of($commits, 'PhabricatorRepositoryCommit'); - $this->commits = mpull($commits, null, 'getPHID'); - return $this; - } - - public function getCommits() { - return $this->commits; - } - - private function getCommitDescription($phid) { - if ($this->commits === null) { - return pht('(Unknown Commit)'); - } - - $commit = idx($this->commits, $phid); - if (!$commit) { - return pht('(Unknown Commit)'); - } - - $summary = $commit->getCommitData()->getSummary(); - if (strlen($summary)) { - return $summary; - } - - // No summary, so either this is still importing or just has an empty - // commit message. - - if (!$commit->isImported()) { - return pht('(Importing Commit...)'); - } else { - return pht('(Untitled Commit)'); - } - } - - public function render() { - $list = $this->buildList(); - $list->setFlush(true); - return $list->render(); - } - - public function buildList() { - $viewer = $this->getViewer(); - $rowc = array(); - - $phids = array(); - foreach ($this->getCommits() as $commit) { - $phids[] = $commit->getPHID(); - - foreach ($commit->getAudits() as $audit) { - $phids[] = $audit->getAuditorPHID(); - } - } - - $handles = $viewer->loadHandles($phids); - - $show_drafts = $this->getShowDrafts(); - - $draft_icon = id(new PHUIIconView()) - ->setIcon('fa-comment yellow') - ->addSigil('has-tooltip') - ->setMetadata( - array( - 'tip' => pht('Unsubmitted Comments'), - )); - - $list = new PHUIObjectItemListView(); - foreach ($this->commits as $commit) { - $commit_phid = $commit->getPHID(); - $commit_handle = $handles[$commit_phid]; - $committed = null; - - $commit_name = $commit_handle->getName(); - $commit_link = $commit_handle->getURI(); - $commit_desc = $this->getCommitDescription($commit_phid); - $committed = phabricator_datetime($commit->getEpoch(), $viewer); - - $status = $commit->getAuditStatusObject(); - - $status_text = $status->getName(); - $status_color = $status->getColor(); - $status_icon = $status->getIcon(); - - $item = id(new PHUIObjectItemView()) - ->setObjectName($commit_name) - ->setHeader($commit_desc) - ->setHref($commit_link) - ->setDisabled($commit->isUnreachable()) - ->addIcon('none', $committed); - - $author_name = $commit->newCommitAuthorView($viewer); - if ($author_name) { - $item->addByline(pht('Author: %s', $author_name)); - } - - if ($show_drafts) { - if ($commit->getHasDraft($viewer)) { - $item->addAttribute($draft_icon); - } - } - - $audits = $commit->getAudits(); - $auditor_phids = mpull($audits, 'getAuditorPHID'); - if ($auditor_phids) { - $auditor_list = $handles->newSublist($auditor_phids) - ->renderList() - ->setAsInline(true); - } else { - $auditor_list = phutil_tag('em', array(), pht('None')); - } - $item->addAttribute(pht('Auditors: %s', $auditor_list)); - - if ($status_color) { - $item->setStatusIcon($status_icon.' '.$status_color, $status_text); - } - - $list->addItem($item); - } - - if ($this->noDataString) { - $list->setNoDataString($this->noDataString); - } - - if ($this->header) { - $list->setHeader($this->header); - } - - return $list; - } - -} diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 3aace69074..ff634a1ad0 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -82,12 +82,15 @@ final class PhabricatorOwnersDetailController )) ->needCommitData(true) ->needAuditRequests(true) + ->needIdentities(true) ->setLimit(10) ->execute(); - $view = id(new PhabricatorAuditListView()) - ->setUser($viewer) - ->setNoDataString(pht('This package has no open problem commits.')) - ->setCommits($attention_commits); + $view = id(new DiffusionCommitGraphView()) + ->setViewer($viewer) + ->setCommits($attention_commits) + ->newObjectItemListView(); + + $view->setNoDataString(pht('This package has no open problem commits.')); $commit_views[] = array( 'view' => $view, @@ -105,13 +108,16 @@ final class PhabricatorOwnersDetailController ->withPackagePHIDs(array($package->getPHID())) ->needCommitData(true) ->needAuditRequests(true) + ->needIdentities(true) ->setLimit(25) ->execute(); - $view = id(new PhabricatorAuditListView()) - ->setUser($viewer) + $view = id(new DiffusionCommitGraphView()) + ->setViewer($viewer) ->setCommits($all_commits) - ->setNoDataString(pht('No commits in this package.')); + ->newObjectItemListView(); + + $view->setNoDataString(pht('No commits in this package.')); $commit_views[] = array( 'view' => $view, From 36dac46ff2638ad6b8baeb0395a1d651352e96aa Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Jul 2020 11:01:32 -0700 Subject: [PATCH 40/80] Clean up some minor commit list CSS Summary: Ref T13552. Some of the CSS can be removed or simplified now that essentially all lists of commits are on a single rendering pathway. Test Plan: Grepped for affected CSS, viewed commit graph. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21416 --- resources/celerity/map.php | 4 +-- .../view/DiffusionCommitGraphView.php | 29 +++++++------------ .../css/application/diffusion/diffusion.css | 9 ++---- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d69ccafee9..6c4d8774a9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b', 'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c', - 'rsrc/css/application/diffusion/diffusion.css' => 'db8bbf58', + 'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6', 'rsrc/css/application/feed/feed.css' => 'd8b6e3f8', 'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4', 'rsrc/css/application/flag/flag.css' => '2b77be8d', @@ -567,7 +567,7 @@ return array( 'differential-revision-history-css' => '8aa3eac5', 'differential-revision-list-css' => '93d2df7d', 'differential-table-of-contents-css' => 'bba788b9', - 'diffusion-css' => 'db8bbf58', + 'diffusion-css' => 'e46232d6', 'diffusion-icons-css' => '23b31a1b', 'diffusion-readme-css' => 'b68a76e4', 'diffusion-repository-css' => 'b89e8c6c', diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index 66cf8c2a18..ae72d50077 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -164,29 +164,22 @@ final class DiffusionCommitGraphView $item_date = phabricator_date($item_epoch, $viewer); if ($item_date !== $last_date) { $last_date = $item_date; - $date_view = phutil_tag( - 'div', - array( - 'class' => 'diffusion-commit-graph-date-header', - ), - $item_date); + $header = $item_date; } else { - $date_view = null; + $header = null; } - $item_view = idx($views, $hash); - if ($item_view) { - $list_view = id(new PHUIObjectItemListView()) - ->setFlush(true) - ->addItem($item_view); - } else { - $list_view = null; + $item_view = $views[$hash]; + + $list_view = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addItem($item_view); + + if ($header !== null) { + $list_view->setHeader($header); } - $rows[] = array( - $date_view, - $list_view, - ); + $rows[] = $list_view; } return $rows; diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index 6423d0fff0..c80a0506ab 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -102,10 +102,6 @@ color: {$darkbluetext}; } -.diffusion-history-list .diffusion-differential-tag { - margin-left: 4px; -} - /* - Branch Styles ----------------------------------------------------------*/ .diffusion-branch-list .phui-oi-attribute a { @@ -238,8 +234,9 @@ display: block; } -.diffusion-commit-graph-date-header { +.diffusion-commit-graph-table .phui-oi-list-header { color: {$darkgreytext}; font-weight: bold; - padding: 4px 0; + padding: 4px 4px; + font-size: {$normalfontsize}; } From 8aec3f916b39c32c5894c72343edb38b97cf6e12 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Jul 2020 12:41:18 -0700 Subject: [PATCH 41/80] Unify more build, property, auditor, and status information into "CommitGraphView" Summary: Ref T13552. In unifying the various Graph/List/Table commit views, some information was dropped -- particularly, audit status. Restore most of it. The result isn't very pretty, but has most of the required information. Test Plan: {F7637411} Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21417 --- resources/celerity/map.php | 6 +- .../query/PhabricatorCommitSearchEngine.php | 3 +- .../view/DiffusionCommitGraphView.php | 229 +++++++++++++----- src/view/phui/PHUIObjectItemView.php | 41 ++++ .../phui/object-item/phui-oi-list-view.css | 6 + 5 files changed, 216 insertions(+), 69 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6c4d8774a9..fb63b68822 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '9b2e2e20', + 'core.pkg.css' => '9cb7cb3f', 'core.pkg.js' => '845355f4', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -133,7 +133,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '4b0def39', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', 'rsrc/css/phui/phui-action-list.css' => '1b0085b2', 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', @@ -866,7 +866,7 @@ return array( 'phui-oi-color-css' => 'b517bfa0', 'phui-oi-drag-ui-css' => 'da15d3dc', 'phui-oi-flush-ui-css' => '490e2e2e', - 'phui-oi-list-view-css' => 'd7723ecc', + 'phui-oi-list-view-css' => '4b0def39', 'phui-oi-simple-ui-css' => '6a30fa46', 'phui-pager-css' => 'd022c7ad', 'phui-pinboard-view-css' => '1f08f5d8', diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index 326cb5c9b6..37e8d563b4 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -222,7 +222,8 @@ final class PhabricatorCommitSearchEngine $bucket = $this->getResultBucket($query); $template = id(new DiffusionCommitGraphView()) - ->setViewer($viewer); + ->setViewer($viewer) + ->setShowAuditors(true); $views = array(); if ($bucket) { diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index ae72d50077..124a94fc69 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -14,6 +14,8 @@ final class DiffusionCommitGraphView private $buildableMap; private $revisionMap; + private $showAuditors; + public function setHistory(array $history) { assert_instances_of($history, 'DiffusionPathChange'); $this->history = $history; @@ -34,6 +36,15 @@ final class DiffusionCommitGraphView return $this->commits; } + public function setShowAuditors($show_auditors) { + $this->showAuditors = $show_auditors; + return $this; + } + + public function getShowAuditors() { + return $this->showAuditors; + } + public function setParents(array $parents) { $this->parents = $parents; return $this; @@ -92,10 +103,36 @@ final class DiffusionCommitGraphView } private function newObjectItemViews() { + $viewer = $this->getViewer(); + require_celerity_resource('diffusion-css'); $show_builds = $this->shouldShowBuilds(); $show_revisions = $this->shouldShowRevisions(); + $show_auditors = $this->shouldShowAuditors(); + + $phids = array(); + + if ($show_revisions) { + $revision_map = $this->getRevisionMap(); + foreach ($revision_map as $revisions) { + foreach ($revisions as $revision) { + $phids[] = $revision->getPHID(); + } + } + } + + if ($show_auditors) { + $commits = $this->getCommitMap(); + foreach ($commits as $commit) { + $audits = $commit->getAudits(); + foreach ($audits as $auditor) { + $phids[] = $auditor->getAuditorPHID(); + } + } + } + + $handles = $viewer->loadHandles($phids); $views = array(); @@ -111,38 +148,52 @@ final class DiffusionCommitGraphView $short_hash = $this->getCommitObjectName($hash); $is_disabled = $this->getCommitIsDisabled($commit); - $author_view = $this->getCommitAuthorView($commit); - $item_view = id(new PHUIObjectItemView()) + ->setViewer($viewer) ->setHeader($commit_description) ->setObjectName($short_hash) ->setHref($commit_link) ->setDisabled($is_disabled); - if ($author_view !== null) { - $item_view->addAttribute($author_view); - } + $this->addBrowseAction($item_view, $hash); - $browse_button = $this->newBrowseButton($hash); - - $build_view = null; if ($show_builds) { - $build_view = $this->newBuildView($hash); + $this->addBuildAction($item_view, $hash); } - $item_view->setSideColumn( - array( - $build_view, - $browse_button, - )); + $this->addAuditAction($item_view, $hash); + + if ($show_auditors) { + $auditor_list = $item_view->newPropertyList(); + if ($commit) { + $auditors = $this->newAuditorList($commit, $handles); + $auditor_list->addProperty(pht('Auditors'), $auditors); + } + } + + $property_list = $item_view->newPropertyList(); + + if ($commit) { + $author_view = $this->getCommitAuthorView($commit); + if ($author_view) { + $property_list->addProperty( + pht('Author'), + $this->getCommitAuthorView($commit)); + } + } - $revision_view = null; if ($show_revisions) { - $revision_view = $this->newRevisionView($hash); - } + if ($commit) { + $revisions = $this->getRevisions($commit); + if ($revisions) { + $revision = head($revisions); + $handle = $handles[$revision->getPHID()]; - if ($revision_view !== null) { - $item_view->addAttribute($revision_view); + $property_list->addProperty( + pht('Revision'), + $handle->renderLink()); + } + } } $views[$hash] = $item_view; @@ -172,6 +223,7 @@ final class DiffusionCommitGraphView $item_view = $views[$hash]; $list_view = id(new PHUIObjectItemListView()) + ->setViewer($viewer) ->setFlush(true) ->addItem($item_view); @@ -268,6 +320,10 @@ final class DiffusionCommitGraphView return $show_revisions; } + private function shouldShowAuditors() { + return $this->getShowAuditors(); + } + private function newHistoryItems() { $items = array(); @@ -367,24 +423,6 @@ final class DiffusionCommitGraphView return $commit->newCommitAuthorView($viewer); } - private function newBrowseButton($hash) { - $repository = $this->getRepository(); - - if ($repository) { - $drequest = $this->getDiffusionRequest(); - - return $this->linkBrowse( - $drequest->getPath(), - array( - 'commit' => $hash, - 'branch' => $drequest->getBranch(), - ), - $as_button = true); - } - - return null; - } - private function getCommit($hash) { $commit_map = $this->getCommitMap(); return idx($commit_map, $hash); @@ -399,18 +437,84 @@ final class DiffusionCommitGraphView return $this->commitMap; } - private function newBuildView($hash) { + private function addBrowseAction(PHUIObjectItemView $item, $hash) { + $repository = $this->getRepository(); + + if (!$repository) { + return; + } + + $drequest = $this->getDiffusionRequest(); + $path = $drequest->getPath(); + + $uri = $drequest->generateURI( + array( + 'action' => 'browse', + 'path' => $path, + )); + + $item->newAction() + ->setIcon('fa-folder-open-o bluegrey') + ->setName(pht('Browse Repository')) + ->setHref($uri); + } + + private function addBuildAction(PHUIObjectItemView $item, $hash) { + $is_disabled = true; + + $buildable = null; + $commit = $this->getCommit($hash); if (!$commit) { - return null; + $buildable = $this->getBuildable($commit); } - $buildable = $this->getBuildable($commit); - if (!$buildable) { - return null; + if ($buildable) { + $icon = $buildable->getStatusIcon(); + $color = $buildable->getStatusColor(); + $name = $buildable->getStatusDisplayName(); + $uri = $buildable->getURI(); + } else { + $icon = 'fa-times'; + $color = 'grey'; + $name = pht('No Builds'); + $uri = null; } - return $this->renderBuildable($buildable, 'button'); + $item->newAction() + ->setIcon($icon.' '.$color) + ->setName($name) + ->setHref($uri) + ->setDisabled(($uri === null)); + } + + private function addAuditAction(PHUIObjectItemView $item_view, $hash) { + $commit = $this->getCommit($hash); + + if ($commit) { + $status = $commit->getAuditStatusObject(); + + $text = $status->getName(); + $color = $status->getColor(); + $icon = $status->getIcon(); + + $uri = $commit->getURI(); + + $is_disabled = $status->isNoAudit(); + } else { + $text = pht('No Audit'); + $color = 'grey'; + $icon = 'fa-times'; + $uri = null; + + $is_disabled = true; + } + + $item_view->newAction() + ->setIcon($icon.' '.$color) + ->setName($text) + ->setHref($uri) + ->setDisabled($is_disabled); } private function getBuildable(PhabricatorRepositoryCommit $commit) { @@ -428,28 +532,6 @@ final class DiffusionCommitGraphView return $this->buildableMap; } - private function newRevisionView($hash) { - $commit = $this->getCommit($hash); - if (!$commit) { - return null; - } - - $revisions = $this->getRevisions($commit); - if (!$revisions) { - return null; - } - - $revision = head($revisions); - - return id(new PHUITagView()) - ->setName($revision->getMonogram()) - ->setType(PHUITagView::TYPE_SHADE) - ->setColor(PHUITagView::COLOR_BLUE) - ->setHref($revision->getURI()) - ->setBorder(PHUITagView::BORDER_NONE) - ->setSlimShady(true); - } - private function getRevisions(PhabricatorRepositoryCommit $commit) { $revision_map = $this->getRevisionMap(); return idx($revision_map, $commit->getPHID(), array()); @@ -510,4 +592,21 @@ final class DiffusionCommitGraphView return $commits; } + private function newAuditorList( + PhabricatorRepositoryCommit $commit, + $handles) { + + $auditors = $commit->getAudits(); + if (!$auditors) { + return phutil_tag('em', array(), pht('None')); + } + + $auditor_phids = mpull($auditors, 'getAuditorPHID'); + $auditor_list = $handles->newSublist($auditor_phids) + ->renderList() + ->setAsInline(true); + + return $auditor_list; + } + } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index b5ad5a7fd6..b96dfc748f 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -16,6 +16,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $bylines = array(); private $grippable; private $actions = array(); + private $actionItems = array(); private $headIcons = array(); private $disabled; private $imageURI; @@ -29,6 +30,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $coverImage; private $description; private $clickable; + private $propertyLists = array(); private $selectableName; private $selectableValue; @@ -212,6 +214,18 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function newAction() { + $action = new PhabricatorActionView(); + $this->actionItems[] = $action; + return $action; + } + + public function newPropertyList() { + $list = new PHUIPropertyListView(); + $this->propertyLists[] = $list; + return $list; + } + /** * This method has been deprecated, use @{method:setImageIcon} instead. * @@ -598,6 +612,14 @@ final class PHUIObjectItemView extends AphrontTagView { ''); } + $property_lists = null; + if ($this->propertyLists) { + $property_lists[] = phutil_tag( + 'div', + array(), + $this->propertyLists); + } + $content = phutil_tag( 'div', array( @@ -606,6 +628,7 @@ final class PHUIObjectItemView extends AphrontTagView { array( $subhead, $attrs, + $property_lists, $this->renderChildren(), )); @@ -733,6 +756,23 @@ final class PHUIObjectItemView extends AphrontTagView { )); } + $column4 = null; + if ($this->actionItems) { + $action_list = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + foreach ($this->actionItems as $action_item) { + $action_list->addAction($action_item); + } + + $column4 = phutil_tag( + 'div', + array( + 'class' => 'phui-oi-col2 phui-oi-action-list', + ), + $action_list); + } + $table = phutil_tag( 'div', array( @@ -745,6 +785,7 @@ final class PHUIObjectItemView extends AphrontTagView { $column1, $column2, $column3, + $column4, ))); $box = phutil_tag( diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 85f83802e7..f38731f3da 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -725,3 +725,9 @@ ul.phui-oi-list-view .phui-oi-selectable padding: 8px 0; background: linear-gradient({$lightbluebackground}, #fff 66%, #fff); } + +.phui-oi-action-list { + background: {$lightbluebackground}; + border-left: 1px solid {$thinblueborder}; + padding: 4px; +} From 57f9450bcfc0f173bb497aaf97bf04b1f559019e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Jul 2020 14:07:17 -0700 Subject: [PATCH 42/80] Improve desktop and mobile layouts for new "CommitGridView" Summary: Ref T13552. The current layout doesn't work particularly well on desktops or devices. We have some device/desktop table layout code, but it isn't generic. We also have property list layout code, but it isn't generic either. Provide generic layout elements ("Fuel", from "Phabricator UI Layout" to "PHUIL"?) and narrowly specialize their display behavior. Then swap the ListItemView stuff to use it. Test Plan: Saw slightly better responsive behavior: {F7637457} Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21418 --- resources/celerity/map.php | 4 ++ src/__phutil_library_map__.php | 14 ++++ .../view/DiffusionCommitGraphView.php | 20 +++--- src/view/fuel/FuelComponentView.php | 35 +++++++++ src/view/fuel/FuelGridCellView.php | 28 ++++++++ src/view/fuel/FuelGridRowView.php | 32 +++++++++ src/view/fuel/FuelGridView.php | 41 +++++++++++ src/view/fuel/FuelMapItemView.php | 54 ++++++++++++++ src/view/fuel/FuelMapView.php | 45 ++++++++++++ src/view/fuel/FuelView.php | 10 +++ src/view/phui/PHUIObjectItemView.php | 29 +++++--- webroot/rsrc/css/fuel/fuel-grid.css | 29 ++++++++ webroot/rsrc/css/fuel/fuel-map.css | 71 +++++++++++++++++++ 13 files changed, 392 insertions(+), 20 deletions(-) create mode 100644 src/view/fuel/FuelComponentView.php create mode 100644 src/view/fuel/FuelGridCellView.php create mode 100644 src/view/fuel/FuelGridRowView.php create mode 100644 src/view/fuel/FuelGridView.php create mode 100644 src/view/fuel/FuelMapItemView.php create mode 100644 src/view/fuel/FuelMapView.php create mode 100644 src/view/fuel/FuelView.php create mode 100644 webroot/rsrc/css/fuel/fuel-grid.css create mode 100644 webroot/rsrc/css/fuel/fuel-map.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fb63b68822..447d96d7e1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -121,6 +121,8 @@ return array( 'rsrc/css/font/font-awesome.css' => '3883938a', 'rsrc/css/font/font-lato.css' => '23631304', 'rsrc/css/font/phui-font-icon-base.css' => '303c9b87', + 'rsrc/css/fuel/fuel-grid.css' => 'd87031e7', + 'rsrc/css/fuel/fuel-map.css' => 'd6e31510', 'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28', 'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4', 'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa', @@ -574,6 +576,8 @@ return array( 'diviner-shared-css' => '4bd263b0', 'font-fontawesome' => '3883938a', 'font-lato' => '23631304', + 'fuel-grid-css' => 'd87031e7', + 'fuel-map-css' => 'd6e31510', 'global-drag-and-drop-css' => '1d2713a4', 'harbormaster-css' => '8dfe16b2', 'herald-css' => '648d39e2', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9828fca099..1578a69868 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1305,6 +1305,13 @@ phutil_register_library_map(array( 'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php', 'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php', 'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php', + 'FuelComponentView' => 'view/fuel/FuelComponentView.php', + 'FuelGridCellView' => 'view/fuel/FuelGridCellView.php', + 'FuelGridRowView' => 'view/fuel/FuelGridRowView.php', + 'FuelGridView' => 'view/fuel/FuelGridView.php', + 'FuelMapItemView' => 'view/fuel/FuelMapItemView.php', + 'FuelMapView' => 'view/fuel/FuelMapView.php', + 'FuelView' => 'view/fuel/FuelView.php', 'FundBacker' => 'applications/fund/storage/FundBacker.php', 'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php', 'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php', @@ -7439,6 +7446,13 @@ phutil_register_library_map(array( 'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', + 'FuelComponentView' => 'FuelView', + 'FuelGridCellView' => 'FuelView', + 'FuelGridRowView' => 'FuelView', + 'FuelGridView' => 'FuelComponentView', + 'FuelMapItemView' => 'AphrontView', + 'FuelMapView' => 'FuelComponentView', + 'FuelView' => 'AphrontView', 'FundBacker' => array( 'FundDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index 124a94fc69..6682e1bf66 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -164,21 +164,23 @@ final class DiffusionCommitGraphView $this->addAuditAction($item_view, $hash); if ($show_auditors) { - $auditor_list = $item_view->newPropertyList(); + $auditor_list = $item_view->newMapView(); if ($commit) { $auditors = $this->newAuditorList($commit, $handles); - $auditor_list->addProperty(pht('Auditors'), $auditors); + $auditor_list->newItem() + ->setName(pht('Auditors')) + ->setValue($auditors); } } - $property_list = $item_view->newPropertyList(); + $property_list = $item_view->newMapView(); if ($commit) { $author_view = $this->getCommitAuthorView($commit); if ($author_view) { - $property_list->addProperty( - pht('Author'), - $this->getCommitAuthorView($commit)); + $property_list->newItem() + ->setName(pht('Author')) + ->setValue($author_view); } } @@ -189,9 +191,9 @@ final class DiffusionCommitGraphView $revision = head($revisions); $handle = $handles[$revision->getPHID()]; - $property_list->addProperty( - pht('Revision'), - $handle->renderLink()); + $property_list->newItem() + ->setName(pht('Revision')) + ->setValue($handle->renderLink()); } } } diff --git a/src/view/fuel/FuelComponentView.php b/src/view/fuel/FuelComponentView.php new file mode 100644 index 0000000000..dda96a7bd5 --- /dev/null +++ b/src/view/fuel/FuelComponentView.php @@ -0,0 +1,35 @@ +classes[] = $class; + return $this; + } + + private function getClasses() { + return $this->classes; + } + + final protected function newComponentTag( + $tag, + array $attributes, + $content) { + + $classes = $this->getClasses(); + if (isset($attributes['class'])) { + $classes[] = $attributes['class']; + } + + if ($classes) { + $classes = implode(' ', $classes); + $attributes['class'] = $classes; + } + + return javelin_tag($tag, $attributes, $content); + } + +} diff --git a/src/view/fuel/FuelGridCellView.php b/src/view/fuel/FuelGridCellView.php new file mode 100644 index 0000000000..edb6391843 --- /dev/null +++ b/src/view/fuel/FuelGridCellView.php @@ -0,0 +1,28 @@ +content = $content; + return $this; + } + + public function getContent() { + return $this->content; + } + + public function render() { + $content = $this->getContent(); + + return phutil_tag( + 'div', + array( + 'class' => 'fuel-grid-cell', + ), + $content); + } + +} diff --git a/src/view/fuel/FuelGridRowView.php b/src/view/fuel/FuelGridRowView.php new file mode 100644 index 0000000000..f8fd8b73d7 --- /dev/null +++ b/src/view/fuel/FuelGridRowView.php @@ -0,0 +1,32 @@ +cells[] = $cell; + return $cell; + } + + public function render() { + $cells = $this->cells; + + $classes = array(); + $classes[] = 'fuel-grid-row'; + + $classes[] = sprintf( + 'fuel-grid-cell-count-%d', + count($cells)); + + return phutil_tag( + 'div', + array( + 'class' => implode(' ', $classes), + ), + $cells); + } + +} diff --git a/src/view/fuel/FuelGridView.php b/src/view/fuel/FuelGridView.php new file mode 100644 index 0000000000..105a7ac6b4 --- /dev/null +++ b/src/view/fuel/FuelGridView.php @@ -0,0 +1,41 @@ +rows[] = $row; + return $row; + } + + public function render() { + require_celerity_resource('fuel-grid-css'); + + $rows = $this->rows; + + $body = phutil_tag( + 'div', + array( + 'class' => 'fuel-grid-body', + ), + $rows); + + $grid = phutil_tag( + 'div', + array( + 'class' => 'fuel-grid', + ), + $body); + + return $this->newComponentTag( + 'div', + array( + 'class' => 'fuel-grid-component', + ), + $grid); + } + +} diff --git a/src/view/fuel/FuelMapItemView.php b/src/view/fuel/FuelMapItemView.php new file mode 100644 index 0000000000..abf64c1d84 --- /dev/null +++ b/src/view/fuel/FuelMapItemView.php @@ -0,0 +1,54 @@ +name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setValue($value) { + $this->value = $value; + return $this; + } + + public function getValue() { + return $this->value; + } + + public function render() { + $value = $this->getValue(); + + $view = array(); + + $view[] = phutil_tag( + 'div', + array( + 'class' => 'fuel-map-name', + ), + $this->getName()); + + $view[] = phutil_tag( + 'div', + array( + 'class' => 'fuel-map-value', + ), + $value); + + return phutil_tag( + 'div', + array( + 'class' => 'fuel-map-pair', + ), + $view); + } + +} diff --git a/src/view/fuel/FuelMapView.php b/src/view/fuel/FuelMapView.php new file mode 100644 index 0000000000..d3a677a99f --- /dev/null +++ b/src/view/fuel/FuelMapView.php @@ -0,0 +1,45 @@ +items[] = $item; + return $item; + } + + public function render() { + require_celerity_resource('fuel-map-css'); + + $items = $this->items; + + if (!$items) { + return null; + } + + $body = phutil_tag( + 'div', + array( + 'class' => 'fuel-map-items', + ), + $items); + + $map = phutil_tag( + 'div', + array( + 'class' => 'fuel-map', + ), + $body); + + return $this->newComponentTag( + 'div', + array( + 'class' => 'fuel-map-component', + ), + $map); + } + +} diff --git a/src/view/fuel/FuelView.php b/src/view/fuel/FuelView.php new file mode 100644 index 0000000000..f0e6b795ec --- /dev/null +++ b/src/view/fuel/FuelView.php @@ -0,0 +1,10 @@ +propertyLists[] = $list; + public function newMapView() { + $list = id(new FuelMapView()) + ->addClass('fuel-map-property-list'); + $this->mapViews[] = $list; return $list; } @@ -612,12 +613,18 @@ final class PHUIObjectItemView extends AphrontTagView { ''); } - $property_lists = null; - if ($this->propertyLists) { - $property_lists[] = phutil_tag( - 'div', - array(), - $this->propertyLists); + $map_views = null; + if ($this->mapViews) { + $grid = id(new FuelGridView()) + ->addClass('fuel-grid-property-list'); + + $row = $grid->newRow(); + foreach ($this->mapViews as $map_view) { + $row->newCell() + ->setContent($map_view); + } + + $map_views = $grid; } $content = phutil_tag( @@ -628,7 +635,7 @@ final class PHUIObjectItemView extends AphrontTagView { array( $subhead, $attrs, - $property_lists, + $map_views, $this->renderChildren(), )); diff --git a/webroot/rsrc/css/fuel/fuel-grid.css b/webroot/rsrc/css/fuel/fuel-grid.css new file mode 100644 index 0000000000..5014bc5cb4 --- /dev/null +++ b/webroot/rsrc/css/fuel/fuel-grid.css @@ -0,0 +1,29 @@ +/** + * @provides fuel-grid-css + */ + +.device-desktop .fuel-grid { + display: table; + table-layout: fixed; +} + +.device-desktop .fuel-grid-body { + display: table-row-group; +} + +.device-desktop .fuel-grid-row { + display: table-row; +} + +.device-desktop .fuel-grid-cell { + display: table-cell; + vertical-align: top; +} + +.device-desktop .fuel-grid-cell-count-2 > .fuel-grid-cell { + width: 50%; +} + +.fuel-grid-property-list > .fuel-grid { + width: 100%; +} diff --git a/webroot/rsrc/css/fuel/fuel-map.css b/webroot/rsrc/css/fuel/fuel-map.css new file mode 100644 index 0000000000..b878689cc0 --- /dev/null +++ b/webroot/rsrc/css/fuel/fuel-map.css @@ -0,0 +1,71 @@ +/** + * @provides fuel-map-css + */ + +.device-desktop .fuel-map { + display: table; + table-layout: fixed; +} + +.device-desktop .fuel-map-items { + display: table-row-group; +} + +.device-desktop .fuel-map-pair { + display: table-row; +} + +.device-desktop .fuel-map-name, +.device-desktop .fuel-map-value { + display: table-cell; + vertical-align: top; +} + +.fuel-map-property-list { + margin: 4px; +} + +.fuel-map-property-list > .fuel-map { + overflow: hidden; + width: 100%; + max-width: 100%; +} + +.fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-name, +.fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-value { + padding: 2px 4px; + white-space: nowrap; +} + +.fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-name { + color: {$bluetext}; +} + +.fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-value { + color: {$lightgreytext}; +} + +.device-desktop + .fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-name { + text-align: right; + min-width: 80px; + width: 80px; +} + +.device-desktop + .fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-value { + text-overflow: ellipsis; + overflow: hidden; +} + +.device + .fuel-map-property-list > .fuel-map > .fuel-map-items > .fuel-map-pair > + .fuel-map-value { + padding-left: 12px; +} From 49af92e903252c3e833267a9520779fa47710aad Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 17 Jul 2020 11:57:10 -0700 Subject: [PATCH 43/80] Improve commit action item layout on mobile Summary: Ref T13552. Build the "commit list" elements so that the menu action items collapse under the element on mobile. Also change the mobile breakpoint to 512px because my Safari window can't go any narrower than 508px. Future changes to responsive design will be more content-aware anyway. Test Plan: Looked at commits in various interfaces, at desktop and mobile widths. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21430 --- resources/celerity/map.php | 32 ++--- src/__phutil_library_map__.php | 6 +- .../view/DiffusionCommitGraphView.php | 33 +++-- src/view/fuel/FuelGridCellView.php | 4 +- src/view/fuel/FuelMenuItemView.php | 114 ++++++++++++++++++ src/view/fuel/FuelMenuView.php | 38 ++++++ src/view/phui/PHUIIconView.php | 5 + src/view/phui/PHUIObjectItemView.php | 47 ++++---- webroot/rsrc/css/fuel/fuel-grid.css | 18 +-- webroot/rsrc/css/fuel/fuel-menu.css | 52 ++++++++ .../phui/object-item/phui-oi-list-view.css | 16 ++- webroot/rsrc/js/core/behavior-device.js | 2 +- 12 files changed, 303 insertions(+), 64 deletions(-) create mode 100644 src/view/fuel/FuelMenuItemView.php create mode 100644 src/view/fuel/FuelMenuView.php create mode 100644 webroot/rsrc/css/fuel/fuel-menu.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 447d96d7e1..91e875769d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '9cb7cb3f', - 'core.pkg.js' => '845355f4', + 'core.pkg.css' => '2ffd6897', + 'core.pkg.js' => 'adc34883', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', 'differential.pkg.js' => '218fda21', @@ -121,8 +121,9 @@ return array( 'rsrc/css/font/font-awesome.css' => '3883938a', 'rsrc/css/font/font-lato.css' => '23631304', 'rsrc/css/font/phui-font-icon-base.css' => '303c9b87', - 'rsrc/css/fuel/fuel-grid.css' => 'd87031e7', + 'rsrc/css/fuel/fuel-grid.css' => '66697240', 'rsrc/css/fuel/fuel-map.css' => 'd6e31510', + 'rsrc/css/fuel/fuel-menu.css' => 'cb35abe3', 'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28', 'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4', 'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa', @@ -135,7 +136,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '4b0def39', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'ecd651d5', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', 'rsrc/css/phui/phui-action-list.css' => '1b0085b2', 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', @@ -475,7 +476,7 @@ return array( 'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3', 'rsrc/js/core/behavior-copy.js' => 'cf32921f', 'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94', - 'rsrc/js/core/behavior-device.js' => '0cf79f45', + 'rsrc/js/core/behavior-device.js' => 'ac2b1e01', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5', 'rsrc/js/core/behavior-fancy-datepicker.js' => '956f3eeb', 'rsrc/js/core/behavior-form.js' => '55d7b788', @@ -576,8 +577,9 @@ return array( 'diviner-shared-css' => '4bd263b0', 'font-fontawesome' => '3883938a', 'font-lato' => '23631304', - 'fuel-grid-css' => 'd87031e7', + 'fuel-grid-css' => '66697240', 'fuel-map-css' => 'd6e31510', + 'fuel-menu-css' => 'cb35abe3', 'global-drag-and-drop-css' => '1d2713a4', 'harbormaster-css' => '8dfe16b2', 'herald-css' => '648d39e2', @@ -615,7 +617,7 @@ return array( 'javelin-behavior-day-view' => '727a5a61', 'javelin-behavior-desktop-notifications-control' => '070679fe', 'javelin-behavior-detect-timezone' => '78bc5d94', - 'javelin-behavior-device' => '0cf79f45', + 'javelin-behavior-device' => 'ac2b1e01', 'javelin-behavior-differential-diff-radios' => '925fe8cd', 'javelin-behavior-differential-populate' => 'b86ef6c2', 'javelin-behavior-diffusion-commit-branches' => '4b671572', @@ -870,7 +872,7 @@ return array( 'phui-oi-color-css' => 'b517bfa0', 'phui-oi-drag-ui-css' => 'da15d3dc', 'phui-oi-flush-ui-css' => '490e2e2e', - 'phui-oi-list-view-css' => '4b0def39', + 'phui-oi-list-view-css' => 'ecd651d5', 'phui-oi-simple-ui-css' => '6a30fa46', 'phui-pager-css' => 'd022c7ad', 'phui-pinboard-view-css' => '1f08f5d8', @@ -1006,13 +1008,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - '0cf79f45' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - ), '0d2490ce' => array( 'javelin-install', ), @@ -1930,6 +1925,13 @@ return array( 'javelin-dom', 'phabricator-notification', ), + 'ac2b1e01' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + ), 'ad258e28' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1578a69868..79d51c46fc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1311,6 +1311,8 @@ phutil_register_library_map(array( 'FuelGridView' => 'view/fuel/FuelGridView.php', 'FuelMapItemView' => 'view/fuel/FuelMapItemView.php', 'FuelMapView' => 'view/fuel/FuelMapView.php', + 'FuelMenuItemView' => 'view/fuel/FuelMenuItemView.php', + 'FuelMenuView' => 'view/fuel/FuelMenuView.php', 'FuelView' => 'view/fuel/FuelView.php', 'FundBacker' => 'applications/fund/storage/FundBacker.php', 'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php', @@ -7447,11 +7449,13 @@ phutil_register_library_map(array( 'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', 'FuelComponentView' => 'FuelView', - 'FuelGridCellView' => 'FuelView', + 'FuelGridCellView' => 'FuelComponentView', 'FuelGridRowView' => 'FuelView', 'FuelGridView' => 'FuelComponentView', 'FuelMapItemView' => 'AphrontView', 'FuelMapView' => 'FuelComponentView', + 'FuelMenuItemView' => 'FuelView', + 'FuelMenuView' => 'FuelComponentView', 'FuelView' => 'AphrontView', 'FundBacker' => array( 'FundDAO', diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index 6682e1bf66..abfd5da349 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -452,13 +452,17 @@ final class DiffusionCommitGraphView $uri = $drequest->generateURI( array( 'action' => 'browse', - 'path' => $path, + 'path' => $path, + 'commit' => $hash, )); - $item->newAction() - ->setIcon('fa-folder-open-o bluegrey') + $menu_item = $item->newMenuItem() ->setName(pht('Browse Repository')) - ->setHref($uri); + ->setURI($uri); + + $menu_item->newIcon() + ->setIcon('fa-folder-open-o') + ->setColor('bluegrey'); } private function addBuildAction(PHUIObjectItemView $item, $hash) { @@ -483,11 +487,14 @@ final class DiffusionCommitGraphView $uri = null; } - $item->newAction() - ->setIcon($icon.' '.$color) + $menu_item = $item->newMenuItem() ->setName($name) - ->setHref($uri) + ->setURI($uri) ->setDisabled(($uri === null)); + + $menu_item->newIcon() + ->setIcon($icon) + ->setColor($color); } private function addAuditAction(PHUIObjectItemView $item_view, $hash) { @@ -502,7 +509,7 @@ final class DiffusionCommitGraphView $uri = $commit->getURI(); - $is_disabled = $status->isNoAudit(); + $is_disabled = false; } else { $text = pht('No Audit'); $color = 'grey'; @@ -512,11 +519,15 @@ final class DiffusionCommitGraphView $is_disabled = true; } - $item_view->newAction() - ->setIcon($icon.' '.$color) + $menu_item = $item_view->newMenuItem() ->setName($text) - ->setHref($uri) + ->setURI($uri) + ->setBackgroundColor($color) ->setDisabled($is_disabled); + + $menu_item->newIcon() + ->setIcon($icon) + ->setColor($color); } private function getBuildable(PhabricatorRepositoryCommit $commit) { diff --git a/src/view/fuel/FuelGridCellView.php b/src/view/fuel/FuelGridCellView.php index edb6391843..b4e4b6ef5a 100644 --- a/src/view/fuel/FuelGridCellView.php +++ b/src/view/fuel/FuelGridCellView.php @@ -1,7 +1,7 @@ getContent(); - return phutil_tag( + return $this->newComponentTag( 'div', array( 'class' => 'fuel-grid-cell', diff --git a/src/view/fuel/FuelMenuItemView.php b/src/view/fuel/FuelMenuItemView.php new file mode 100644 index 0000000000..70799b6630 --- /dev/null +++ b/src/view/fuel/FuelMenuItemView.php @@ -0,0 +1,114 @@ +uri = $uri; + return $this; + } + + public function getURI() { + return $this->uri; + } + + public function setName($name) { + $this->name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setIcon(PHUIIconView $icon) { + $this->icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + + public function newIcon() { + $icon = new PHUIIconView(); + $this->setIcon($icon); + return $icon; + } + + public function setDisabled($disabled) { + $this->disabled = $disabled; + return $this; + } + + public function getDisabled() { + return $this->disabled; + } + + public function setBackgroundColor($background_color) { + $this->backgroundColor = $background_color; + return $this; + } + + public function getBackgroundColor() { + return $this->backgroundColor; + } + + public function render() { + $icon = $this->getIcon(); + + $name = $this->getName(); + $uri = $this->getURI(); + + $icon = phutil_tag( + 'span', + array( + 'class' => 'fuel-menu-item-icon', + ), + $icon); + + $item_link = phutil_tag( + 'a', + array( + 'href' => $uri, + 'class' => 'fuel-menu-item-link', + ), + array( + $icon, + $name, + )); + + $classes = array(); + $classes[] = 'fuel-menu-item'; + + if ($this->getDisabled()) { + $classes[] = 'disabled'; + } + + $background_color = $this->getBackgroundColor(); + if ($background_color !== null) { + $classes[] = 'fuel-menu-item-background-color-'.$background_color; + } + + + if ($uri !== null) { + $classes[] = 'has-link'; + } + + $classes = implode(' ', $classes); + + return phutil_tag( + 'div', + array( + 'class' => $classes, + ), + $item_link); + } + +} diff --git a/src/view/fuel/FuelMenuView.php b/src/view/fuel/FuelMenuView.php new file mode 100644 index 0000000000..5b35aeae09 --- /dev/null +++ b/src/view/fuel/FuelMenuView.php @@ -0,0 +1,38 @@ +items[] = $item; + return $item; + } + + public function render() { + require_celerity_resource('fuel-menu-css'); + + $items = $this->items; + + if (!$items) { + return null; + } + + $list = phutil_tag( + 'div', + array( + 'class' => 'fuel-menu', + ), + $items); + + return $this->newComponentTag( + 'div', + array( + 'class' => 'fuel-menu-component', + ), + $list); + } + +} diff --git a/src/view/phui/PHUIIconView.php b/src/view/phui/PHUIIconView.php index e3f9f076c2..a5897e02d6 100644 --- a/src/view/phui/PHUIIconView.php +++ b/src/view/phui/PHUIIconView.php @@ -57,6 +57,11 @@ final class PHUIIconView extends AphrontTagView { return $this; } + public function setColor($color) { + $this->iconColor = $color; + return $this; + } + public function getIconName() { return $this->iconFont; } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 86c8d8d380..1dfa1a198e 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -16,7 +16,6 @@ final class PHUIObjectItemView extends AphrontTagView { private $bylines = array(); private $grippable; private $actions = array(); - private $actionItems = array(); private $headIcons = array(); private $disabled; private $imageURI; @@ -31,6 +30,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $description; private $clickable; private $mapViews = array(); + private $menu; private $selectableName; private $selectableValue; @@ -214,10 +214,12 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } - public function newAction() { - $action = new PhabricatorActionView(); - $this->actionItems[] = $action; - return $action; + public function newMenuItem() { + if (!$this->menu) { + $this->menu = new FuelMenuView(); + } + + return $this->menu->newItem(); } public function newMapView() { @@ -763,23 +765,6 @@ final class PHUIObjectItemView extends AphrontTagView { )); } - $column4 = null; - if ($this->actionItems) { - $action_list = id(new PhabricatorActionListView()) - ->setViewer($viewer); - - foreach ($this->actionItems as $action_item) { - $action_list->addAction($action_item); - } - - $column4 = phutil_tag( - 'div', - array( - 'class' => 'phui-oi-col2 phui-oi-action-list', - ), - $action_list); - } - $table = phutil_tag( 'div', array( @@ -792,7 +777,6 @@ final class PHUIObjectItemView extends AphrontTagView { $column1, $column2, $column3, - $column4, ))); $box = phutil_tag( @@ -859,7 +843,22 @@ final class PHUIObjectItemView extends AphrontTagView { $frame_content, )); - return $frame; + $grid_view = id(new FuelGridView()) + ->addClass('fuel-grid-tablet'); + $grid_row = $grid_view->newRow(); + + $grid_row->newCell() + ->setContent($frame); + + if ($this->menu) { + $menu = $this->menu; + + $grid_row->newCell() + ->addClass('phui-oi-menu') + ->setContent($menu); + } + + return $grid_view; } private function renderStatusIcon($icon, $label) { diff --git a/webroot/rsrc/css/fuel/fuel-grid.css b/webroot/rsrc/css/fuel/fuel-grid.css index 5014bc5cb4..16acb3eed7 100644 --- a/webroot/rsrc/css/fuel/fuel-grid.css +++ b/webroot/rsrc/css/fuel/fuel-grid.css @@ -2,28 +2,30 @@ * @provides fuel-grid-css */ -.device-desktop .fuel-grid { +.device-desktop .fuel-grid, +.device-tablet .fuel-grid-tablet > .fuel-grid { display: table; table-layout: fixed; } -.device-desktop .fuel-grid-body { +.device-desktop .fuel-grid-body, +.device-tablet .fuel-grid-tablet > .fuel-grid > .fuel-grid-body { display: table-row-group; } -.device-desktop .fuel-grid-row { +.device-desktop .fuel-grid-row, +.device-tablet .fuel-grid-tablet > .fuel-grid > .fuel-grid-body > + .fuel-grid-row { display: table-row; } -.device-desktop .fuel-grid-cell { +.device-desktop .fuel-grid-cell, +.device-tablet .fuel-grid-tablet > .fuel-grid > .fuel-grid-body > + .fuel-grid-row > .fuel-grid-cell { display: table-cell; vertical-align: top; } -.device-desktop .fuel-grid-cell-count-2 > .fuel-grid-cell { - width: 50%; -} - .fuel-grid-property-list > .fuel-grid { width: 100%; } diff --git a/webroot/rsrc/css/fuel/fuel-menu.css b/webroot/rsrc/css/fuel/fuel-menu.css new file mode 100644 index 0000000000..d03c43f691 --- /dev/null +++ b/webroot/rsrc/css/fuel/fuel-menu.css @@ -0,0 +1,52 @@ +/** + * @provides fuel-menu-css + */ + +.fuel-menu-item-icon { + width: 14px; + height: 14px; + position: absolute; + left: 8px; + top: 4px; + text-align: center; +} + +.fuel-menu-item-link { + padding: 4px 4px 4px 28px; + position: relative; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: {$greytext}; + cursor: default; + border-radius: 3px; +} + +.fuel-menu-item.disabled > .fuel-menu-item-link { + color: {$lightgreytext}; +} + +.fuel-menu-item.has-link > .fuel-menu-item-link { + color: {$darkbluetext}; + cursor: pointer; +} + +.device-desktop .fuel-menu-item > .fuel-menu-item-link:hover { + text-decoration: none; +} + +.device-desktop .fuel-menu-item.has-link > .fuel-menu-item-link:hover { + color: {$sky}; + background: rgba({$alphablue}, .08); +} + +.fuel-menu-item-background-color-orange > .fuel-menu-item-link { + background: {$sh-orangebackground}; +} + +.device-desktop .fuel-menu-item.has-link.fuel-menu-item-background-color-orange + > .fuel-menu-item-link:hover { + background: {$lightorange}; + color: {$orange}; +} diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index f38731f3da..ea04518bdf 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -726,8 +726,20 @@ ul.phui-oi-list-view .phui-oi-selectable background: linear-gradient({$lightbluebackground}, #fff 66%, #fff); } -.phui-oi-action-list { +.phui-oi-menu { background: {$lightbluebackground}; - border-left: 1px solid {$thinblueborder}; + border-style: solid; + border-color: {$thinblueborder}; padding: 4px; } + +.device-desktop .phui-oi-menu, +.device-tablet .phui-oi-menu { + width: 200px; + border-width: 0 0 1px 1px; +} + +.device-phone .phui-oi-menu { + border-width: 0 0 1px; + margin-bottom: 12px; +} diff --git a/webroot/rsrc/js/core/behavior-device.js b/webroot/rsrc/js/core/behavior-device.js index d68a46ff4e..9e294b0663 100644 --- a/webroot/rsrc/js/core/behavior-device.js +++ b/webroot/rsrc/js/core/behavior-device.js @@ -43,7 +43,7 @@ JX.install('Device', { if (v.x <= self._tabletBreakpoint) { device = 'tablet'; } - if (v.x <= 480) { + if (v.x <= 512) { device = 'phone'; } From 0b64092d25d0533bd90185d31e5b5a30bcef1d58 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Jul 2020 10:19:20 -0700 Subject: [PATCH 44/80] Improve handle/status list display on devices in commit graph lists Summary: Ref T13552. Provide a richer handle/status list item for commit lists. Test Plan: Viewed commits in various interfaces, saw richer information. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21431 --- resources/celerity/map.php | 6 +- src/__phutil_library_map__.php | 4 + .../view/DiffusionCommitGraphView.php | 39 +++++--- .../handle/pool/PhabricatorHandleList.php | 4 + src/view/fuel/FuelHandleListItemView.php | 99 +++++++++++++++++++ src/view/fuel/FuelHandleListView.php | 58 +++++++++++ webroot/rsrc/css/fuel/fuel-handle-list.css | 33 +++++++ webroot/rsrc/css/fuel/fuel-menu.css | 8 +- 8 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 src/view/fuel/FuelHandleListItemView.php create mode 100644 src/view/fuel/FuelHandleListView.php create mode 100644 webroot/rsrc/css/fuel/fuel-handle-list.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 91e875769d..77b041e0fa 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -122,8 +122,9 @@ return array( 'rsrc/css/font/font-lato.css' => '23631304', 'rsrc/css/font/phui-font-icon-base.css' => '303c9b87', 'rsrc/css/fuel/fuel-grid.css' => '66697240', + 'rsrc/css/fuel/fuel-handle-list.css' => '2c4cbeca', 'rsrc/css/fuel/fuel-map.css' => 'd6e31510', - 'rsrc/css/fuel/fuel-menu.css' => 'cb35abe3', + 'rsrc/css/fuel/fuel-menu.css' => '21f5d199', 'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28', 'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4', 'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa', @@ -578,8 +579,9 @@ return array( 'font-fontawesome' => '3883938a', 'font-lato' => '23631304', 'fuel-grid-css' => '66697240', + 'fuel-handle-list-css' => '2c4cbeca', 'fuel-map-css' => 'd6e31510', - 'fuel-menu-css' => 'cb35abe3', + 'fuel-menu-css' => '21f5d199', 'global-drag-and-drop-css' => '1d2713a4', 'harbormaster-css' => '8dfe16b2', 'herald-css' => '648d39e2', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 79d51c46fc..b3caee7463 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1309,6 +1309,8 @@ phutil_register_library_map(array( 'FuelGridCellView' => 'view/fuel/FuelGridCellView.php', 'FuelGridRowView' => 'view/fuel/FuelGridRowView.php', 'FuelGridView' => 'view/fuel/FuelGridView.php', + 'FuelHandleListItemView' => 'view/fuel/FuelHandleListItemView.php', + 'FuelHandleListView' => 'view/fuel/FuelHandleListView.php', 'FuelMapItemView' => 'view/fuel/FuelMapItemView.php', 'FuelMapView' => 'view/fuel/FuelMapView.php', 'FuelMenuItemView' => 'view/fuel/FuelMenuItemView.php', @@ -7452,6 +7454,8 @@ phutil_register_library_map(array( 'FuelGridCellView' => 'FuelComponentView', 'FuelGridRowView' => 'FuelView', 'FuelGridView' => 'FuelComponentView', + 'FuelHandleListItemView' => 'FuelView', + 'FuelHandleListView' => 'FuelComponentView', 'FuelMapItemView' => 'AphrontView', 'FuelMapView' => 'FuelComponentView', 'FuelMenuItemView' => 'FuelView', diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index abfd5da349..a43f44f033 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -122,8 +122,16 @@ final class DiffusionCommitGraphView } } + $commits = $this->getCommitMap(); + + foreach ($commits as $commit) { + $author_phid = $commit->getAuthorDisplayPHID(); + if ($author_phid !== null) { + $phids[] = $author_phid; + } + } + if ($show_auditors) { - $commits = $this->getCommitMap(); foreach ($commits as $commit) { $audits = $commit->getAudits(); foreach ($audits as $auditor) { @@ -188,12 +196,12 @@ final class DiffusionCommitGraphView if ($commit) { $revisions = $this->getRevisions($commit); if ($revisions) { - $revision = head($revisions); - $handle = $handles[$revision->getPHID()]; + $list_view = $handles->newSublist(mpull($revisions, 'getPHID')) + ->newListView(); $property_list->newItem() - ->setName(pht('Revision')) - ->setValue($handle->renderLink()); + ->setName(pht('Revisions')) + ->setValue($list_view); } } } @@ -422,6 +430,12 @@ final class DiffusionCommitGraphView $viewer = $this->getViewer(); + $author_phid = $commit->getAuthorDisplayPHID(); + if ($author_phid) { + return $viewer->loadHandles(array($author_phid)) + ->newListView(); + } + return $commit->newCommitAuthorView($viewer); } @@ -504,12 +518,16 @@ final class DiffusionCommitGraphView $status = $commit->getAuditStatusObject(); $text = $status->getName(); - $color = $status->getColor(); $icon = $status->getIcon(); - $uri = $commit->getURI(); - - $is_disabled = false; + $is_disabled = $status->isNoAudit(); + if ($is_disabled) { + $uri = null; + $color = 'grey'; + } else { + $color = $status->getColor(); + $uri = $commit->getURI(); + } } else { $text = pht('No Audit'); $color = 'grey'; @@ -616,8 +634,7 @@ final class DiffusionCommitGraphView $auditor_phids = mpull($auditors, 'getAuditorPHID'); $auditor_list = $handles->newSublist($auditor_phids) - ->renderList() - ->setAsInline(true); + ->newListView(); return $auditor_list; } diff --git a/src/applications/phid/handle/pool/PhabricatorHandleList.php b/src/applications/phid/handle/pool/PhabricatorHandleList.php index a7b404c5a7..c3c6f40324 100644 --- a/src/applications/phid/handle/pool/PhabricatorHandleList.php +++ b/src/applications/phid/handle/pool/PhabricatorHandleList.php @@ -104,6 +104,10 @@ final class PhabricatorHandleList ->setHandleList($this); } + public function newListView() { + return id(new FuelHandleListView()) + ->addHandleList($this); + } /** * Return a @{class:PHUIHandleView} which can render a specific handle. diff --git a/src/view/fuel/FuelHandleListItemView.php b/src/view/fuel/FuelHandleListItemView.php new file mode 100644 index 0000000000..21ea14d745 --- /dev/null +++ b/src/view/fuel/FuelHandleListItemView.php @@ -0,0 +1,99 @@ +handle = $handle; + return $this; + } + + public function render() { + $cells = array(); + + $cells[] = phutil_tag( + 'div', + array( + 'class' => 'fuel-handle-list-item-cell fuel-handle-list-item-icon', + ), + $this->newIconView()); + + $cells[] = phutil_tag( + 'div', + array( + 'class' => 'fuel-handle-list-item-cell fuel-handle-list-item-handle', + ), + $this->newHandleView()); + + $cells[] = phutil_tag( + 'div', + array( + 'class' => 'fuel-handle-list-item-cell fuel-handle-list-item-note', + ), + $this->newNoteView()); + + return phutil_tag( + 'div', + array( + 'class' => 'fuel-handle-list-item', + ), + $cells); + } + + + private function newIconView() { + $icon_icon = null; + $icon_image = null; + $icon_color = null; + + $handle = $this->handle; + if ($handle) { + $icon_image = $handle->getImageURI(); + if (!$icon_image) { + $icon_icon = $handle->getIcon(); + $icon_color = $handle->getIconColor(); + } + } + + if ($icon_image === null && $icon_icon === null) { + return null; + } + + $view = new PHUIIconView(); + + if ($icon_image !== null) { + $view->setImage($icon_image); + } else { + if ($icon_color === null) { + $icon_color = 'bluegrey'; + } + + if ($icon_icon !== null) { + $view->setIcon($icon_icon); + } + + if ($icon_color !== null) { + $view->setColor($icon_color); + } + } + + + return $view; + } + + private function newHandleView() { + $handle = $this->handle; + if ($handle) { + return $handle->renderLink(); + } + + return null; + } + + private function newNoteView() { + return null; + } + +} diff --git a/src/view/fuel/FuelHandleListView.php b/src/view/fuel/FuelHandleListView.php new file mode 100644 index 0000000000..77c17ce668 --- /dev/null +++ b/src/view/fuel/FuelHandleListView.php @@ -0,0 +1,58 @@ +items[] = array( + 'type' => 'list', + 'item' => $list, + ); + return $this; + } + + public function render() { + require_celerity_resource('fuel-handle-list-css'); + + $items = $this->items; + + $item_views = array(); + foreach ($items as $item) { + $item_type = $item['type']; + $item_item = $item['item']; + + switch ($item_type) { + case 'list': + foreach ($item_item as $handle) { + $item_views[] = id(new FuelHandleListItemView()) + ->setHandle($handle); + } + break; + } + } + + $body = phutil_tag( + 'div', + array( + 'class' => 'fuel-handle-list-body', + ), + $item_views); + + $list = phutil_tag( + 'div', + array( + 'class' => 'fuel-handle-list', + ), + $body); + + return $this->newComponentTag( + 'div', + array( + 'class' => 'fuel-handle-list-component', + ), + $list); + } + +} diff --git a/webroot/rsrc/css/fuel/fuel-handle-list.css b/webroot/rsrc/css/fuel/fuel-handle-list.css new file mode 100644 index 0000000000..1a0b19f14e --- /dev/null +++ b/webroot/rsrc/css/fuel/fuel-handle-list.css @@ -0,0 +1,33 @@ +/** + * @provides fuel-handle-list-css + */ + +.fuel-handle-list { + display: table; + max-width: 100%; +} + +.fuel-handle-list-body { + display: table-row-group; +} + +.fuel-handle-list-item { + display: table-row; +} + +.fuel-handle-list-item-cell { + display: table-cell; + vertical-align: top; +} + +.fuel-handle-list-item-icon { + padding-right: 4px; +} + +.fuel-handle-list-item-icon > .phui-icon-view { + width: 18px; + height: 18px; + text-align: center; + display: inline-block; + background-size: 100%; +} diff --git a/webroot/rsrc/css/fuel/fuel-menu.css b/webroot/rsrc/css/fuel/fuel-menu.css index d03c43f691..513804361a 100644 --- a/webroot/rsrc/css/fuel/fuel-menu.css +++ b/webroot/rsrc/css/fuel/fuel-menu.css @@ -23,15 +23,15 @@ border-radius: 3px; } -.fuel-menu-item.disabled > .fuel-menu-item-link { - color: {$lightgreytext}; -} - .fuel-menu-item.has-link > .fuel-menu-item-link { color: {$darkbluetext}; cursor: pointer; } +.fuel-menu-item.disabled > .fuel-menu-item-link { + color: {$lightgreytext}; +} + .device-desktop .fuel-menu-item > .fuel-menu-item-link:hover { text-decoration: none; } From 429543b6376fbe3a2a9bc1449951953e038577f6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 08:48:25 -0700 Subject: [PATCH 45/80] Fix some content/background overflow issues with commit graph lists Summary: Ref T13552. There are currently some content overflow issues on the graph view where the menu height can exceed the content height and the frame is drawn on a sub-element. Make the frame draw around all the content. Test Plan: Viewed commit graph history view, saw more sensible UI. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21440 --- resources/celerity/map.php | 6 ++-- src/view/phui/PHUIObjectItemView.php | 34 ++++++++++--------- .../phui/object-item/phui-oi-list-view.css | 5 ++- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 77b041e0fa..4bac12ed9e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '2ffd6897', + 'core.pkg.css' => 'b13bef94', 'core.pkg.js' => 'adc34883', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -137,7 +137,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'ecd651d5', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'af98a277', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', 'rsrc/css/phui/phui-action-list.css' => '1b0085b2', 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', @@ -874,7 +874,7 @@ return array( 'phui-oi-color-css' => 'b517bfa0', 'phui-oi-drag-ui-css' => 'da15d3dc', 'phui-oi-flush-ui-css' => '490e2e2e', - 'phui-oi-list-view-css' => 'ecd651d5', + 'phui-oi-list-view-css' => 'af98a277', 'phui-oi-simple-ui-css' => '6a30fa46', 'phui-pager-css' => 'd022c7ad', 'phui-pinboard-view-css' => '1f08f5d8', diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 1dfa1a198e..c1974bdd33 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -816,6 +816,23 @@ final class PHUIObjectItemView extends AphrontTagView { $box, )); + if ($this->menu) { + $grid_view = id(new FuelGridView()) + ->addClass('fuel-grid-tablet'); + $grid_row = $grid_view->newRow(); + + $grid_row->newCell() + ->setContent($frame_content); + + $menu = $this->menu; + + $grid_row->newCell() + ->addClass('phui-oi-menu') + ->setContent($menu); + + $frame_content = $grid_view; + } + $frame_cover = null; if ($this->coverImage) { $cover_image = phutil_tag( @@ -843,22 +860,7 @@ final class PHUIObjectItemView extends AphrontTagView { $frame_content, )); - $grid_view = id(new FuelGridView()) - ->addClass('fuel-grid-tablet'); - $grid_row = $grid_view->newRow(); - - $grid_row->newCell() - ->setContent($frame); - - if ($this->menu) { - $menu = $this->menu; - - $grid_row->newCell() - ->addClass('phui-oi-menu') - ->setContent($menu); - } - - return $grid_view; + return $frame; } private function renderStatusIcon($icon, $label) { diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index ea04518bdf..e64989f8cd 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -736,10 +736,9 @@ ul.phui-oi-list-view .phui-oi-selectable .device-desktop .phui-oi-menu, .device-tablet .phui-oi-menu { width: 200px; - border-width: 0 0 1px 1px; + border-width: 0 0 0 1px; } .device-phone .phui-oi-menu { - border-width: 0 0 1px; - margin-bottom: 12px; + border-width: 1px 0 0; } From 72f149bf399ab1ba3fb3f55c563a7e8506381a8d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Jul 2020 10:53:20 -0700 Subject: [PATCH 46/80] Require rows passed to "loadAllFromArray()" have unique keys Summary: See PHI1809, which identified a bug in Project search where queries with a large number of slugs could paginate improperly. This change detects problems in this category: cases where multiple rows with the same ID are passed to "loadAllFromArray()". It's likely that all cases it detects are cases where a GROUP BY is missing. Since this might have some false positives or detect some things which aren't fundamentally problematic, I'm planning to hold it until the next release. Test Plan: - Reverted D21399, then created a project with multiple slugs and queried for one of them via "project.search". Hit this new exeception. - Browsed around a bit, didn't immediately catch any collateral damage. Differential Revision: https://secure.phabricator.com/D21400 --- src/infrastructure/storage/lisk/LiskDAO.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php index 0bbbdd83a7..03b735d315 100644 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -653,7 +653,18 @@ abstract class LiskDAO extends Phobject foreach ($rows as $row) { $obj = clone $this; if ($id_key && isset($row[$id_key])) { - $result[$row[$id_key]] = $obj->loadFromArray($row); + $row_id = $row[$id_key]; + + if (isset($result[$row_id])) { + throw new Exception( + pht( + 'Rows passed to "loadAllFromArray(...)" include two or more '. + 'rows with the same ID ("%s"). Rows must have unique IDs. '. + 'An underlying query may be missing a GROUP BY.', + $row_id)); + } + + $result[$row_id] = $obj->loadFromArray($row); } else { $result[] = $obj->loadFromArray($row); } From 0854425d191fdf5b323f57289881435ce8c7afcb Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Sep 2020 12:07:01 -0700 Subject: [PATCH 47/80] When printing timestamps on paper: use an absolute, context-free date format Summary: Ref T13573. Using the browser "Print" feature on pages produces "Thu, Aug 4, 12:22" timestamps which require context to interpret precisely (they don't have a year and don't have a timezone). Instead, retain these timestamps in "screen" contexts but use "YYYY-MM-DD HH:MM:SS (UTC+X)" timestamps when printing. Test Plan: Printed Maniphest tasks and other pages in Safari and Chrome using "?__print__=1" and "Print to PDF", saw absolute timestamps after this chagne in the printed documents. Maniphest Tasks: T13573 Differential Revision: https://secure.phabricator.com/D21451 --- resources/celerity/map.php | 6 ++-- src/__phutil_library_map__.php | 2 ++ .../PhrictionDocumentController.php | 2 +- src/infrastructure/javelin/markup.php | 17 ++++++++++ src/view/phui/PHUICurtainObjectRefView.php | 2 +- src/view/phui/PHUIObjectItemView.php | 2 +- src/view/phui/PHUITimelineEventView.php | 3 +- src/view/viewutils.php | 33 +++++++++++++++++++ webroot/rsrc/css/core/core.css | 27 +++++++++++++++ 9 files changed, 87 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4bac12ed9e..de3a9627ff 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'b13bef94', + 'core.pkg.css' => 'bd937962', 'core.pkg.js' => 'adc34883', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -113,7 +113,7 @@ return array( 'rsrc/css/application/slowvote/slowvote.css' => '1694baed', 'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd', 'rsrc/css/application/uiexample/example.css' => 'b4795059', - 'rsrc/css/core/core.css' => '1b29ed61', + 'rsrc/css/core/core.css' => 'b3ebd90d', 'rsrc/css/core/remarkup.css' => '94c3d777', 'rsrc/css/core/syntax.css' => '548567f6', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', @@ -779,7 +779,7 @@ return array( 'phabricator-busy' => '5202e831', 'phabricator-chatlog-css' => 'abdc76ee', 'phabricator-content-source-view-css' => 'cdf0d579', - 'phabricator-core-css' => '1b29ed61', + 'phabricator-core-css' => 'b3ebd90d', 'phabricator-countdown-css' => 'bff8012f', 'phabricator-darklog' => '3b869402', 'phabricator-darkmessage' => '26cd4b73', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b3caee7463..76ad86feee 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5959,9 +5959,11 @@ phutil_register_library_map(array( 'celerity_get_resource_uri' => 'applications/celerity/api.php', 'hsprintf' => 'infrastructure/markup/render.php', 'javelin_tag' => 'infrastructure/javelin/markup.php', + 'phabricator_absolute_datetime' => 'view/viewutils.php', 'phabricator_date' => 'view/viewutils.php', 'phabricator_datetime' => 'view/viewutils.php', 'phabricator_datetimezone' => 'view/viewutils.php', + 'phabricator_dual_datetime' => 'view/viewutils.php', 'phabricator_form' => 'infrastructure/javelin/markup.php', 'phabricator_format_local_time' => 'view/viewutils.php', 'phabricator_relative_date' => 'view/viewutils.php', diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 5d30b5a90b..11b0b63174 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -401,7 +401,7 @@ final class PhrictionDocumentController $view->addProperty( pht('Last Edited'), - phabricator_datetime($content->getDateCreated(), $viewer)); + phabricator_dual_datetime($content->getDateCreated(), $viewer)); return $view; } diff --git a/src/infrastructure/javelin/markup.php b/src/infrastructure/javelin/markup.php index 1909a3bd2b..533baad65e 100644 --- a/src/infrastructure/javelin/markup.php +++ b/src/infrastructure/javelin/markup.php @@ -50,6 +50,23 @@ function javelin_tag( unset($attributes['aural']); } + if (isset($attributes['print'])) { + if ($attributes['print']) { + $class = idx($attributes, 'class', ''); + $class = rtrim('print-only '.$class); + $attributes['class'] = $class; + + // NOTE: Alternative print content is hidden from screen readers. + $attributes['aria-hidden'] = 'true'; + } else { + $class = idx($attributes, 'class', ''); + $class = rtrim('screen-only '.$class); + $attributes['class'] = $class; + } + unset($attributes['print']); + } + + return phutil_tag($tag, $attributes, $content); } diff --git a/src/view/phui/PHUICurtainObjectRefView.php b/src/view/phui/PHUICurtainObjectRefView.php index f8a04796ca..f9f383a2ec 100644 --- a/src/view/phui/PHUICurtainObjectRefView.php +++ b/src/view/phui/PHUICurtainObjectRefView.php @@ -46,7 +46,7 @@ final class PHUICurtainObjectRefView $epoch = $this->epoch; if ($epoch !== null) { - $epoch_view = phabricator_datetime($epoch, $viewer); + $epoch_view = phabricator_dual_datetime($epoch, $viewer); $epoch_cells = array(); diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index c1974bdd33..4bb9c3ea6a 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -192,7 +192,7 @@ final class PHUIObjectItemView extends AphrontTagView { } public function setEpoch($epoch) { - $date = phabricator_datetime($epoch, $this->getUser()); + $date = phabricator_dual_datetime($epoch, $this->getUser()); $this->addIcon('none', $date); return $this; } diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index 73acf437af..4bf8737758 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -591,7 +591,7 @@ final class PHUITimelineEventView extends AphrontView { } if ($date_created) { - $date = phabricator_datetime( + $date = phabricator_dual_datetime( $date_created, $this->getUser()); if ($this->anchor) { @@ -611,6 +611,7 @@ final class PHUITimelineEventView extends AphrontView { $date), ); } + $extra[] = $date; } diff --git a/src/view/viewutils.php b/src/view/viewutils.php index 7eee3e03fb..956f6ca2c2 100644 --- a/src/view/viewutils.php +++ b/src/view/viewutils.php @@ -38,6 +38,39 @@ function phabricator_time($epoch, $user) { $user->getUserSetting($time_key)); } +function phabricator_dual_datetime($epoch, $user) { + $screen_view = phabricator_datetime($epoch, $user); + $print_view = phabricator_absolute_datetime($epoch, $user); + + $screen_tag = javelin_tag( + 'span', + array( + 'print' => false, + ), + $screen_view); + + $print_tag = javelin_tag( + 'span', + array( + 'print' => true, + ), + $print_view); + + return array( + $screen_tag, + $print_tag, + ); +} + +function phabricator_absolute_datetime($epoch, $user) { + $format = 'Y-m-d H:i:s (\\U\\T\\CP)'; + + $datetime = phabricator_format_local_time($epoch, $user, $format); + $datetime = preg_replace('/(UTC[+-])0?([^:]+)(:00)?/', '\\1\\2', $datetime); + + return $datetime; +} + function phabricator_datetime($epoch, $user) { $time_key = PhabricatorTimeFormatSetting::SETTINGKEY; return phabricator_format_local_time( diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index cc14d7ab5a..ccb3d1697f 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -162,6 +162,33 @@ hr { opacity: 0.25; } +.print-only { + display: none; +} + +/* NOTE: These rules currently only work when applied to elements which + actually want "display: inline". There is no "display: auto". If there + is a future need to mix inline and block print elements, using + "display: initial" may be a reasonable approach. */ + +.printable .print-only { + display: inline; +} + +.printable .screen-only { + display: none; +} + +@media print { + .screen-only { + display: none; + } + + .print-only { + display: inline; + } +} + .routing-bar { position: fixed; top: 0; From 7daaaa8463e2a97e0f034d3c8370d4fea19c707e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Sep 2020 16:37:23 -0700 Subject: [PATCH 48/80] Remove obsolete write to "pid" property in "annihilateProcessGroup()" in Daemon Overseer Summary: Ref T13579. This property was removed in D21425, but I missed this usage site. Remove the assignment; this class no longer tracks the subprocess PID directly. Test Plan: Searched for "->pid", no further hits. Maniphest Tasks: T13579 Differential Revision: https://secure.phabricator.com/D21452 --- src/infrastructure/daemon/PhutilDaemonHandle.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/infrastructure/daemon/PhutilDaemonHandle.php b/src/infrastructure/daemon/PhutilDaemonHandle.php index 428a64a056..3792c3b773 100644 --- a/src/infrastructure/daemon/PhutilDaemonHandle.php +++ b/src/infrastructure/daemon/PhutilDaemonHandle.php @@ -356,7 +356,6 @@ final class PhutilDaemonHandle extends Phobject { posix_kill(-$pgid, SIGTERM); sleep($this->getKillDelay()); posix_kill(-$pgid, SIGKILL); - $this->pid = null; } } } From 6e1b5da1121c0a78e42ca1e0e683a4e33cedc19c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Sep 2020 08:53:39 -0700 Subject: [PATCH 49/80] Fix additional "xprintf()"-class static parameter lint errors Summary: Ref T13577. After the fix in D21453, lint identifies additional static errors in Phabricator; fix them. Test Plan: Ran `arc lint`; these messages are essentially all very obscure. Subscribers: hach-que, yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13577 Differential Revision: https://secure.phabricator.com/D21457 --- resources/sql/autopatches/20150602.mlist.2.php | 3 ++- .../PhabricatorDifferentialRebuildChangesetsWorkflow.php | 3 ++- .../xaction/DifferentialRevisionRepositoryTransaction.php | 3 ++- .../fact/chart/PhabricatorChartFunctionArgumentParser.php | 1 + .../management/HarbormasterManagementPublishWorkflow.php | 1 + .../harbormaster/storage/build/HarbormasterBuildLog.php | 2 ++ .../maniphest/constants/ManiphestTaskPriority.php | 3 +-- src/applications/metamta/adapter/PhabricatorMailAdapter.php | 1 + .../config/PhabricatorNotificationServersConfigType.php | 4 +++- .../nuance/github/__tests__/NuanceGitHubRawEventTestCase.php | 3 ++- .../phragment/controller/PhragmentRevertController.php | 1 - src/applications/policy/storage/PhabricatorPolicy.php | 3 +-- src/applications/project/icon/PhabricatorProjectIconSet.php | 1 + .../search/ferret/function/FerretSearchFunction.php | 4 +++- .../transactions/editengine/PhabricatorEditEngine.php | 4 +++- .../transactions/editengine/PhabricatorEditEngineSubtype.php | 3 ++- src/infrastructure/cluster/PhabricatorDatabaseRefParser.php | 2 ++ ...atorCustomFieldApplicationSearchNoneFunctionDatasource.php | 2 +- 18 files changed, 30 insertions(+), 14 deletions(-) diff --git a/resources/sql/autopatches/20150602.mlist.2.php b/resources/sql/autopatches/20150602.mlist.2.php index a8f2a090ba..26d08e6f89 100644 --- a/resources/sql/autopatches/20150602.mlist.2.php +++ b/resources/sql/autopatches/20150602.mlist.2.php @@ -40,7 +40,8 @@ foreach ($lists as $list) { if (!$username_okay) { echo pht( 'Failed to migrate mailing list "%s": unable to generate a unique '. - 'username for it.')."\n"; + 'username for it.', + $name)."\n"; continue; } diff --git a/src/applications/differential/management/PhabricatorDifferentialRebuildChangesetsWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialRebuildChangesetsWorkflow.php index 80a8ac29a8..44e5be5d4a 100644 --- a/src/applications/differential/management/PhabricatorDifferentialRebuildChangesetsWorkflow.php +++ b/src/applications/differential/management/PhabricatorDifferentialRebuildChangesetsWorkflow.php @@ -36,7 +36,8 @@ final class PhabricatorDifferentialRebuildChangesetsWorkflow throw new PhutilArgumentUsageException( pht( 'Object "%s" specified by "--revision" must be a Differential '. - 'revision.')); + 'revision.', + $revision_identifier)); } } else { $revision = id(new DifferentialRevisionQuery()) diff --git a/src/applications/differential/xaction/DifferentialRevisionRepositoryTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRepositoryTransaction.php index 63c7f2c32d..f92871ca16 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRepositoryTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRepositoryTransaction.php @@ -84,7 +84,8 @@ final class DifferentialRevisionRepositoryTransaction $errors[] = $this->newInvalidError( pht( 'Repository "%s" is not a valid repository, or you do not have '. - 'permission to view it.'), + 'permission to view it.', + $new_value), $xaction); } } diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php index 01fec105a3..a3b8554c5e 100644 --- a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php +++ b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php @@ -43,6 +43,7 @@ final class PhabricatorChartFunctionArgumentParser pht( 'Chart function "%s" emitted an argument specification ("%s") with '. 'no type. Each argument specification must have a valid type.', + $this->getFunctionArgumentSignature(), $name)); } diff --git a/src/applications/harbormaster/management/HarbormasterManagementPublishWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementPublishWorkflow.php index df42d8632f..fecce6136e 100644 --- a/src/applications/harbormaster/management/HarbormasterManagementPublishWorkflow.php +++ b/src/applications/harbormaster/management/HarbormasterManagementPublishWorkflow.php @@ -54,6 +54,7 @@ final class HarbormasterManagementPublishWorkflow pht( 'Object "%s" is not a HarbormasterBuildable (it is a "%s"). '. 'Name one or more buildables to publish, like "B123".', + $name, get_class($result))); } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 2b160445d8..5df8b30bff 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -177,6 +177,8 @@ final class HarbormasterBuildLog pht( 'Attempt to load log bytes (%d - %d) failed: failed to '. 'load a single contiguous range. Actual ranges: %s.', + $offset, + $end, implode('; ', $display_ranges))); } diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index d559299af9..8b43da132b 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -199,8 +199,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { throw new Exception( pht( 'Configuration is not valid. Maniphest priority configurations '. - 'must be dictionaries.', - $config)); + 'must be dictionaries.')); } $all_keywords = array(); diff --git a/src/applications/metamta/adapter/PhabricatorMailAdapter.php b/src/applications/metamta/adapter/PhabricatorMailAdapter.php index 8c1a6c0ba7..7a0f1bae31 100644 --- a/src/applications/metamta/adapter/PhabricatorMailAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailAdapter.php @@ -65,6 +65,7 @@ abstract class PhabricatorMailAdapter pht( 'Adapter ("%s") is configured for medium "%s", but this is not '. 'a supported delivery medium. Supported media are: %s.', + get_class($this), $medium, implode(', ', $native_map))); } diff --git a/src/applications/notification/config/PhabricatorNotificationServersConfigType.php b/src/applications/notification/config/PhabricatorNotificationServersConfigType.php index 5f0c1f7e2f..f13105a249 100644 --- a/src/applications/notification/config/PhabricatorNotificationServersConfigType.php +++ b/src/applications/notification/config/PhabricatorNotificationServersConfigType.php @@ -98,7 +98,9 @@ final class PhabricatorNotificationServersConfigType 'Notification server configuration describes an invalid host '. '("%s", at index "%s"). This is an "admin" service but it has a '. '"path" property. This property is only valid for "client" '. - 'services.')); + 'services.', + $host, + $index)); } // We can't guarantee that you didn't just give the same host two diff --git a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php index 29b9e3c0a9..4af9a440e2 100644 --- a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php +++ b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php @@ -85,7 +85,8 @@ final class NuanceGitHubRawEventTestCase throw new Exception( pht( 'Expected test file "%s" to contain exactly two sections, '. - 'but it has more than two sections.')); + 'but it has more than two sections.', + $file)); } list($input, $expect) = $parts; diff --git a/src/applications/phragment/controller/PhragmentRevertController.php b/src/applications/phragment/controller/PhragmentRevertController.php index e9d56eb112..b9aa050327 100644 --- a/src/applications/phragment/controller/PhragmentRevertController.php +++ b/src/applications/phragment/controller/PhragmentRevertController.php @@ -71,7 +71,6 @@ final class PhragmentRevertController extends PhragmentController { ->appendParagraph(pht( 'Reverting this fragment to version %d will create a new version of '. 'the fragment. It will not delete any version history.', - $version->getSequence(), $version->getSequence())); return id(new AphrontDialogResponse())->setDialog($dialog); } diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php index 66a7d9e3be..7904f17927 100644 --- a/src/applications/policy/storage/PhabricatorPolicy.php +++ b/src/applications/policy/storage/PhabricatorPolicy.php @@ -266,8 +266,7 @@ final class PhabricatorPolicy return pht( 'Members of a particular project can take this action. (You '. 'can not see this object, so the name of this project is '. - 'restricted.)', - $handle->getFullName()); + 'restricted.)'); } else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) { return pht( '%s can take this action.', diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php index b128a35cad..5462da78d6 100644 --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -224,6 +224,7 @@ final class PhabricatorProjectIconSet 'Icon key "%s" is not a valid icon key. Icon keys must be 1-32 '. 'characters long and contain only lowercase letters. For example, '. '"%s" and "%s" are reasonable keys.', + $value['key'], 'tag', 'group')); } diff --git a/src/applications/search/ferret/function/FerretSearchFunction.php b/src/applications/search/ferret/function/FerretSearchFunction.php index 60886c2fb6..8019a741ca 100644 --- a/src/applications/search/ferret/function/FerretSearchFunction.php +++ b/src/applications/search/ferret/function/FerretSearchFunction.php @@ -17,7 +17,8 @@ abstract class FerretSearchFunction pht( 'Ferret search engine function name ("%s") is invalid. Function '. 'names must be nonempty and may only contain latin letters and '. - 'hyphens.')); + 'hyphens.', + $function_name)); } } @@ -77,6 +78,7 @@ abstract class FerretSearchFunction 'Ferret function "%s" is specified with a denormalized name. '. 'Instead, specify the function using the normalized '. 'function name ("%s").', + $function_name, $normal_name)); } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 1c142847a3..ea1d7ed773 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -2548,7 +2548,9 @@ abstract class PhabricatorEditEngine pht( 'Extension "%s" defines a bulk edit group with the same key '. '("%s") as the main editor or another extension. Each bulk '. - 'edit group must have a unique key.')); + 'edit group must have a unique key.', + get_class($extension), + $key)); } $map[$key] = $group; diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php b/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php index f471fcd92f..d177595a2b 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php @@ -204,7 +204,8 @@ final class PhabricatorEditEngineSubtype pht( 'Subtype configuration is invalid: subtype with key "%s" '. 'specifies both child subtypes and child forms. Specify one '. - 'or the other, but not both.')); + 'or the other, but not both.', + $key)); } } diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php b/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php index 989095c52c..db498e8a82 100644 --- a/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php @@ -205,6 +205,8 @@ final class PhabricatorDatabaseRefParser 'Database "%s" is configured as a replica and specifies a '. 'master ("%s"), but that master is not a valid master. Valid '. 'masters are: %s.', + $ref->getRefKey(), + $master_key, implode(', ', $master_keys))); } diff --git a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php index 591e4fda01..7302da3181 100644 --- a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php +++ b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php @@ -22,7 +22,7 @@ final class PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource 'summary' => pht('Find results with no value.'), 'description' => pht( "This function includes results which have no value. Use a query ". - "like this to find results with no value:\n\n%s\n\n", + "like this to find results with no value:\n\n%s\n\n". 'If you combine this function with other constraints, results '. 'which have no value or the specified values will be returned.', '> any()'), From 3dfa89dd5d27a0484e94ac7e13115bec3692b99b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 15 Sep 2020 09:06:59 -0700 Subject: [PATCH 50/80] Update SES API to use AWSv4 signatures Summary: Ref T13570. Fixes T13235. In most cases, we use modern (v4) signatures for almost all AWS API calls, and have for several years. However, sending email via SES currently uses an older piece of external code which uses the older (v3) signature method. AWS is retiring v3 signatures on October 1 2020, so this pathway will stop working. Update the pathway to use `PhutilAWSFuture`, which provides v4 signatures. T13235 discusses poor error messages from SES. Switching to Futures fixes this for free, as they have more useful error handling. Test Plan: - Configured an SES mailer, including the new `region` parameter. - Used `bin/mail send-test` to send mail via SES. - Sent invalid mail (from an unverified address); got a more useful error message. - Grepped for removed external, no hits. Maniphest Tasks: T13570, T13235 Differential Revision: https://secure.phabricator.com/D21461 --- externals/amazon-ses/ses.php | 722 ------------------ src/__phutil_library_map__.php | 2 + .../PhabricatorMailAmazonSESAdapter.php | 34 +- .../PhabricatorMailAdapterTestCase.php | 1 + .../future/PhabricatorAWSSESFuture.php | 21 + .../configuring_outbound_email.diviner | 4 +- 6 files changed, 50 insertions(+), 734 deletions(-) delete mode 100644 externals/amazon-ses/ses.php create mode 100644 src/applications/metamta/future/PhabricatorAWSSESFuture.php diff --git a/externals/amazon-ses/ses.php b/externals/amazon-ses/ses.php deleted file mode 100644 index 9968e33ac9..0000000000 --- a/externals/amazon-ses/ses.php +++ /dev/null @@ -1,722 +0,0 @@ -__accessKey; } - public function getSecretKey() { return $this->__secretKey; } - public function getHost() { return $this->__host; } - - protected $__verifyHost = 1; - protected $__verifyPeer = 1; - - // verifyHost and verifyPeer determine whether curl verifies ssl certificates. - // It may be necessary to disable these checks on certain systems. - // These only have an effect if SSL is enabled. - public function verifyHost() { return $this->__verifyHost; } - public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; } - - public function verifyPeer() { return $this->__verifyPeer; } - public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; } - - // If you use exceptions, errors will be communicated by throwing a - // SimpleEmailServiceException. By default, they will be trigger_error()'d. - protected $__useExceptions = 0; - public function useExceptions() { return $this->__useExceptions; } - public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; } - - /** - * Constructor - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @return void - */ - public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') { - if (!function_exists('simplexml_load_string')) { - throw new Exception( - pht( - 'The PHP SimpleXML extension is not available, but this '. - 'extension is required to send mail via Amazon SES, because '. - 'Amazon SES returns API responses in XML format. Install or '. - 'enable the SimpleXML extension.')); - } - - // Catch mistakes with reading the wrong column out of the SES - // documentation. See T10728. - if (preg_match('(-smtp)', $host)) { - throw new Exception( - pht( - 'Amazon SES is not configured correctly: the configured SES '. - 'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '. - 'endpoint.', - $host)); - } - - if ($accessKey !== null && $secretKey !== null) { - $this->setAuth($accessKey, $secretKey); - } - - $this->__host = $host; - } - - /** - * Set AWS access key and secret key - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @return void - */ - public function setAuth($accessKey, $secretKey) { - $this->__accessKey = $accessKey; - $this->__secretKey = $secretKey; - } - - /** - * Lists the email addresses that have been verified and can be used as the 'From' address - * - * @return An array containing two items: a list of verified email addresses, and the request id. - */ - public function listVerifiedEmailAddresses() { - $rest = new SimpleEmailServiceRequest($this, 'GET'); - $rest->setParameter('Action', 'ListVerifiedEmailAddresses'); - - $rest = $rest->getResponse(); - - $response = array(); - if(!isset($rest->body)) { - return $response; - } - - $addresses = array(); - foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) { - $addresses[] = (string)$address; - } - - $response['Addresses'] = $addresses; - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - - return $response; - } - - /** - * Requests verification of the provided email address, so it can be used - * as the 'From' address when sending emails through SimpleEmailService. - * - * After submitting this request, you should receive a verification email - * from Amazon at the specified address containing instructions to follow. - * - * @param string email The email address to get verified - * @return The request id for this request. - */ - public function verifyEmailAddress($email) { - $rest = new SimpleEmailServiceRequest($this, 'POST'); - $rest->setParameter('Action', 'VerifyEmailAddress'); - $rest->setParameter('EmailAddress', $email); - - $rest = $rest->getResponse(); - - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - return $response; - } - - /** - * Removes the specified email address from the list of verified addresses. - * - * @param string email The email address to remove - * @return The request id for this request. - */ - public function deleteVerifiedEmailAddress($email) { - $rest = new SimpleEmailServiceRequest($this, 'DELETE'); - $rest->setParameter('Action', 'DeleteVerifiedEmailAddress'); - $rest->setParameter('EmailAddress', $email); - - $rest = $rest->getResponse(); - - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - return $response; - } - - /** - * Retrieves information on the current activity limits for this account. - * See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html - * - * @return An array containing information on this account's activity limits. - */ - public function getSendQuota() { - $rest = new SimpleEmailServiceRequest($this, 'GET'); - $rest->setParameter('Action', 'GetSendQuota'); - - $rest = $rest->getResponse(); - - $response = array(); - if(!isset($rest->body)) { - return $response; - } - - $response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend; - $response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate; - $response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours; - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - - return $response; - } - - /** - * Retrieves statistics for the last two weeks of activity on this account. - * See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html - * - * @return An array of activity statistics. Each array item covers a 15-minute period. - */ - public function getSendStatistics() { - $rest = new SimpleEmailServiceRequest($this, 'GET'); - $rest->setParameter('Action', 'GetSendStatistics'); - - $rest = $rest->getResponse(); - - $response = array(); - if(!isset($rest->body)) { - return $response; - } - - $datapoints = array(); - foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) { - $p = array(); - $p['Bounces'] = (string)$datapoint->Bounces; - $p['Complaints'] = (string)$datapoint->Complaints; - $p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts; - $p['Rejects'] = (string)$datapoint->Rejects; - $p['Timestamp'] = (string)$datapoint->Timestamp; - - $datapoints[] = $p; - } - - $response['SendDataPoints'] = $datapoints; - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - - return $response; - } - - - public function sendRawEmail($raw) { - $rest = new SimpleEmailServiceRequest($this, 'POST'); - $rest->setParameter('Action', 'SendRawEmail'); - $rest->setParameter('RawMessage.Data', base64_encode($raw)); - - $rest = $rest->getResponse(); - - $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId; - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - return $response; - } - - /** - * Given a SimpleEmailServiceMessage object, submits the message to the service for sending. - * - * @return An array containing the unique identifier for this message and a separate request id. - * Returns false if the provided message is missing any required fields. - */ - public function sendEmail($sesMessage) { - if(!$sesMessage->validate()) { - return false; - } - - $rest = new SimpleEmailServiceRequest($this, 'POST'); - $rest->setParameter('Action', 'SendEmail'); - - $i = 1; - foreach($sesMessage->to as $to) { - $rest->setParameter('Destination.ToAddresses.member.'.$i, $to); - $i++; - } - - if(is_array($sesMessage->cc)) { - $i = 1; - foreach($sesMessage->cc as $cc) { - $rest->setParameter('Destination.CcAddresses.member.'.$i, $cc); - $i++; - } - } - - if(is_array($sesMessage->bcc)) { - $i = 1; - foreach($sesMessage->bcc as $bcc) { - $rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc); - $i++; - } - } - - if(is_array($sesMessage->replyto)) { - $i = 1; - foreach($sesMessage->replyto as $replyto) { - $rest->setParameter('ReplyToAddresses.member.'.$i, $replyto); - $i++; - } - } - - $rest->setParameter('Source', $sesMessage->from); - - if($sesMessage->returnpath != null) { - $rest->setParameter('ReturnPath', $sesMessage->returnpath); - } - - if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) { - $rest->setParameter('Message.Subject.Data', $sesMessage->subject); - if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) { - $rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset); - } - } - - - if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) { - $rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext); - if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) { - $rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset); - } - } - - if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) { - $rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml); - if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) { - $rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset); - } - } - - $rest = $rest->getResponse(); - - $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId; - $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; - return $response; - } - - /** - * Trigger an error message - * - * @internal Used by member functions to output errors - * @param array $error Array containing error information - * @return string - */ - public function __triggerError($functionname, $error) - { - if($error == false) { - $message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname); - } - else if(isset($error['curl']) && $error['curl']) - { - $message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']); - } - else if(isset($error['Error'])) - { - $e = $error['Error']; - $message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']); - } - - if ($this->useExceptions()) { - throw new SimpleEmailServiceException($message); - } else { - trigger_error($message, E_USER_WARNING); - } - } - - /** - * Callback handler for 503 retries. - * - * @internal Used by SimpleDBRequest to call the user-specified callback, if set - * @param $attempt The number of failed attempts so far - * @return The retry delay in microseconds, or 0 to stop retrying. - */ - public function __executeServiceTemporarilyUnavailableRetryDelay($attempt) - { - if(is_callable($this->__serviceUnavailableRetryDelayCallback)) { - $callback = $this->__serviceUnavailableRetryDelayCallback; - return $callback($attempt); - } - return 0; - } -} - -final class SimpleEmailServiceRequest -{ - private $ses, $verb, $parameters = array(); - public $response; - - /** - * Constructor - * - * @param string $ses The SimpleEmailService object making this request - * @param string $action action - * @param string $verb HTTP verb - * @return mixed - */ - function __construct($ses, $verb) { - $this->ses = $ses; - $this->verb = $verb; - $this->response = new STDClass; - $this->response->error = false; - } - - /** - * Set request parameter - * - * @param string $key Key - * @param string $value Value - * @param boolean $replace Whether to replace the key if it already exists (default true) - * @return void - */ - public function setParameter($key, $value, $replace = true) { - if(!$replace && isset($this->parameters[$key])) - { - $temp = (array)($this->parameters[$key]); - $temp[] = $value; - $this->parameters[$key] = $temp; - } - else - { - $this->parameters[$key] = $value; - } - } - - /** - * Get the response - * - * @return object | false - */ - public function getResponse() { - - $params = array(); - foreach ($this->parameters as $var => $value) - { - if(is_array($value)) - { - foreach($value as $v) - { - $params[] = $var.'='.$this->__customUrlEncode($v); - } - } - else - { - $params[] = $var.'='.$this->__customUrlEncode($value); - } - } - - sort($params, SORT_STRING); - - // must be in format 'Sun, 06 Nov 1994 08:49:37 GMT' - $date = gmdate('D, d M Y H:i:s e'); - - $query = implode('&', $params); - - $headers = array(); - $headers[] = 'Date: '.$date; - $headers[] = 'Host: '.$this->ses->getHost(); - - $auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey(); - $auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date); - $headers[] = 'X-Amzn-Authorization: '.$auth; - - $url = 'https://'.$this->ses->getHost().'/'; - - // Basic setup - $curl = curl_init(); - curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php'); - - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0)); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0)); - - // Request types - switch ($this->verb) { - case 'GET': - $url .= '?'.$query; - break; - case 'POST': - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); - curl_setopt($curl, CURLOPT_POSTFIELDS, $query); - $headers[] = 'Content-Type: application/x-www-form-urlencoded'; - break; - case 'DELETE': - $url .= '?'.$query; - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); - break; - default: break; - } - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_HEADER, false); - - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); - curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); - - // Execute, grab errors - if (!curl_exec($curl)) { - throw new SimpleEmailServiceException( - pht( - 'Encountered an error while making an HTTP request to Amazon SES '. - '(cURL Error #%d): %s', - curl_errno($curl), - curl_error($curl))); - } - - $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - if ($this->response->code != 200) { - throw new SimpleEmailServiceException( - pht( - 'Unexpected HTTP status while making request to Amazon SES: '. - 'expected 200, got %s.', - $this->response->code)); - } - - @curl_close($curl); - - // Parse body into XML - if ($this->response->error === false && isset($this->response->body)) { - $this->response->body = simplexml_load_string($this->response->body); - - // Grab SES errors - if (!in_array($this->response->code, array(200, 201, 202, 204)) - && isset($this->response->body->Error)) { - $error = $this->response->body->Error; - $output = array(); - $output['curl'] = false; - $output['Error'] = array(); - $output['Error']['Type'] = (string)$error->Type; - $output['Error']['Code'] = (string)$error->Code; - $output['Error']['Message'] = (string)$error->Message; - $output['RequestId'] = (string)$this->response->body->RequestId; - - $this->response->error = $output; - unset($this->response->body); - } - } - - return $this->response; - } - - /** - * CURL write callback - * - * @param resource &$curl CURL resource - * @param string &$data Data - * @return integer - */ - private function __responseWriteCallback(&$curl, &$data) { - if(!isset($this->response->body)) $this->response->body = ''; - $this->response->body .= $data; - return strlen($data); - } - - /** - * Contributed by afx114 - * URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html - * PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode - * See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php - * - * @param string $var String to encode - * @return string - */ - private function __customUrlEncode($var) { - return str_replace('%7E', '~', rawurlencode($var)); - } - - /** - * Generate the auth string using Hmac-SHA256 - * - * @internal Used by SimpleDBRequest::getResponse() - * @param string $string String to sign - * @return string - */ - private function __getSignature($string) { - return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true)); - } -} - - -final class SimpleEmailServiceMessage { - - // these are public for convenience only - // these are not to be used outside of the SimpleEmailService class! - public $to, $cc, $bcc, $replyto; - public $from, $returnpath; - public $subject, $messagetext, $messagehtml; - public $subjectCharset, $messageTextCharset, $messageHtmlCharset; - - function __construct() { - $to = array(); - $cc = array(); - $bcc = array(); - $replyto = array(); - - $from = null; - $returnpath = null; - - $subject = null; - $messagetext = null; - $messagehtml = null; - - $subjectCharset = null; - $messageTextCharset = null; - $messageHtmlCharset = null; - } - - - /** - * addTo, addCC, addBCC, and addReplyTo have the following behavior: - * If a single address is passed, it is appended to the current list of addresses. - * If an array of addresses is passed, that array is merged into the current list. - */ - function addTo($to) { - if(!is_array($to)) { - $this->to[] = $to; - } - else { - $this->to = array_merge($this->to, $to); - } - } - - function addCC($cc) { - if(!is_array($cc)) { - $this->cc[] = $cc; - } - else { - $this->cc = array_merge($this->cc, $cc); - } - } - - function addBCC($bcc) { - if(!is_array($bcc)) { - $this->bcc[] = $bcc; - } - else { - $this->bcc = array_merge($this->bcc, $bcc); - } - } - - function addReplyTo($replyto) { - if(!is_array($replyto)) { - $this->replyto[] = $replyto; - } - else { - $this->replyto = array_merge($this->replyto, $replyto); - } - } - - function setFrom($from) { - $this->from = $from; - } - - function setReturnPath($returnpath) { - $this->returnpath = $returnpath; - } - - function setSubject($subject) { - $this->subject = $subject; - } - - function setSubjectCharset($charset) { - $this->subjectCharset = $charset; - } - - function setMessageFromString($text, $html = null) { - $this->messagetext = $text; - $this->messagehtml = $html; - } - - function setMessageFromFile($textfile, $htmlfile = null) { - if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) { - $this->messagetext = file_get_contents($textfile); - } - if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) { - $this->messagehtml = file_get_contents($htmlfile); - } - } - - function setMessageFromURL($texturl, $htmlurl = null) { - $this->messagetext = file_get_contents($texturl); - if($htmlurl !== null) { - $this->messagehtml = file_get_contents($htmlurl); - } - } - - function setMessageCharset($textCharset, $htmlCharset = null) { - $this->messageTextCharset = $textCharset; - $this->messageHtmlCharset = $htmlCharset; - } - - /** - * Validates whether the message object has sufficient information to submit a request to SES. - * This does not guarantee the message will arrive, nor that the request will succeed; - * instead, it makes sure that no required fields are missing. - * - * This is used internally before attempting a SendEmail or SendRawEmail request, - * but it can be used outside of this file if verification is desired. - * May be useful if e.g. the data is being populated from a form; developers can generally - * use this function to verify completeness instead of writing custom logic. - * - * @return boolean - */ - public function validate() { - if(count($this->to) == 0) - return false; - if($this->from == null || strlen($this->from) == 0) - return false; - if($this->messagetext == null) - return false; - return true; - } -} - - -/** - * Thrown by SimpleEmailService when errors occur if you call - * enableUseExceptions(true). - */ -final class SimpleEmailServiceException extends Exception { - -} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 76ad86feee..0d11820ff0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2177,6 +2177,7 @@ phutil_register_library_map(array( 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', + 'PhabricatorAWSSESFuture' => 'applications/metamta/future/PhabricatorAWSSESFuture.php', 'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php', 'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php', 'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php', @@ -8486,6 +8487,7 @@ phutil_register_library_map(array( 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorAWSSESFuture' => 'PhutilAWSFuture', 'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase', 'PhabricatorAccessLog' => 'Phobject', 'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php b/src/applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php index 793cd56091..e5711e7e47 100644 --- a/src/applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php @@ -17,6 +17,7 @@ final class PhabricatorMailAmazonSESAdapter array( 'access-key' => 'string', 'secret-key' => 'string', + 'region' => 'string', 'endpoint' => 'string', )); } @@ -25,6 +26,7 @@ final class PhabricatorMailAmazonSESAdapter return array( 'access-key' => null, 'secret-key' => null, + 'region' => null, 'endpoint' => null, ); } @@ -45,23 +47,33 @@ final class PhabricatorMailAmazonSESAdapter $mailer->Send(); } - - - /** - * @phutil-external-symbol class SimpleEmailService - */ public function executeSend($body) { $key = $this->getOption('access-key'); + $secret = $this->getOption('secret-key'); + $secret = new PhutilOpaqueEnvelope($secret); + + $region = $this->getOption('region'); $endpoint = $this->getOption('endpoint'); - $root = phutil_get_library_root('phabricator'); - $root = dirname($root); - require_once $root.'/externals/amazon-ses/ses.php'; + $data = array( + 'Action' => 'SendRawEmail', + 'RawMessage.Data' => base64_encode($body), + ); - $service = new SimpleEmailService($key, $secret, $endpoint); - $service->enableUseExceptions(true); - return $service->sendRawEmail($body); + $data = phutil_build_http_querystring($data); + + $future = id(new PhabricatorAWSSESFuture()) + ->setAccessKey($key) + ->setSecretKey($secret) + ->setRegion($region) + ->setEndpoint($endpoint) + ->setHTTPMethod('POST') + ->setData($data); + + $future->resolve(); + + return true; } } diff --git a/src/applications/metamta/adapter/__tests__/PhabricatorMailAdapterTestCase.php b/src/applications/metamta/adapter/__tests__/PhabricatorMailAdapterTestCase.php index 9c194f24c2..5922b230bc 100644 --- a/src/applications/metamta/adapter/__tests__/PhabricatorMailAdapterTestCase.php +++ b/src/applications/metamta/adapter/__tests__/PhabricatorMailAdapterTestCase.php @@ -12,6 +12,7 @@ final class PhabricatorMailAdapterTestCase array( 'access-key' => 'test', 'secret-key' => 'test', + 'region' => 'test', 'endpoint' => 'test', ), ), diff --git a/src/applications/metamta/future/PhabricatorAWSSESFuture.php b/src/applications/metamta/future/PhabricatorAWSSESFuture.php new file mode 100644 index 0000000000..71a2956420 --- /dev/null +++ b/src/applications/metamta/future/PhabricatorAWSSESFuture.php @@ -0,0 +1,21 @@ +isError()) { + return $body; + } + + return parent::didReceiveResult($result); + } + +} diff --git a/src/docs/user/configuration/configuring_outbound_email.diviner b/src/docs/user/configuration/configuring_outbound_email.diviner index 884e4e7fdb..736d4f625c 100644 --- a/src/docs/user/configuration/configuring_outbound_email.diviner +++ b/src/docs/user/configuration/configuring_outbound_email.diviner @@ -247,7 +247,9 @@ To use this mailer, set `type` to `ses`, then configure these `options`: - `access-key`: Required string. Your Amazon SES access key. - `secret-key`: Required string. Your Amazon SES secret key. - - `endpoint`: Required string. Your Amazon SES endpoint. + - `region`: Required string. Your Amazon SES region, like `us-west-2`. + - `endpoint`: Required string. Your Amazon SES endpoint, like + `email.us-west-2.amazonaws.com`. NOTE: Amazon SES **requires you to verify your "From" address**. Configure which "From" address to use by setting `metamta.default-address` in your From 367cd289270d96e41ea88bf9576f4c9ca99552d1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 09:29:52 -0700 Subject: [PATCH 51/80] Delete some commit dead parsing code Summary: Ref T13552. Neither "$hashes" or "$user" are used, and constructing them has no side effects. Test Plan: Searched for these symbols. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21441 --- .../PhabricatorRepositoryCommitMessageParserWorker.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index e589b52142..ab4719aee5 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -63,7 +63,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit = $this->commit; $author = $ref->getAuthor(); $committer = $ref->getCommitter(); - $hashes = $ref->getHashes(); $has_committer = (bool)strlen($committer); $viewer = PhabricatorUser::getOmnipotentUser(); @@ -137,13 +136,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $author_phid = $data->getCommitDetail('authorPHID'); $committer_phid = $data->getCommitDetail('committerPHID'); - $user = new PhabricatorUser(); - if ($author_phid) { - $user = $user->loadOneWhere( - 'phid = %s', - $author_phid); - } - if ($author_phid != $commit->getAuthorPHID()) { $commit->setAuthorPHID($author_phid); } From a745055813e4c4a3687df82ebbd4fb5cfc54a89b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 11:11:07 -0700 Subject: [PATCH 52/80] Lift Diffusion Conduit call proxying to the root level of Conduit Summary: Ref T13552. Some Diffusion conduit calls may only be served by a node which hosts a working copy on disk, so they're proxied if received by a different node. This capability is currently bound tightly to "DiffusionRequest", which is a bundle of context parameters used by some Diffusion calls. However, call proxying is not fundamentally a Diffusion behavior. I want to perform proxying on a "*.search" call which does not use the "DiffusionRequest" parameter bundle. Lift proxying to the root level of Conduit. Test Plan: Browsed diffusion in a clusterized repsository. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21442 --- .../conduit/method/ConduitAPIMethod.php | 12 +++ .../conduit/protocol/ConduitAPIRequest.php | 4 + .../DiffusionQueryConduitAPIMethod.php | 75 +++++++++---------- .../storage/PhabricatorRepository.php | 17 +++++ 4 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php index 0fbfaa2fc3..090235500d 100644 --- a/src/applications/conduit/method/ConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitAPIMethod.php @@ -120,9 +120,21 @@ abstract class ConduitAPIMethod public function executeMethod(ConduitAPIRequest $request) { $this->setViewer($request->getUser()); + $client = $this->newConduitCallProxyClient($request); + if ($client) { + // We're proxying, so just make an intracluster call. + return $client->callMethodSynchronous( + $this->getAPIMethodName(), + $request->getAllParameters()); + } + return $this->execute($request); } + protected function newConduitCallProxyClient(ConduitAPIRequest $request) { + return null; + } + abstract public function getAPIMethodName(); /** diff --git a/src/applications/conduit/protocol/ConduitAPIRequest.php b/src/applications/conduit/protocol/ConduitAPIRequest.php index 3a2818a47a..78988f2b3b 100644 --- a/src/applications/conduit/protocol/ConduitAPIRequest.php +++ b/src/applications/conduit/protocol/ConduitAPIRequest.php @@ -51,6 +51,10 @@ final class ConduitAPIRequest extends Phobject { return $this->user; } + public function getViewer() { + return $this->getUser(); + } + public function setOAuthToken( PhabricatorOAuthServerAccessToken $oauth_token) { $this->oauthToken = $oauth_token; diff --git a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php index ca32cc0127..cd6a45360b 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php @@ -85,6 +85,35 @@ abstract class DiffusionQueryConduitAPIMethod * should occur after @{method:getResult}, like formatting a timestamp. */ final protected function execute(ConduitAPIRequest $request) { + $drequest = $this->getDiffusionRequest(); + + // We pass this flag on to prevent proxying of any other Conduit calls + // which we need to make in order to respond to this one. Although we + // could safely proxy them, we take a big performance hit in the common + // case, and doing more proxying wouldn't exercise any additional code so + // we wouldn't gain a testability/predictability benefit. + $is_cluster_request = $request->getIsClusterRequest(); + $drequest->setIsClusterRequest($is_cluster_request); + + $viewer = $request->getViewer(); + $repository = $drequest->getRepository(); + + // TODO: Allow web UI queries opt out of this if they don't care about + // fetching the most up-to-date data? Synchronization can be slow, and a + // lot of web reads are probably fine if they're a few seconds out of + // date. + id(new DiffusionRepositoryClusterEngine()) + ->setViewer($viewer) + ->setRepository($repository) + ->synchronizeWorkingCopyBeforeRead(); + + return $this->getResult($request); + } + + + protected function newConduitCallProxyClient(ConduitAPIRequest $request) { + $viewer = $request->getViewer(); + $identifier = $request->getValue('repository'); if ($identifier === null) { $identifier = $request->getValue('callsign'); @@ -92,7 +121,7 @@ abstract class DiffusionQueryConduitAPIMethod $drequest = DiffusionRequest::newFromDictionary( array( - 'user' => $request->getUser(), + 'user' => $viewer, 'repository' => $identifier, 'branch' => $request->getValue('branch'), 'path' => $request->getValue('path'), @@ -106,46 +135,16 @@ abstract class DiffusionQueryConduitAPIMethod $identifier)); } - // Figure out whether we're going to handle this request on this device, - // or proxy it to another node in the cluster. - - // If this is a cluster request and we need to proxy, we'll explode here - // to prevent infinite recursion. - - $is_cluster_request = $request->getIsClusterRequest(); - $viewer = $request->getUser(); - $repository = $drequest->getRepository(); - $client = $repository->newConduitClient( - $viewer, - $is_cluster_request); + + $client = $repository->newConduitClientForRequest($request); if ($client) { - // We're proxying, so just make an intracluster call. - return $client->callMethodSynchronous( - $this->getAPIMethodName(), - $request->getAllParameters()); - } else { - - // We pass this flag on to prevent proxying of any other Conduit calls - // which we need to make in order to respond to this one. Although we - // could safely proxy them, we take a big performance hit in the common - // case, and doing more proxying wouldn't exercise any additional code so - // we wouldn't gain a testability/predictability benefit. - $drequest->setIsClusterRequest($is_cluster_request); - - $this->setDiffusionRequest($drequest); - - // TODO: Allow web UI queries opt out of this if they don't care about - // fetching the most up-to-date data? Synchronization can be slow, and a - // lot of web reads are probably fine if they're a few seconds out of - // date. - id(new DiffusionRepositoryClusterEngine()) - ->setViewer($viewer) - ->setRepository($repository) - ->synchronizeWorkingCopyBeforeRead(); - - return $this->getResult($request); + return $client; } + + $this->setDiffusionRequest($drequest); + + return null; } protected function getResult(ConduitAPIRequest $request) { diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 05009c0d91..71afc56281 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2241,6 +2241,23 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $client; } + public function newConduitClientForRequest(ConduitAPIRequest $request) { + // Figure out whether we're going to handle this request on this device, + // or proxy it to another node in the cluster. + + // If this is a cluster request and we need to proxy, we'll explode here + // to prevent infinite recursion. + + $viewer = $request->getViewer(); + $is_cluster_request = $request->getIsClusterRequest(); + + $client = $this->newConduitClient( + $viewer, + $is_cluster_request); + + return $client; + } + public function getPassthroughEnvironmentalVariables() { $env = $_ENV; From a9506097ea041142615e8f50ca23ad93287409c3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 10:13:42 -0700 Subject: [PATCH 53/80] Add "internal.commit.search" to replace the cache bypass mode of "diffusion.querycommits" Summary: Ref T13552. Commit parsers currently invoke a special mode of "diffusion.querycommits", which is an older frozen method. The replacement, "diffusion.commit.search", is not really appropriate for low-level access. This mode of having a single method which operates in "cache" or "non-cache" modes also ends up in a lot of unnecessary field shuffling. Provide "internal.commit.search" as a modern equivalent that returns a "DiffusionCommitRef"-compatible structure. Test Plan: Executed "internal.commit.search", got sensible low-level commit results. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21443 --- src/__phutil_library_map__.php | 4 + .../DiffusionInternalCommitSearchEngine.php | 75 +++++++++++++++++++ ...onInternalCommitSearchConduitAPIMethod.php | 58 ++++++++++++++ .../diffusion/data/DiffusionCommitHash.php | 17 +++++ .../diffusion/data/DiffusionCommitRef.php | 54 +++++++++---- .../PhabricatorRepositoryCommitData.php | 9 +++ 6 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 src/applications/audit/query/DiffusionInternalCommitSearchEngine.php create mode 100644 src/applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0d11820ff0..3769b19f8d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -873,6 +873,8 @@ phutil_register_library_map(array( 'DiffusionIdentityViewController' => 'applications/diffusion/controller/DiffusionIdentityViewController.php', 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php', + 'DiffusionInternalCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php', + 'DiffusionInternalCommitSearchEngine' => 'applications/audit/query/DiffusionInternalCommitSearchEngine.php', 'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php', 'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php', 'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php', @@ -6976,6 +6978,8 @@ phutil_register_library_map(array( 'DiffusionIdentityViewController' => 'DiffusionController', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionInternalCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'DiffusionInternalCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', diff --git a/src/applications/audit/query/DiffusionInternalCommitSearchEngine.php b/src/applications/audit/query/DiffusionInternalCommitSearchEngine.php new file mode 100644 index 0000000000..45780140fb --- /dev/null +++ b/src/applications/audit/query/DiffusionInternalCommitSearchEngine.php @@ -0,0 +1,75 @@ +newQuery(); + + if ($map['repositoryPHIDs']) { + $query->withRepositoryPHIDs($map['repositoryPHIDs']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Repositories')) + ->setKey('repositoryPHIDs') + ->setDatasource(new DiffusionRepositoryFunctionDatasource()) + ->setDescription(pht('Find commits in particular repositories.')), + ); + } + + protected function getURI($path) { + return null; + } + + protected function renderResultList( + array $commits, + PhabricatorSavedQuery $query, + array $handles) { + return null; + } + + protected function getObjectWireFieldsForConduit( + $object, + array $field_extensions, + array $extension_data) { + + $commit = $object; + $viewer = $this->requireViewer(); + + $repository = $commit->getRepository(); + $identifier = $commit->getCommitIdentifier(); + + id(new DiffusionRepositoryClusterEngine()) + ->setViewer($viewer) + ->setRepository($repository) + ->synchronizeWorkingCopyBeforeRead(); + + $ref = id(new DiffusionLowLevelCommitQuery()) + ->setRepository($repository) + ->withIdentifier($identifier) + ->execute(); + + return array( + 'ref' => $ref->newDictionary(), + ); + } + +} diff --git a/src/applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php new file mode 100644 index 0000000000..646b413cdb --- /dev/null +++ b/src/applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php @@ -0,0 +1,58 @@ +getViewer(); + + $constraints = $request->getValue('constraints'); + if (is_array($constraints)) { + $repository_phids = idx($constraints, 'repositoryPHIDs'); + } else { + $repository_phids = array(); + } + + $repository_phid = null; + if (is_array($repository_phids)) { + if (phutil_is_natural_list($repository_phids)) { + if (count($repository_phids) === 1) { + $value = head($repository_phids); + if (is_string($value)) { + $repository_phid = $value; + } + } + } + } + + if ($repository_phid === null) { + throw new Exception( + pht( + 'This internal method must be invoked with a "repositoryPHIDs" '. + 'constraint with exactly one value.')); + } + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withPHIDs(array($repository_phid)) + ->executeOne(); + if (!$repository) { + return array(); + } + + return $repository->newConduitClientForRequest($request); + } + +} diff --git a/src/applications/diffusion/data/DiffusionCommitHash.php b/src/applications/diffusion/data/DiffusionCommitHash.php index 7fbf23a2bf..b6eb4c75f4 100644 --- a/src/applications/diffusion/data/DiffusionCommitHash.php +++ b/src/applications/diffusion/data/DiffusionCommitHash.php @@ -34,4 +34,21 @@ final class DiffusionCommitHash extends Phobject { } return $hash_objects; } + + public static function newFromDictionary(array $map) { + $hash_type = idx($map, 'type'); + $hash_value = idx($map, 'value'); + + return id(new self()) + ->setHashType($hash_type) + ->setHashValue($hash_value); + } + + public function newDictionary() { + return array( + 'type' => $this->hashType, + 'value' => $this->hashValue, + ); + } + } diff --git a/src/applications/diffusion/data/DiffusionCommitRef.php b/src/applications/diffusion/data/DiffusionCommitRef.php index 373fdfaf0d..5224fc872f 100644 --- a/src/applications/diffusion/data/DiffusionCommitRef.php +++ b/src/applications/diffusion/data/DiffusionCommitRef.php @@ -10,28 +10,52 @@ final class DiffusionCommitRef extends Phobject { private $committerEmail; private $hashes = array(); - public static function newFromConduitResult(array $result) { - $ref = id(new DiffusionCommitRef()) - ->setAuthorEpoch(idx($result, 'authorEpoch')) - ->setCommitterEmail(idx($result, 'committerEmail')) - ->setCommitterName(idx($result, 'committerName')) - ->setAuthorEmail(idx($result, 'authorEmail')) - ->setAuthorName(idx($result, 'authorName')) - ->setMessage(idx($result, 'message')); + public function newDictionary() { + $hashes = $this->getHashes(); + $hashes = mpull($hashes, 'newDictionary'); + $hashes = array_values($hashes); - $hashes = array(); - foreach (idx($result, 'hashes', array()) as $hash_result) { - $hashes[] = id(new DiffusionCommitHash()) - ->setHashType(idx($hash_result, 'type')) - ->setHashValue(idx($hash_result, 'value')); + return array( + 'authorEpoch' => $this->authorEpoch, + 'authorName' => $this->authorName, + 'authorEmail' => $this->authorEmail, + 'committerName' => $this->committerName, + 'committerEmail' => $this->committerEmail, + 'message' => $this->message, + 'hashes' => $hashes, + ); + } + + public static function newFromDictionary(array $map) { + $hashes = idx($map, 'hashes', array()); + foreach ($hashes as $key => $hash_map) { + $hashes[$key] = DiffusionCommitHash::newFromDictionary($hash_map); } + $hashes = array_values($hashes); - $ref->setHashes($hashes); + $author_epoch = idx($map, 'authorEpoch'); + $author_name = idx($map, 'authorName'); + $author_email = idx($map, 'authorEmail'); + $committer_name = idx($map, 'committerName'); + $committer_email = idx($map, 'committerEmail'); + $message = idx($map, 'message'); - return $ref; + return id(new self()) + ->setAuthorEpoch($author_epoch) + ->setAuthorName($author_name) + ->setAuthorEmail($author_email) + ->setCommitterName($committer_name) + ->setCommitterEmail($committer_email) + ->setMessage($message) + ->setHashes($hashes); + } + + public static function newFromConduitResult(array $result) { + return self::newFromDictionary($result); } public function setHashes(array $hashes) { + assert_instances_of($hashes, 'DiffusionCommitHash'); $this->hashes = $hashes; return $this; } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php index 2f2fc3986f..f60784fc01 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php @@ -92,4 +92,13 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { return array_values($holds); } + public function setCommitRef(DiffusionCommitRef $ref) { + $this->setCommitDetail('commitRef', $ref->newDictionary()); + } + + public function newCommitRef() { + $map = $this->getCommitDetail('commitRef', array()); + return DiffusionCommitRef::neWFromDictionary($map); + } + } From f6238f9d9bd38f3f7ade4a33e496b85352499df9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 12:00:45 -0700 Subject: [PATCH 54/80] Remove "bin/repository lookup-users" workflow Summary: Ref T13552. This is one of two callsites to "diffusion.querycommits". It's an old debugging workflow which I haven't used in years and which is likely obsoleted by identities and other changes. I believe the root problem here was also ultimately user error (a user has misconfigured their local Git author email as another user). Test Plan: Grepped for "lookup-users", got no hits. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21444 --- src/__phutil_library_map__.php | 2 - ...epositoryManagementLookupUsersWorkflow.php | 115 ------------------ 2 files changed, 117 deletions(-) delete mode 100644 src/applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3769b19f8d..c4f688ac23 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4588,7 +4588,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php', 'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php', - 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php', 'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php', 'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php', @@ -11326,7 +11325,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', - 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow', diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php deleted file mode 100644 index ec65a8bcfa..0000000000 --- a/src/applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php +++ /dev/null @@ -1,115 +0,0 @@ -setName('lookup-users') - ->setExamples('**lookup-users** __commit__ ...') - ->setSynopsis( - pht('Resolve user accounts for users attached to __commit__.')) - ->setArguments( - array( - array( - 'name' => 'commits', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $commits = $this->loadCommits($args, 'commits'); - if (!$commits) { - throw new PhutilArgumentUsageException( - pht('Specify one or more commits to resolve users for.')); - } - - $console = PhutilConsole::getConsole(); - foreach ($commits as $commit) { - $repo = $commit->getRepository(); - $name = $repo->formatCommitName($commit->getCommitIdentifier()); - - $console->writeOut( - "%s\n", - pht('Examining commit %s...', $name)); - - $refs_raw = DiffusionQuery::callConduitWithDiffusionRequest( - $this->getViewer(), - DiffusionRequest::newFromDictionary( - array( - 'repository' => $repo, - 'user' => $this->getViewer(), - )), - 'diffusion.querycommits', - array( - 'repositoryPHID' => $repo->getPHID(), - 'phids' => array($commit->getPHID()), - 'bypassCache' => true, - )); - - if (empty($refs_raw['data'])) { - throw new Exception( - pht( - 'Unable to retrieve details for commit "%s"!', - $commit->getPHID())); - } - - $ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data'])); - - $author = $ref->getAuthor(); - $console->writeOut( - "%s\n", - pht('Raw author string: %s', coalesce($author, 'null'))); - - if ($author !== null) { - $handle = $this->resolveUser($commit, $author); - if ($handle) { - $console->writeOut( - "%s\n", - pht('Phabricator user: %s', $handle->getFullName())); - } else { - $console->writeOut( - "%s\n", - pht('Unable to resolve a corresponding Phabricator user.')); - } - } - - $committer = $ref->getCommitter(); - $console->writeOut( - "%s\n", - pht('Raw committer string: %s', coalesce($committer, 'null'))); - - if ($committer !== null) { - $handle = $this->resolveUser($commit, $committer); - if ($handle) { - $console->writeOut( - "%s\n", - pht('Phabricator user: %s', $handle->getFullName())); - } else { - $console->writeOut( - "%s\n", - pht('Unable to resolve a corresponding Phabricator user.')); - } - } - } - - return 0; - } - - private function resolveUser(PhabricatorRepositoryCommit $commit, $name) { - $phid = id(new DiffusionResolveUserQuery()) - ->withName($name) - ->execute(); - - if (!$phid) { - return null; - } - - return id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs(array($phid)) - ->executeOne(); - } - -} From 3a80efa440f042af09e571bf4e372f1e381fdae8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 12:00:13 -0700 Subject: [PATCH 55/80] Build "DiffusionCommitRef" objects from "internal.commit.search", not "diffusion.querycommits", in the message parser worker Summary: Ref T13552. Swap the call we're using to build "CommitRef" objects here to the recently-introduced "internal.commit.search" method. Test Plan: Used "bin/repository reparse --message ..." to reparse commits, added "var_dump()" to inspect results. Saw sensible CommitRef and CommitData objects get built. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21446 --- .../diffusion/query/DiffusionQuery.php | 12 ++--- .../storage/PhabricatorRepository.php | 22 ++++++++++ .../storage/PhabricatorRepositoryCommit.php | 44 +++++++++++++++++++ ...habricatorRepositoryCommitParserWorker.php | 5 +++ ...torRepositoryCommitMessageParserWorker.php | 25 +---------- 5 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php index 6675197932..4b2258da06 100644 --- a/src/applications/diffusion/query/DiffusionQuery.php +++ b/src/applications/diffusion/query/DiffusionQuery.php @@ -73,17 +73,11 @@ abstract class DiffusionQuery extends PhabricatorQuery { $params = $params + $core_params; - $client = $repository->newConduitClient( + $future = $repository->newConduitFuture( $user, + $method, + $params, $drequest->getIsClusterRequest()); - if (!$client) { - $result = id(new ConduitCall($method, $params)) - ->setUser($user) - ->execute(); - $future = new ImmediateFuture($result); - } else { - $future = $client->callMethod($method, $params); - } if (!$return_future) { return $future->resolve(); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 71afc56281..ca6fd7c484 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2258,6 +2258,28 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $client; } + public function newConduitFuture( + PhabricatorUser $viewer, + $method, + array $params, + $never_proxy = false) { + + $client = $this->newConduitClient( + $viewer, + $never_proxy); + + if (!$client) { + $result = id(new ConduitCall($method, $params)) + ->setUser($viewer) + ->execute(); + $future = new ImmediateFuture($result); + } else { + $future = $client->callMethod($method, $params); + } + + return $future; + } + public function getPassthroughEnvironmentalVariables() { $env = $_ENV; diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index bfc01ae97c..7093b75370 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -523,6 +523,50 @@ final class PhabricatorRepositoryCommit return $data->getCommitDetail('committer'); } + public function newCommitRef(PhabricatorUser $viewer) { + $repository = $this->getRepository(); + + $future = $repository->newConduitFuture( + $viewer, + 'internal.commit.search', + array( + 'constraints' => array( + 'repositoryPHIDs' => array($repository->getPHID()), + 'phids' => array($this->getPHID()), + ), + )); + $result = $future->resolve(); + + $commit_display = $this->getMonogram(); + + if (empty($result['data'])) { + throw new Exception( + pht( + 'Unable to retrieve details for commit "%s"!', + $commit_display)); + } + + if (count($result['data']) !== 1) { + throw new Exception( + pht( + 'Got too many results (%s) for commit "%s", expected %s.', + phutil_count($result['data']), + $commit_display, + 1)); + } + + $record = head($result['data']); + $ref_record = idxv($record, array('fields', 'ref')); + + if (!$ref_record) { + throw new Exception( + pht( + 'Unable to retrieve CommitRef record for commit "%s".', + $commit_display)); + } + + return DiffusionCommitRef::newFromDictionary($ref_record); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index 9d3b2793f8..656b258b40 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -124,4 +124,9 @@ abstract class PhabricatorRepositoryCommitParserWorker return array($link, $suffix); } + + final public function getViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index ab4719aee5..3eead0a26d 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -14,31 +14,10 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker PhabricatorRepositoryCommit $commit) { if (!$this->shouldSkipImportStep()) { - $viewer = PhabricatorUser::getOmnipotentUser(); + $viewer = $this->getViewer(); - $refs_raw = DiffusionQuery::callConduitWithDiffusionRequest( - $viewer, - DiffusionRequest::newFromDictionary( - array( - 'repository' => $repository, - 'user' => $viewer, - )), - 'diffusion.querycommits', - array( - 'repositoryPHID' => $repository->getPHID(), - 'phids' => array($commit->getPHID()), - 'bypassCache' => true, - 'needMessages' => true, - )); + $ref = $commit->newCommitRef($viewer); - if (empty($refs_raw['data'])) { - throw new Exception( - pht( - 'Unable to retrieve details for commit "%s"!', - $commit->getPHID())); - } - - $ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data'])); $this->updateCommitData($ref); } From 7d6874d9f099420f8c3e1300f9dd537cfb6ded56 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 12:09:32 -0700 Subject: [PATCH 56/80] Turn "bypassCache" into a no-op in "diffusion.querycommits" Summary: Ref T13552. The internal caller for this now uses "internal.commit.search", which is always authority-reading. No legitimate external caller should rely on the behavior of "bypassCache"; no-op it to simplify behavior. Test Plan: Called "diffusion.querycommits", saw the same data as before. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21447 --- .../DiffusionQueryCommitsConduitAPIMethod.php | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php index c070d358df..652f93fa63 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php @@ -38,7 +38,6 @@ final class DiffusionQueryCommitsConduitAPIMethod protected function execute(ConduitAPIRequest $request) { $need_messages = $request->getValue('needMessages'); - $bypass_cache = $request->getValue('bypassCache'); $viewer = $request->getUser(); $query = id(new DiffusionCommitQuery()) @@ -53,12 +52,6 @@ final class DiffusionQueryCommitsConduitAPIMethod ->executeOne(); if ($repository) { $query->withRepository($repository); - if ($bypass_cache) { - id(new DiffusionRepositoryClusterEngine()) - ->setViewer($viewer) - ->setRepository($repository) - ->synchronizeWorkingCopyBeforeRead(); - } } } @@ -111,33 +104,7 @@ final class DiffusionQueryCommitsConduitAPIMethod 'hashes' => array(), ); - if ($bypass_cache) { - $lowlevel_commitref = id(new DiffusionLowLevelCommitQuery()) - ->setRepository($commit->getRepository()) - ->withIdentifier($commit->getCommitIdentifier()) - ->execute(); - - $dict['authorEpoch'] = $lowlevel_commitref->getAuthorEpoch(); - $dict['author'] = $lowlevel_commitref->getAuthor(); - $dict['authorName'] = $lowlevel_commitref->getAuthorName(); - $dict['authorEmail'] = $lowlevel_commitref->getAuthorEmail(); - $dict['committer'] = $lowlevel_commitref->getCommitter(); - $dict['committerName'] = $lowlevel_commitref->getCommitterName(); - $dict['committerEmail'] = $lowlevel_commitref->getCommitterEmail(); - - if ($need_messages) { - $dict['message'] = $lowlevel_commitref->getMessage(); - } - - foreach ($lowlevel_commitref->getHashes() as $hash) { - $dict['hashes'][] = array( - 'type' => $hash->getHashType(), - 'value' => $hash->getHashValue(), - ); - } - } - - if ($need_messages && !$bypass_cache) { + if ($need_messages) { $dict['message'] = $commit_data->getCommitMessage(); } From e454c3dafe998d497c8c8c133d48e3bafacd611a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 12:34:03 -0700 Subject: [PATCH 57/80] Wrap all direct access to author/committer properties on "CommitData" Summary: Ref T13552. Currently, various callers read raw properties off "CommitData" directly. Wrap these in accessors to support storage changes which persist "CommitRef" information instead. Test Plan: - Ran "diffusion.querycommits", saw the same data before and after. - Looked at a commit, saw authorship information and date. - Viewed tags in a repository, saw author information. - Ran "rebuild-identities", saw no net effect. - Grepped for callers to "getCommitDetail(...)". Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21448 --- .../DiffusionQueryCommitsConduitAPIMethod.php | 14 ++--- .../controller/DiffusionCommitController.php | 2 +- .../diffusion/data/DiffusionPathChange.php | 2 +- .../diffusion/view/DiffusionTagListView.php | 2 +- ...oryManagementRebuildIdentitiesWorkflow.php | 6 +-- .../storage/PhabricatorRepositoryCommit.php | 11 ++-- .../PhabricatorRepositoryCommitData.php | 52 +++++++++++++++++-- ...abricatorRepositoryCommitPublishWorker.php | 4 +- 8 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php index 652f93fa63..4a1c83b400 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php @@ -89,18 +89,18 @@ final class DiffusionQueryCommitsConduitAPIMethod 'repositoryPHID' => $commit->getRepository()->getPHID(), 'identifier' => $commit->getCommitIdentifier(), 'epoch' => $commit->getEpoch(), - 'authorEpoch' => $commit_data->getCommitDetail('authorEpoch'), + 'authorEpoch' => $commit_data->getAuthorEpoch(), 'uri' => $uri, 'isImporting' => !$commit->isImported(), 'summary' => $commit->getSummary(), 'authorPHID' => $commit->getAuthorPHID(), 'committerPHID' => $commit_data->getCommitDetail('committerPHID'), - 'author' => $commit_data->getAuthorName(), - 'authorName' => $commit_data->getCommitDetail('authorName'), - 'authorEmail' => $commit_data->getCommitDetail('authorEmail'), - 'committer' => $commit_data->getCommitDetail('committer'), - 'committerName' => $commit_data->getCommitDetail('committerName'), - 'committerEmail' => $commit_data->getCommitDetail('committerEmail'), + 'author' => $commit_data->getAuthorString(), + 'authorName' => $commit_data->getAuthorDisplayName(), + 'authorEmail' => $commit_data->getAuthorEmail(), + 'committer' => $commit_data->getCommitterString(), + 'committerName' => $commit_data->getCommitterDisplayName(), + 'committerEmail' => $commit_data->getCommitterEmail(), 'hashes' => array(), ); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 8d52b7facf..473e8911ac 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -626,7 +626,7 @@ final class DiffusionCommitController extends DiffusionController { $author_view = $commit->newCommitAuthorView($viewer); if ($author_view) { - $author_date = $data->getCommitDetail('authorEpoch'); + $author_date = $data->getAuthorEpoch(); $author_date = phabricator_datetime($author_date, $viewer); $provenance_list->addItem( diff --git a/src/applications/diffusion/data/DiffusionPathChange.php b/src/applications/diffusion/data/DiffusionPathChange.php index 62dfdd6ace..02db490d4d 100644 --- a/src/applications/diffusion/data/DiffusionPathChange.php +++ b/src/applications/diffusion/data/DiffusionPathChange.php @@ -104,7 +104,7 @@ final class DiffusionPathChange extends Phobject { public function getAuthorName() { if ($this->getCommitData()) { - return $this->getCommitData()->getAuthorName(); + return $this->getCommitData()->getAuthorString(); } return null; } diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index 0f8cbeae71..0fa7db4c0f 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -150,7 +150,7 @@ final class DiffusionTagListView extends DiffusionView { if ($commit->getAuthorPHID()) { $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); } else if ($commit->getCommitData()) { - $author = self::renderName($commit->getCommitData()->getAuthorName()); + $author = self::renderName($commit->getCommitData()->getAuthorString()); } else { $author = self::renderName($tag->getAuthor()); } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementRebuildIdentitiesWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementRebuildIdentitiesWorkflow.php index bd906aa5da..9a0f68713e 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementRebuildIdentitiesWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementRebuildIdentitiesWorkflow.php @@ -202,11 +202,11 @@ final class PhabricatorRepositoryManagementRebuildIdentitiesWorkflow $needs_update = false; $data = $commit->getCommitData(); - $author_name = $data->getAuthorName(); + $author = $data->getAuthorString(); $author_identity = $this->getIdentityForCommit( $commit, - $author_name); + $author); $author_phid = $commit->getAuthorIdentityPHID(); $identity_phid = $author_identity->getPHID(); @@ -218,7 +218,7 @@ final class PhabricatorRepositoryManagementRebuildIdentitiesWorkflow $needs_update = true; } - $committer_name = $data->getCommitDetail('committer', null); + $committer_name = $data->getCommitterString(); $committer_phid = $commit->getCommitterIdentityPHID(); if (strlen($committer_name)) { $committer_identity = $this->getIdentityForCommit( diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 7093b75370..ef65219b4b 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -515,12 +515,12 @@ final class PhabricatorRepositoryCommit private function getRawAuthorStringForDisplay() { $data = $this->getCommitData(); - return $data->getAuthorName(); + return $data->getAuthorString(); } private function getRawCommitterStringForDisplay() { $data = $this->getCommitData(); - return $data->getCommitDetail('committer'); + return $data->getCommitterString(); } public function newCommitRef(PhabricatorUser $viewer) { @@ -898,12 +898,7 @@ final class PhabricatorRepositoryCommit $committer_user_phid = null; } - $author_epoch = $data->getCommitDetail('authorEpoch'); - if ($author_epoch) { - $author_epoch = (int)$author_epoch; - } else { - $author_epoch = null; - } + $author_epoch = $data->getAuthorEpoch(); $audit_status = $this->getAuditStatusObject(); diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php index f60784fc01..84f9522785 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php @@ -92,13 +92,55 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { return array_values($holds); } - public function setCommitRef(DiffusionCommitRef $ref) { - $this->setCommitDetail('commitRef', $ref->newDictionary()); + public function getAuthorString() { + $author = phutil_string_cast($this->authorName); + + if (strlen($author)) { + return $author; + } + + return null; } - public function newCommitRef() { - $map = $this->getCommitDetail('commitRef', array()); - return DiffusionCommitRef::neWFromDictionary($map); + public function getAuthorDisplayName() { + return $this->getCommitDetailString('authorName'); + } + + public function getAuthorEmail() { + return $this->getCommitDetailString('authorEmail'); + } + + public function getAuthorEpoch() { + $epoch = $this->getCommitDetail('authorEpoch'); + + if ($epoch) { + return (int)$epoch; + } + + return null; + } + + public function getCommitterString() { + return $this->getCommitDetailString('committer'); + } + + public function getCommitterDisplayName() { + return $this->getCommitDetailString('committerName'); + } + + public function getCommitterEmail() { + return $this->getCommitDetailString('committerEmail'); + } + + private function getCommitDetailString($key) { + $string = $this->getCommitDetail($key); + $string = phutil_string_cast($string); + + if (strlen($string)) { + return $string; + } + + return null; } } diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php index 0b0a194806..3a555e7075 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php @@ -116,9 +116,9 @@ final class PhabricatorRepositoryCommitPublishWorker array( 'description' => $data->getCommitMessage(), 'summary' => $data->getSummary(), - 'authorName' => $data->getAuthorName(), + 'authorName' => $data->getAuthorString(), 'authorPHID' => $commit->getAuthorPHID(), - 'committerName' => $data->getCommitDetail('committer'), + 'committerName' => $data->getCommitterString(), 'committerPHID' => $data->getCommitDetail('committerPHID'), )); From cebde34425c2b8df6ef88621a866fa5315247a21 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 12:44:39 -0700 Subject: [PATCH 58/80] Make "CommitData" wrap and persist a "CommitRef" record Summary: Ref T13552. Turn "CommitData" into an application-level layer on top of the repository-level "CommitRef" object. For older commits which will not have a "CommitRef" record on disk, build a synthetic one at runtime. This could eventually be migrated. Test Plan: Ran "bin/repository reparse --message", browsed Diffusion. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21449 --- .../diffusion/data/DiffusionCommitRef.php | 4 -- .../storage/PhabricatorRepositoryCommit.php | 5 ++ .../PhabricatorRepositoryCommitData.php | 57 +++++++++++++++++-- ...habricatorRepositoryCommitParserWorker.php | 20 +++++++ ...torRepositoryCommitMessageParserWorker.php | 48 ++++++++-------- 5 files changed, 100 insertions(+), 34 deletions(-) diff --git a/src/applications/diffusion/data/DiffusionCommitRef.php b/src/applications/diffusion/data/DiffusionCommitRef.php index 5224fc872f..06dd4d5ffb 100644 --- a/src/applications/diffusion/data/DiffusionCommitRef.php +++ b/src/applications/diffusion/data/DiffusionCommitRef.php @@ -50,10 +50,6 @@ final class DiffusionCommitRef extends Phobject { ->setHashes($hashes); } - public static function newFromConduitResult(array $result) { - return self::newFromDictionary($result); - } - public function setHashes(array $hashes) { assert_instances_of($hashes, 'DiffusionCommitHash'); $this->hashes = $hashes; diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index ef65219b4b..586f1e2d0a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -174,6 +174,11 @@ final class PhabricatorRepositoryCommit return $this; } + public function hasCommitData() { + return ($this->commitData !== self::ATTACHABLE) && + ($this->commitData !== null); + } + public function getCommitData() { return $this->assertAttached($this->commitData); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php index 84f9522785..c77da64ec2 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php @@ -6,6 +6,7 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { protected $authorName = ''; protected $commitMessage = ''; protected $commitDetails = array(); + private $commitRef; protected function getConfiguration() { return array( @@ -93,8 +94,14 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { } public function getAuthorString() { - $author = phutil_string_cast($this->authorName); + $ref = $this->getCommitRef(); + $author = $ref->getAuthor(); + if (strlen($author)) { + return $author; + } + + $author = phutil_string_cast($this->authorName); if (strlen($author)) { return $author; } @@ -103,15 +110,15 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { } public function getAuthorDisplayName() { - return $this->getCommitDetailString('authorName'); + return $this->getCommitRef()->getAuthorName(); } public function getAuthorEmail() { - return $this->getCommitDetailString('authorEmail'); + return $this->getCommitRef()->getAuthorEmail(); } public function getAuthorEpoch() { - $epoch = $this->getCommitDetail('authorEpoch'); + $epoch = $this->getCommitRef()->getAuthorEpoch(); if ($epoch) { return (int)$epoch; @@ -121,15 +128,22 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { } public function getCommitterString() { + $ref = $this->getCommitRef(); + + $committer = $ref->getCommitter(); + if (strlen($committer)) { + return $committer; + } + return $this->getCommitDetailString('committer'); } public function getCommitterDisplayName() { - return $this->getCommitDetailString('committerName'); + return $this->getCommitRef()->getCommitterName(); } public function getCommitterEmail() { - return $this->getCommitDetailString('committerEmail'); + return $this->getCommitRef()->getCommitterEmail(); } private function getCommitDetailString($key) { @@ -143,4 +157,35 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { return null; } + public function setCommitRef(DiffusionCommitRef $ref) { + $this->setCommitDetail('ref', $ref->newDictionary()); + $this->commitRef = null; + + return $this; + } + + public function getCommitRef() { + if ($this->commitRef === null) { + $map = $this->getCommitDetail('ref', array()); + + if (!is_array($map)) { + $map = array(); + } + + $map = $map + array( + 'authorName' => $this->getCommitDetailString('authorName'), + 'authorEmail' => $this->getCommitDetailString('authorEmail'), + 'authorEpoch' => $this->getCommitDetailString('authorEpoch'), + 'committerName' => $this->getCommitDetailString('committerName'), + 'committerEmail' => $this->getCommitDetailString('committerEmail'), + 'message' => $this->getCommitMessage(), + ); + + $ref = DiffusionCommitRef::newFromDictionary($map); + $this->commitRef = $ref; + } + + return $this->commitRef; + } + } diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index 656b258b40..e1990eaec3 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -125,6 +125,26 @@ abstract class PhabricatorRepositoryCommitParserWorker return array($link, $suffix); } + final protected function loadCommitData(PhabricatorRepositoryCommit $commit) { + if ($commit->hasCommitData()) { + return $commit->getCommitData(); + } + + $commit_id = $commit->getID(); + + $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( + 'commitID = %d', + $commit_id); + if (!$data) { + $data = id(new PhabricatorRepositoryCommitData()) + ->setCommitID($commit_id); + } + + $commit->attachCommitData($data); + + return $data; + } + final public function getViewer() { return PhabricatorUser::getOmnipotentUser(); } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 3eead0a26d..1ae1084743 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -18,7 +18,10 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $ref = $commit->newCommitRef($viewer); - $this->updateCommitData($ref); + $data = $this->loadCommitData($commit); + $data->setCommitRef($ref); + + $this->updateCommitData($commit, $data); } if ($this->shouldQueueFollowupTasks()) { @@ -38,14 +41,17 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker } } - final protected function updateCommitData(DiffusionCommitRef $ref) { - $commit = $this->commit; + final protected function updateCommitData( + PhabricatorRepositoryCommit $commit, + PhabricatorRepositoryCommitData $data) { + + $ref = $data->getCommitRef(); + $viewer = $this->getViewer(); + $author = $ref->getAuthor(); $committer = $ref->getCommitter(); $has_committer = (bool)strlen($committer); - $viewer = PhabricatorUser::getOmnipotentUser(); - $identity_engine = id(new DiffusionRepositoryIdentityEngine()) ->setViewer($viewer) ->setSourcePHID($commit->getPHID()); @@ -66,13 +72,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $committer_identity = null; } - $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( - 'commitID = %d', - $commit->getID()); - if (!$data) { - $data = new PhabricatorRepositoryCommitData(); - } - $data->setCommitID($commit->getID()); $data->setAuthorName(id(new PhutilUTF8StringTruncator()) ->setMaximumBytes(255) ->truncateString((string)$author)); @@ -122,7 +121,9 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit->setAuthorIdentityPHID($author_identity->getPHID()); $commit->setSummary($data->getSummary()); + $commit->save(); + $data->save(); // If we're publishing this commit, we're going to queue tasks to update // referenced objects (like tasks and revisions). Otherwise, record some @@ -130,15 +131,14 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $publisher = $repository->newPublisher(); if ($publisher->shouldPublishCommit($commit)) { - $actor = PhabricatorUser::getOmnipotentUser(); - $this->closeRevisions($actor, $ref, $commit, $data); - $this->closeTasks($actor, $ref, $commit, $data); + $this->closeRevisions($viewer, $commit); + $this->closeTasks($viewer, $commit); } else { $hold_reasons = $publisher->getCommitHoldReasons($commit); - $data->setCommitDetail('holdReasons', $hold_reasons); - } - $data->save(); + $data->setCommitDetail('holdReasons', $hold_reasons); + $data->save(); + } $commit->writeImportStatusFlag( PhabricatorRepositoryCommit::IMPORTED_MESSAGE); @@ -146,9 +146,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker private function closeRevisions( PhabricatorUser $actor, - DiffusionCommitRef $ref, - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { + PhabricatorRepositoryCommit $commit) { $differential = 'PhabricatorDifferentialApplication'; if (!PhabricatorApplication::isClassInstalled($differential)) { @@ -156,6 +154,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker } $repository = $commit->getRepository(); + $data = $commit->getCommitData(); + $ref = $data->getCommitRef(); $field_query = id(new DiffusionLowLevelCommitFieldsQuery()) ->setRepository($repository) @@ -198,15 +198,15 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker private function closeTasks( PhabricatorUser $actor, - DiffusionCommitRef $ref, - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { + PhabricatorRepositoryCommit $commit) { $maniphest = 'PhabricatorManiphestApplication'; if (!PhabricatorApplication::isClassInstalled($maniphest)) { return; } + $data = $commit->getCommitData(); + $prefixes = ManiphestTaskStatus::getStatusPrefixMap(); $suffixes = ManiphestTaskStatus::getStatusSuffixMap(); $message = $data->getCommitMessage(); From a39c5904423548f22e166bd37532c66ee2829d4d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Aug 2020 13:07:44 -0700 Subject: [PATCH 59/80] Move task and revision closure to the "publishing" step of the commit import pipeline Summary: Ref T13552. Now that these steps can build their own "CommitRef" object from storage on the "CommitData" object, move them from the "Message" step to the "Publishing" step. This should resolve the root issue in T13552, where a commit moved from a non-permanent branch to a permanent branch does not publish closures properly. Test Plan: Used "bin/repository reparse --publish ..." to republish changes. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21450 --- ...abricatorRepositoryCommitPublishWorker.php | 164 +++++++++++++++++- ...torRepositoryCommitMessageParserWorker.php | 137 --------------- 2 files changed, 157 insertions(+), 144 deletions(-) diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php index 3a555e7075..846eb7987a 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php @@ -30,11 +30,6 @@ final class PhabricatorRepositoryCommitPublishWorker PhabricatorRepositoryCommit $commit) { $viewer = PhabricatorUser::getOmnipotentUser(); - $publisher = $repository->newPublisher(); - if (!$publisher->shouldPublishCommit($commit)) { - return; - } - $commit_phid = $commit->getPHID(); // Reload the commit to get the commit data, identities, and any @@ -53,6 +48,36 @@ final class PhabricatorRepositoryCommitPublishWorker $commit_phid)); } + $publisher = $repository->newPublisher(); + $should_publish = $publisher->shouldPublishCommit($commit); + + if (!$should_publish) { + $hold_reasons = $publisher->getCommitHoldReasons($commit); + } else { + $hold_reasons = array(); + } + + $data = $commit->getCommitData(); + if ($data->getCommitDetail('holdReasons') !== $hold_reasons) { + $data->setCommitDetail('holdReasons', $hold_reasons); + $data->save(); + } + + if (!$should_publish) { + return; + } + + $this->applyTransactions($viewer, $repository, $commit); + + $this->closeRevisions($viewer, $commit); + $this->closeTasks($viewer, $commit); + } + + private function applyTransactions( + PhabricatorUser $actor, + PhabricatorRepository $repository, + PhabricatorRepositoryCommit $commit) { + $xactions = array( $this->newAuditTransactions($commit), $this->newPublishTransactions($commit), @@ -63,7 +88,7 @@ final class PhabricatorRepositoryCommitPublishWorker $content_source = $this->newContentSource(); $revision = DiffusionCommitRevisionQuery::loadRevisionForCommit( - $viewer, + $actor, $commit); // Prevent the commit from generating a mention of the associated @@ -75,7 +100,7 @@ final class PhabricatorRepositoryCommitPublishWorker } $editor = $commit->getApplicationTransactionEditor() - ->setActor($viewer) + ->setActor($actor) ->setActingAsPHID($acting_phid) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) @@ -391,4 +416,129 @@ final class PhabricatorRepositoryCommitPublishWorker return $file->loadFileData(); } + + private function closeRevisions( + PhabricatorUser $actor, + PhabricatorRepositoryCommit $commit) { + + $differential = 'PhabricatorDifferentialApplication'; + if (!PhabricatorApplication::isClassInstalled($differential)) { + return; + } + + $repository = $commit->getRepository(); + $data = $commit->getCommitData(); + $ref = $data->getCommitRef(); + + $field_query = id(new DiffusionLowLevelCommitFieldsQuery()) + ->setRepository($repository) + ->withCommitRef($ref); + + $field_values = $field_query->execute(); + + $revision_id = idx($field_values, 'revisionID'); + if (!$revision_id) { + return; + } + + $revision = id(new DifferentialRevisionQuery()) + ->setViewer($actor) + ->withIDs(array($revision_id)) + ->executeOne(); + if (!$revision) { + return; + } + + // NOTE: This is very old code from when revisions had a single reviewer. + // It still powers the "Reviewer (Deprecated)" field in Herald, but should + // be removed. + if (!empty($field_values['reviewedByPHIDs'])) { + $data->setCommitDetail( + 'reviewerPHID', + head($field_values['reviewedByPHIDs'])); + } + + $match_data = $field_query->getRevisionMatchData(); + + $data->setCommitDetail('differential.revisionID', $revision_id); + $data->setCommitDetail('revisionMatchData', $match_data); + + $data->save(); + + $properties = array( + 'revisionMatchData' => $match_data, + ); + $this->queueObjectUpdate($commit, $revision, $properties); + } + + private function closeTasks( + PhabricatorUser $actor, + PhabricatorRepositoryCommit $commit) { + + $maniphest = 'PhabricatorManiphestApplication'; + if (!PhabricatorApplication::isClassInstalled($maniphest)) { + return; + } + + $data = $commit->getCommitData(); + + $prefixes = ManiphestTaskStatus::getStatusPrefixMap(); + $suffixes = ManiphestTaskStatus::getStatusSuffixMap(); + $message = $data->getCommitMessage(); + + $matches = id(new ManiphestCustomFieldStatusParser()) + ->parseCorpus($message); + + $task_map = array(); + foreach ($matches as $match) { + $prefix = phutil_utf8_strtolower($match['prefix']); + $suffix = phutil_utf8_strtolower($match['suffix']); + + $status = idx($suffixes, $suffix); + if (!$status) { + $status = idx($prefixes, $prefix); + } + + foreach ($match['monograms'] as $task_monogram) { + $task_id = (int)trim($task_monogram, 'tT'); + $task_map[$task_id] = $status; + } + } + + if (!$task_map) { + return; + } + + $tasks = id(new ManiphestTaskQuery()) + ->setViewer($actor) + ->withIDs(array_keys($task_map)) + ->execute(); + foreach ($tasks as $task_id => $task) { + $status = $task_map[$task_id]; + + $properties = array( + 'status' => $status, + ); + + $this->queueObjectUpdate($commit, $task, $properties); + } + } + + private function queueObjectUpdate( + PhabricatorRepositoryCommit $commit, + $object, + array $properties) { + + $this->queueTask( + 'DiffusionUpdateObjectAfterCommitWorker', + array( + 'commitPHID' => $commit->getPHID(), + 'objectPHID' => $object->getPHID(), + 'properties' => $properties, + ), + array( + 'priority' => PhabricatorWorker::PRIORITY_DEFAULT, + )); + } + } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 1ae1084743..ea277a84c9 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -125,145 +125,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit->save(); $data->save(); - // If we're publishing this commit, we're going to queue tasks to update - // referenced objects (like tasks and revisions). Otherwise, record some - // details about why we are not publishing it yet. - - $publisher = $repository->newPublisher(); - if ($publisher->shouldPublishCommit($commit)) { - $this->closeRevisions($viewer, $commit); - $this->closeTasks($viewer, $commit); - } else { - $hold_reasons = $publisher->getCommitHoldReasons($commit); - - $data->setCommitDetail('holdReasons', $hold_reasons); - $data->save(); - } - $commit->writeImportStatusFlag( PhabricatorRepositoryCommit::IMPORTED_MESSAGE); } - private function closeRevisions( - PhabricatorUser $actor, - PhabricatorRepositoryCommit $commit) { - - $differential = 'PhabricatorDifferentialApplication'; - if (!PhabricatorApplication::isClassInstalled($differential)) { - return; - } - - $repository = $commit->getRepository(); - $data = $commit->getCommitData(); - $ref = $data->getCommitRef(); - - $field_query = id(new DiffusionLowLevelCommitFieldsQuery()) - ->setRepository($repository) - ->withCommitRef($ref); - - $field_values = $field_query->execute(); - - $revision_id = idx($field_values, 'revisionID'); - if (!$revision_id) { - return; - } - - $revision = id(new DifferentialRevisionQuery()) - ->setViewer($actor) - ->withIDs(array($revision_id)) - ->executeOne(); - if (!$revision) { - return; - } - - // NOTE: This is very old code from when revisions had a single reviewer. - // It still powers the "Reviewer (Deprecated)" field in Herald, but should - // be removed. - if (!empty($field_values['reviewedByPHIDs'])) { - $data->setCommitDetail( - 'reviewerPHID', - head($field_values['reviewedByPHIDs'])); - } - - $match_data = $field_query->getRevisionMatchData(); - - $data->setCommitDetail('differential.revisionID', $revision_id); - $data->setCommitDetail('revisionMatchData', $match_data); - - $properties = array( - 'revisionMatchData' => $match_data, - ); - $this->queueObjectUpdate($commit, $revision, $properties); - } - - private function closeTasks( - PhabricatorUser $actor, - PhabricatorRepositoryCommit $commit) { - - $maniphest = 'PhabricatorManiphestApplication'; - if (!PhabricatorApplication::isClassInstalled($maniphest)) { - return; - } - - $data = $commit->getCommitData(); - - $prefixes = ManiphestTaskStatus::getStatusPrefixMap(); - $suffixes = ManiphestTaskStatus::getStatusSuffixMap(); - $message = $data->getCommitMessage(); - - $matches = id(new ManiphestCustomFieldStatusParser()) - ->parseCorpus($message); - - $task_map = array(); - foreach ($matches as $match) { - $prefix = phutil_utf8_strtolower($match['prefix']); - $suffix = phutil_utf8_strtolower($match['suffix']); - - $status = idx($suffixes, $suffix); - if (!$status) { - $status = idx($prefixes, $prefix); - } - - foreach ($match['monograms'] as $task_monogram) { - $task_id = (int)trim($task_monogram, 'tT'); - $task_map[$task_id] = $status; - } - } - - if (!$task_map) { - return; - } - - $tasks = id(new ManiphestTaskQuery()) - ->setViewer($actor) - ->withIDs(array_keys($task_map)) - ->execute(); - foreach ($tasks as $task_id => $task) { - $status = $task_map[$task_id]; - - $properties = array( - 'status' => $status, - ); - - $this->queueObjectUpdate($commit, $task, $properties); - } - } - - private function queueObjectUpdate( - PhabricatorRepositoryCommit $commit, - $object, - array $properties) { - - $this->queueTask( - 'DiffusionUpdateObjectAfterCommitWorker', - array( - 'commitPHID' => $commit->getPHID(), - 'objectPHID' => $object->getPHID(), - 'properties' => $properties, - ), - array( - 'priority' => PhabricatorWorker::PRIORITY_DEFAULT, - )); - } - } From 93ef902ffa3c47de45dc00b722c7e7849d3449e8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Sep 2020 12:00:05 -0700 Subject: [PATCH 60/80] Fix a view fatal in CommitGraphView when commits are undiscovered Summary: Ref T13552. See . This condition is flipped and can fatal by passing a `NULL` value for `$commit` to a typehinted method. Test Plan: Viewed history page with undiscovered commits. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21458 --- src/applications/diffusion/view/DiffusionCommitGraphView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index a43f44f033..2009328a9d 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -485,7 +485,7 @@ final class DiffusionCommitGraphView $buildable = null; $commit = $this->getCommit($hash); - if (!$commit) { + if ($commit) { $buildable = $this->getBuildable($commit); } From 737e7c85412ccf75ea1461398a91fea490cd026c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Sep 2020 12:15:34 -0700 Subject: [PATCH 61/80] When an in-process worker subtask fails permanently, don't fatal the whole process Summary: Ref T13552. Fixes T13569. Currently, if a process uses in-process tasks (usually, a debugging/diagnostic workflow) and those tasks (or tasks those tasks queue) fail permanently, the exception escapes to top level and the process exits. This isn't desirable; catch the exception and fail them locally instead. Test Plan: With a failing Asana integration and misconfigured Webhook, ran `bin/repository reparse --publish ...`. - Before: fatals on each substep. - After: warnings emitted for failed substep, but process completes. Maniphest Tasks: T13569, T13552 Differential Revision: https://secure.phabricator.com/D21459 --- .../daemon/workers/PhabricatorWorker.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index f055544b7b..95e91cf7d5 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -162,6 +162,19 @@ abstract class PhabricatorWorker extends Phobject { try { $worker->executeTask(); $worker->flushTaskQueue(); + + $task_result = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; + break; + } catch (PhabricatorWorkerPermanentFailureException $ex) { + $proxy = new PhutilProxyException( + pht( + 'In-process task ("%s") failed permanently.', + $task_class), + $ex); + + phlog($proxy); + + $task_result = PhabricatorWorkerArchiveTask::RESULT_FAILURE; break; } catch (PhabricatorWorkerYieldException $ex) { phlog( @@ -177,9 +190,7 @@ abstract class PhabricatorWorker extends Phobject { // object with a valid ID. $task->openTransaction(); $task->save(); - $archived = $task->archiveTask( - PhabricatorWorkerArchiveTask::RESULT_SUCCESS, - 0); + $archived = $task->archiveTask($task_result, 0); $task->saveTransaction(); return $archived; From 6f78e2a91c88657dbc6956a86a8a1db1b126e0de Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Sep 2020 12:51:09 -0700 Subject: [PATCH 62/80] When a commit is marked "closeable", clear the "published" flag Summary: Ref T13552. When a previously discovered commit becomes reachable from a permanent ref, we re-queue workers to update it. However, the commit may already be marked as "published", so the publish worker may do nothing. It would perhaps be simpler to not mark the commit as published when it isn't reachable from a permanent ref, but this is tricky because the flag is also part of the "imported / all steps" state (see T13580). Until that can be cleaned up, just clear the flag. Test Plan: - Pushed a commit with "fixes X" to a non-permanent branch. - Pushed it to a permanent branch. - Before change: task failed to close. - After change: task closes properly. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21460 --- .../engine/PhabricatorRepositoryRefEngine.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php index 68e2c344fc..a1b7ae5f55 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php @@ -553,6 +553,7 @@ final class PhabricatorRepositoryRefEngine } $closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE; + $published_flag = PhabricatorRepositoryCommit::IMPORTED_PUBLISH; $all_commits = ipull($all_commits, null, 'commitIdentifier'); foreach ($identifiers as $identifier) { @@ -566,12 +567,21 @@ final class PhabricatorRepositoryRefEngine $identifier)); } - if (!($row['importStatus'] & $closeable_flag)) { + $import_status = $row['importStatus']; + if (!($import_status & $closeable_flag)) { + // Set the "closeable" flag. + $import_status = ($import_status | $closeable_flag); + + // See T13580. Clear the "published" flag, so publishing executes + // again. We may have previously performed a no-op "publish" on the + // commit to make sure it has all bits in the "IMPORTED_ALL" bitmask. + $import_status = ($import_status & ~$published_flag); + queryfx( $conn, - 'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d', + 'UPDATE %T SET importStatus = %d WHERE id = %d', $commit_table->getTableName(), - $closeable_flag, + $import_status, $row['id']); $data = array( From 2a83df578604cf5229ff95e8108985c2e0b0e97c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 15 Sep 2020 17:13:21 -0700 Subject: [PATCH 63/80] Fix an issue where a GROUP BY was missing when a query matched a revision using multiple hashes Summary: Ref T13581. If you query for revisions by hash and provide multiple hashes (A, B) which match a single revision (e.g., older and newer diffs for that revision), the query omits a GROUP BY clause but should contain one. Add a GROUP BY clause in this case. Test Plan: With a working copy that has multiple hashes corresponding to a single revision, ran `arc branches` before and after the change. Before, got this error: ``` [2020-09-15 17:02:07] EXCEPTION: (ConduitClientException) ERR-CONDUIT-CORE: Rows passed to "loadAllFromArray(...)" include two or more rows with the same ID ("130"). Rows must have unique IDs. An underlying query may be missing a GROUP BY. at [/src/conduit/ConduitFuture.php:65] ``` After, clean execution. Maniphest Tasks: T13581 Differential Revision: https://secure.phabricator.com/D21462 --- .../query/DifferentialRevisionQuery.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 84f9f07f61..0f6d5d944b 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -778,12 +778,19 @@ final class DifferentialRevisionQuery */ protected function shouldGroupQueryResultRows() { - $join_triggers = array_merge( - $this->pathIDs, - $this->ccs, - $this->reviewers); + if (count($this->pathIDs) > 1) { + return true; + } - if (count($join_triggers) > 1) { + if (count($this->ccs) > 1) { + return true; + } + + if (count($this->reviewers) > 1) { + return true; + } + + if (count($this->commitHashes) > 1) { return true; } From 969587f7b0c38218a774d30371a542b3bee6d6a4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 15 Sep 2020 17:22:21 -0700 Subject: [PATCH 64/80] Log unexpected exceptions raised by Conduit calls Summary: Ref T13581. Currently, unexpected exceptions inside Conduit calls are passed to the client, but not logged on the server. These exceptions should generally be unexpected, and producing a server-side trace is potentially useful. Test Plan: Simulated a during-execution exception, saw it get logged on the server. Maniphest Tasks: T13581 Differential Revision: https://secure.phabricator.com/D21464 --- .../controller/PhabricatorConduitAPIController.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index 3b3e6ee423..9ca84fff3d 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -100,9 +100,17 @@ final class PhabricatorConduitAPIController } } catch (Exception $ex) { $result = null; - $error_code = ($ex instanceof ConduitException - ? 'ERR-CONDUIT-CALL' - : 'ERR-CONDUIT-CORE'); + + if ($ex instanceof ConduitException) { + $error_code = 'ERR-CONDUIT-CALL'; + } else { + $error_code = 'ERR-CONDUIT-CORE'; + + // See T13581. When a Conduit method raises an uncaught exception + // other than a "ConduitException", log it. + phlog($ex); + } + $error_info = $ex->getMessage(); } From a754c694deab984d4de6b1ef1b53d5982ffbb565 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Sep 2020 12:06:52 -0700 Subject: [PATCH 65/80] Add missing indexes to DrydockRepositoryOperation Summary: See PHI1885. Repository operations are queryable by state and author, but neither column has a usable key. Add usable keys. Test Plan: Ran EXPLAIN on a state query. Ran `bin/storage upgrade`. Ran EXPLAIN again, saw query go from a table scan to a `const` key lookup. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D21465 --- .../drydock/storage/DrydockRepositoryOperation.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php index 195c5de8ec..1447aaa456 100644 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -55,6 +55,12 @@ final class DrydockRepositoryOperation extends DrydockDAO 'key_repository' => array( 'columns' => array('repositoryPHID', 'operationState'), ), + 'key_state' => array( + 'columns' => array('operationState'), + ), + 'key_author' => array( + 'columns' => array('authorPHID', 'operationState'), + ), ), ) + parent::getConfiguration(); } From f21a00a3159e6e0154de8e27f7787864e6bbb4ea Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Sep 2020 13:34:50 -0700 Subject: [PATCH 66/80] Fix an out-of-order issue in the new update-during-publish behavior Summary: Ref T13552. The Herald field "Accepted Differential revision" (and similar fields) depend on the task/revision update steps running before Herald executes. Herald currently executes first, so it never sees associated revisions. Swap this order. Test Plan: Published a commit, got a clean parse/import. Will test with production rules ("Cowboy Commits"). Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21468 --- .../worker/PhabricatorRepositoryCommitPublishWorker.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php index 846eb7987a..9d70e3db1c 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php @@ -67,10 +67,17 @@ final class PhabricatorRepositoryCommitPublishWorker return; } - $this->applyTransactions($viewer, $repository, $commit); + // NOTE: Close revisions and tasks before applying transactions, because + // we want a side effect of closure (the commit being associated with + // a revision) to occur before a side effect of transactions (Herald + // executing). The close methods queue tasks for the actual updates to + // commits/revisions, so those won't occur until after the commit gets + // transactions. $this->closeRevisions($viewer, $commit); $this->closeTasks($viewer, $commit); + + $this->applyTransactions($viewer, $repository, $commit); } private function applyTransactions( From 58d3f6145a853fe6439014e8b24383ca749da966 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Sep 2020 13:50:24 -0700 Subject: [PATCH 67/80] Fix an issue where known Subversion commits are incorrectly shown as "Discovering..." Summary: Ref T13552. The behavior of "RepositoryQuery" with ambiguous identifiers under "withRepositoryPHIDs()" is tricky. This leads to failure to load commits in Subversion in some cases. Use "withRepository()", which gives us the correct identifier resolution behavior. Test Plan: Viewed a subversion repository history in Diffusion, saw commit details after change. Maniphest Tasks: T13552 Differential Revision: https://secure.phabricator.com/D21469 --- src/applications/diffusion/view/DiffusionCommitGraphView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php index 2009328a9d..30c252712c 100644 --- a/src/applications/diffusion/view/DiffusionCommitGraphView.php +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -614,7 +614,7 @@ final class DiffusionCommitGraphView $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) - ->withRepositoryPHIDs(array($repository->getPHID())) + ->withRepository($repository) ->withIdentifiers($identifiers) ->needCommitData(true) ->needIdentities(true) From a5f20f7106697a405f26b558098426f8d4452f4a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2020 09:17:52 -0700 Subject: [PATCH 68/80] When printing, wrap all content in Remarkup tables more aggressively Summary: Ref T13564. See PHI1798. Earlier efforts here (see D21439) still leave us with: - Incorrect behavior for long URIs, like `http://www.example.com/MMMMM...`. - Incorrect beahvior for long text blocks, like `MMMMMM...`. - Undesirable behavior for monospaced text in non-printing contexts (it wraps when we'd prefer it not wrap). Apply the wrapping rules to all "" content to resolve these three prongs. Test Plan: - Viewed long URIs, text blocks, and monospaced text in and out of tables, while printed and not printed, in Safari, Firefox, and Chrome. - All browser behavior now appears to be correct ("all content is preserved in printed document"). - Some browser behavior when making wrapping choices is questionable, but I can't find an automatic solution for that. Maniphest Tasks: T13564 Differential Revision: https://secure.phabricator.com/D21472 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index de3a9627ff..ffa1e95405 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'bd937962', + 'core.pkg.css' => '937616c0', 'core.pkg.js' => 'adc34883', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', @@ -114,7 +114,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd', 'rsrc/css/application/uiexample/example.css' => 'b4795059', 'rsrc/css/core/core.css' => 'b3ebd90d', - 'rsrc/css/core/remarkup.css' => '94c3d777', + 'rsrc/css/core/remarkup.css' => '24d48a73', 'rsrc/css/core/syntax.css' => '548567f6', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', 'rsrc/css/diviner/diviner-shared.css' => '4bd263b0', @@ -806,7 +806,7 @@ return array( 'phabricator-object-selector-css' => 'ee77366f', 'phabricator-phtize' => '2f1db1ed', 'phabricator-prefab' => '5793d835', - 'phabricator-remarkup-css' => '94c3d777', + 'phabricator-remarkup-css' => '24d48a73', 'phabricator-search-results-css' => '9ea70ace', 'phabricator-shaped-request' => '995f5102', 'phabricator-slowvote-css' => '1694baed', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 5bee3e373d..bb698f3ac1 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -86,11 +86,6 @@ padding: 1px 4px; border-radius: 3px; white-space: pre-wrap; - - /* See T13564. This is a narrow control for PDF printing behavior in - Chrome. */ - line-break: anywhere; - overflow-wrap: anywhere; } /* NOTE: You can currently produce this with [[link | `name`]]. Restore the @@ -499,6 +494,10 @@ video.phabricator-media { overflow-x: auto; } +!print .phabricator-remarkup .remarkup-table-wrap { + overflow-x: hidden; +} + .phabricator-remarkup table.remarkup-table { border-collapse: separate; border-spacing: 1px; @@ -518,6 +517,14 @@ video.phabricator-media { padding: 3px 6px; } +!print .phabricator-remarkup table.remarkup-table td { + /* See T13564. This is a narrow control for PDF printing behavior in + Chrome. */ + + line-break: anywhere; + overflow-wrap: anywhere; +} + body div.phabricator-remarkup.remarkup-has-toc .phabricator-remarkup-toc + .remarkup-header { margin-top: 0; From 0f0e94ca71fe6c3320e6f13e1f856fe95e15ecac Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2020 09:09:10 -0700 Subject: [PATCH 69/80] Use "getInlines()", not "_inlines", to access inlines on client Changeset objects Summary: See PHI1898. An install is reporting an execution/initialization order issue where this code is reachable before `_inlines` is initialized. I can't immediately reproduce it, but using "getInlines()" is preferable anyway and seems likely to fix the problem. Test Plan: Viewed revisions with inlines, added/removed/edited/replied to inlines, didn't find anything broken. Differential Revision: https://secure.phabricator.com/D21475 --- resources/celerity/map.php | 22 +++++++++---------- .../rsrc/js/application/diff/DiffChangeset.js | 19 ++++++++-------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ffa1e95405..4b33b32da5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => 'adc34883', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', - 'differential.pkg.js' => '218fda21', + 'differential.pkg.js' => '5080baf4', 'diffusion.pkg.css' => '42c75c37', 'diffusion.pkg.js' => '8ee48a4b', 'maniphest.pkg.css' => '35995d6d', @@ -383,7 +383,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', - 'rsrc/js/application/diff/DiffChangeset.js' => '39dcf2c3', + 'rsrc/js/application/diff/DiffChangeset.js' => '3b6e1fde', 'rsrc/js/application/diff/DiffChangesetList.js' => 'cc2c5de5', 'rsrc/js/application/diff/DiffInline.js' => '511a1315', 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9', @@ -784,7 +784,7 @@ return array( 'phabricator-darklog' => '3b869402', 'phabricator-darkmessage' => '26cd4b73', 'phabricator-dashboard-css' => '5a205b9d', - 'phabricator-diff-changeset' => '39dcf2c3', + 'phabricator-diff-changeset' => '3b6e1fde', 'phabricator-diff-changeset-list' => 'cc2c5de5', 'phabricator-diff-inline' => '511a1315', 'phabricator-diff-path-view' => '8207abf9', @@ -1229,7 +1229,14 @@ return array( 'trigger-rule', 'trigger-rule-type', ), - '39dcf2c3' => array( + '3ae89b20' => array( + 'phui-workcard-view-css', + ), + '3b4899b0' => array( + 'javelin-behavior', + 'phabricator-prefab', + ), + '3b6e1fde' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', @@ -1243,13 +1250,6 @@ return array( 'phuix-button-view', 'javelin-external-editor-link-engine', ), - '3ae89b20' => array( - 'phui-workcard-view-css', - ), - '3b4899b0' => array( - 'javelin-behavior', - 'phabricator-prefab', - ), '3be6ef4f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 212d19f5c6..668634cb30 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -723,16 +723,19 @@ JX.install('DiffChangeset', { var data = JX.Stratcom.getData(node); if (!data.inline) { - var inline = new JX.DiffInline() - .setChangeset(this) - .bindToRow(node); - - this._inlines.push(inline); + var inline = this._newInlineForRow(node); + this.getInlines().push(inline); } return data.inline; }, + _newInlineForRow: function(node) { + return new JX.DiffInline() + .setChangeset(this) + .bindToRow(node); + }, + newInlineForRange: function(origin, target, options) { var list = this.getChangesetList(); @@ -770,7 +773,7 @@ JX.install('DiffChangeset', { .setChangeset(this) .bindToRange(data); - this._inlines.push(inline); + this.getInlines().push(inline); inline.create(); @@ -855,9 +858,7 @@ JX.install('DiffChangeset', { continue; } - // As a side effect, this builds any missing inline objects and adds - // them to this Changeset's list of inlines. - this.getInlineForRow(row); + this._inlines.push(this._newInlineForRow(row)); } }, From 0f27cd46ccadb2522babca5c5ab18ab26a3b5dbc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2020 09:41:29 -0700 Subject: [PATCH 70/80] Never render "Show More Context" inside an inline comment suggestion diff Summary: See PHI1896. If you do this: - Create an inline comment over a wide range of lines. - Suggest an edit. - Make a change near the beginning of the block. - Make a change near the end of the block. - Save the inline. ...you get a rendering which includes a "Show More Context" fold in the middle. Currently, this element renders in a visually broken way and consumes too many columns. However, this element isn't ever desirable inside inline comment suggestions. Stop it from rendering entirely. Test Plan: - Made an inline comment suggestion across lines 1-50 with edits at the beginning and end, saw a contiguous diff. - Made smaller inline comment suggestions (one line, a few lines). Differential Revision: https://secure.phabricator.com/D21476 --- .../view/PHUIDiffInlineCommentDetailView.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index fdaa99ac7e..3ff87c11ab 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -568,7 +568,24 @@ final class PHUIDiffInlineCommentDetailView $parser->setRenderer($renderer); - $diff_view = $parser->render(0, 0xFFFF, array()); + // See PHI1896. If a user leaves an inline on a very long range with + // suggestions at the beginning and end, we'll hide context in the middle + // by default. We don't want to do this in the context of an inline + // suggestion, so build a mask to force display of all lines. + + // (We don't know exactly how many lines the diff has, we just know that + // it can't have more lines than the old file plus the new file, so we're + // using that as an upper bound.) + + $min = 0; + + $old_len = count(phutil_split_lines($old_lines)); + $new_len = count(phutil_split_lines($new_lines)); + $max = ($old_len + $new_len); + + $mask = array_fill($min, ($max - $min), true); + + $diff_view = $parser->render($min, ($max - $min), $mask); $view = phutil_tag( 'div', From 1f7c736f9ab6a155632a2a8ad014ce4b994ff8bd Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Oct 2020 13:19:38 -0700 Subject: [PATCH 71/80] Add a "Comment content" field to Herald Summary: Ref T13583. To improve support for making it harder to improperly mix data retention policies, allow Herald to act on comment content. Test Plan: - Wrote comment content Herald rules in Maniphest and Differential. - Submitted non-matching comments (no action) and matching comments (Herald action). - In Differential, triggered rules by submitting non-matching main content and a matching inline comment. Maniphest Tasks: T13583 Differential Revision: https://secure.phabricator.com/D21479 --- src/__phutil_library_map__.php | 2 + .../field/HeraldCommentContentField.php | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/applications/herald/field/HeraldCommentContentField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c4f688ac23..4f6ae109bb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1557,6 +1557,7 @@ phutil_register_library_map(array( 'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php', 'HeraldCallWebhookAction' => 'applications/herald/action/HeraldCallWebhookAction.php', 'HeraldCommentAction' => 'applications/herald/action/HeraldCommentAction.php', + 'HeraldCommentContentField' => 'applications/herald/field/HeraldCommentContentField.php', 'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php', 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', @@ -7770,6 +7771,7 @@ phutil_register_library_map(array( 'HeraldBuildableState' => 'HeraldState', 'HeraldCallWebhookAction' => 'HeraldAction', 'HeraldCommentAction' => 'HeraldAction', + 'HeraldCommentContentField' => 'HeraldField', 'HeraldCommitAdapter' => array( 'HeraldAdapter', 'HarbormasterBuildableAdapterInterface', diff --git a/src/applications/herald/field/HeraldCommentContentField.php b/src/applications/herald/field/HeraldCommentContentField.php new file mode 100644 index 0000000000..d74a78fe45 --- /dev/null +++ b/src/applications/herald/field/HeraldCommentContentField.php @@ -0,0 +1,43 @@ +getAdapter(); + + $xactions = $adapter->getAppliedTransactions(); + + $result = array(); + foreach ($xactions as $xaction) { + if (!$xaction->hasComment()) { + continue; + } + + $comment = $xaction->getComment(); + $content = $comment->getContent(); + + $result[] = $content; + } + + return $result; + } + + public function supportsObject($object) { + return true; + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_TEXT_LIST; + } + +} From 058d2489e7a3c1f2f4badea5e095dad5ad714f8b Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Oct 2020 13:38:17 -0700 Subject: [PATCH 72/80] Expose the "file attached to object" and "object attached to file" edges via "edge.search" Summary: See PHI1901. An install would like improved support for identifying files related to an object (like a task or revision) for retention/archival/backup/migration/snapshotting purposes. The "attachment" edge is not really user-level: it just means "if you can see the object, that allows you to see the file". This set includes files that users may not think of as "attached", like thumbnails and internal objects which are attached for technical reasons. However, this is generally an appropriate relationship to expose for retention purposes. Test Plan: Used "edge.search" to find files attached to a revision and objects attached to a file. Differential Revision: https://secure.phabricator.com/D21480 --- .../files/edge/PhabricatorFileHasObjectEdgeType.php | 12 ++++++++++++ .../edges/PhabricatorObjectHasFileEdgeType.php | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php b/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php index 50ff9ceafa..c4a3c2af87 100644 --- a/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php +++ b/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php @@ -12,4 +12,16 @@ final class PhabricatorFileHasObjectEdgeType extends PhabricatorEdgeType { return true; } + public function getConduitKey() { + return 'file.attached-objects'; + } + + public function getConduitName() { + return pht('File Has Object'); + } + + public function getConduitDescription() { + return pht('The source file is attached to the destination object.'); + } + } diff --git a/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php b/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php index 864665e45b..6ed89ff91b 100644 --- a/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php +++ b/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php @@ -12,6 +12,18 @@ final class PhabricatorObjectHasFileEdgeType extends PhabricatorEdgeType { return true; } + public function getConduitKey() { + return 'object.attached-files'; + } + + public function getConduitName() { + return pht('Object Has Files'); + } + + public function getConduitDescription() { + return pht('The source object is associated with the destination file.'); + } + public function getTransactionAddString( $actor, $add_count, From 2b8bbae5fb276b8ec7c26f6a82335eba680e8357 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Oct 2020 14:01:55 -0700 Subject: [PATCH 73/80] Set an explicit height when drawing the dependent revision graph Summary: See PHI1900. Recent changes to how commit graphs are drawn made the height automatic in most cases, but it fails in Differential because the element isn't initially visible so the computed height is 0. Just give them an explicit height so they show up again. Test Plan: Viewed graphs in Maniphest, Differential, and Diffusion; saw them all render properly. Differential Revision: https://secure.phabricator.com/D21481 --- resources/celerity/map.php | 16 +++++++-------- .../DifferentialRevisionViewController.php | 5 +++++ .../diff/view/PHUIDiffGraphView.php | 12 ++++++++++- .../graph/PhabricatorObjectGraph.php | 20 +++++++++++++++++-- .../diffusion/behavior-commit-graph.js | 6 +++--- 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4b33b32da5..0ae7849bfb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -15,7 +15,7 @@ return array( 'differential.pkg.css' => '5c459f92', 'differential.pkg.js' => '5080baf4', 'diffusion.pkg.css' => '42c75c37', - 'diffusion.pkg.js' => '8ee48a4b', + 'diffusion.pkg.js' => '78c9885d', 'maniphest.pkg.css' => '35995d6d', 'maniphest.pkg.js' => 'c9308721', 'rsrc/audio/basic/alert.mp3' => '17889334', @@ -394,7 +394,7 @@ return array( 'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '3be6ef4f', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ac10c917', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', @@ -623,7 +623,7 @@ return array( 'javelin-behavior-differential-diff-radios' => '925fe8cd', 'javelin-behavior-differential-populate' => 'b86ef6c2', 'javelin-behavior-diffusion-commit-branches' => '4b671572', - 'javelin-behavior-diffusion-commit-graph' => '3be6ef4f', + 'javelin-behavior-diffusion-commit-graph' => 'ac10c917', 'javelin-behavior-diffusion-locate-file' => '87428eb2', 'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123', 'javelin-behavior-document-engine' => '243d6c22', @@ -1250,11 +1250,6 @@ return array( 'phuix-button-view', 'javelin-external-editor-link-engine', ), - '3be6ef4f' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '3dc5ad43' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1927,6 +1922,11 @@ return array( 'javelin-dom', 'phabricator-notification', ), + 'ac10c917' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), 'ac2b1e01' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index ccb4b85867..e37fa2b529 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -510,6 +510,11 @@ final class DifferentialRevisionViewController ->setLoadEntireGraph(true) ->loadGraph(); if (!$stack_graph->isEmpty()) { + // See PHI1900. The graph UI element now tries to figure out the correct + // height automatically, but currently can't in this case because the + // element is not visible when the page loads. Set an explicit height. + $stack_graph->setHeight(34); + $stack_table = $stack_graph->newGraphTable(); $parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST; diff --git a/src/infrastructure/diff/view/PHUIDiffGraphView.php b/src/infrastructure/diff/view/PHUIDiffGraphView.php index 589526feb6..6fa1b7c239 100644 --- a/src/infrastructure/diff/view/PHUIDiffGraphView.php +++ b/src/infrastructure/diff/view/PHUIDiffGraphView.php @@ -4,6 +4,7 @@ final class PHUIDiffGraphView extends Phobject { private $isHead = true; private $isTail = true; + private $height; public function setIsHead($is_head) { $this->isHead = $is_head; @@ -23,6 +24,15 @@ final class PHUIDiffGraphView extends Phobject { return $this->isTail; } + public function setHeight($height) { + $this->height = $height; + return $this; + } + + public function getHeight() { + return $this->height; + } + public function renderRawGraph(array $parents) { // This keeps our accumulated information about each line of the // merge/branch graph. @@ -205,7 +215,7 @@ final class PHUIDiffGraphView extends Phobject { 'diffusion-commit-graph', array( 'count' => $count, - 'autoheight' => true, + 'height' => $this->getHeight(), )); return $graph; diff --git a/src/infrastructure/graph/PhabricatorObjectGraph.php b/src/infrastructure/graph/PhabricatorObjectGraph.php index 67130e8c05..93e538b3de 100644 --- a/src/infrastructure/graph/PhabricatorObjectGraph.php +++ b/src/infrastructure/graph/PhabricatorObjectGraph.php @@ -11,6 +11,7 @@ abstract class PhabricatorObjectGraph private $loadEntireGraph = false; private $limit; private $adjacent; + private $height; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -34,6 +35,15 @@ abstract class PhabricatorObjectGraph return $this->limit; } + public function setHeight($height) { + $this->height = $height; + return $this; + } + + public function getHeight() { + return $this->height; + } + final public function setRenderOnlyAdjacentNodes($adjacent) { $this->adjacent = $adjacent; return $this; @@ -193,8 +203,14 @@ abstract class PhabricatorObjectGraph $ancestry = array_select_keys($ancestry, $order); - $traces = id(new PHUIDiffGraphView()) - ->renderGraph($ancestry); + $graph_view = id(new PHUIDiffGraphView()); + + $height = $this->getHeight(); + if ($height !== null) { + $graph_view->setHeight($height); + } + + $traces = $graph_view->renderGraph($ancestry); $ii = 0; $rows = array(); diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index c0aa9bcb58..e8946ca9b4 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -62,10 +62,10 @@ JX.behavior('diffusion-commit-graph', function(config) { }; var h; - if (config.autoheight) { - h = JX.Vector.getDim(nodes[ii].parentNode).y; + if (config.height) { + h = config.height; } else { - h = 34; + h = JX.Vector.getDim(nodes[ii].parentNode).y; } var w = cell * config.count; From b2e96df3a3bed0ba0d9a6662e1c5934910fb0425 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Oct 2020 11:53:09 -0700 Subject: [PATCH 74/80] Update "arc call-conduit" instructions in Conduit API console for required "--" Summary: See PHI1912. Ref T13491. "arc" now requires "--" when stdin is not a TTY; provide this argument for users. Test Plan: Viewed example in console, saw "--". Executed example. Maniphest Tasks: T13491 Differential Revision: https://secure.phabricator.com/D21482 --- .../conduit/controller/PhabricatorConduitController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php index 97e1ad0294..0f7b2ef54b 100644 --- a/src/applications/conduit/controller/PhabricatorConduitController.php +++ b/src/applications/conduit/controller/PhabricatorConduitController.php @@ -142,6 +142,8 @@ abstract class PhabricatorConduitController extends PhabricatorController { $parts[] = '--conduit-token '; $parts[] = phutil_tag('strong', array(), ''); $parts[] = ' '; + $parts[] = '--'; + $parts[] = ' '; $parts[] = $method->getAPIMethodName(); From bc4f86d2799681b8e96702d0671245c85ee8215b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Oct 2020 12:21:06 -0700 Subject: [PATCH 75/80] When a new, deleted, draft inline is revived with "Undo", undelete it Summary: See PHI1876. Normally, deleted inlines are undeleted with an "undelete" operation, which clears the "isDeleted" flag. However, when an inline is deleted implicitly by using "Cancel" without first saving it, the flag currently isn't cleared properly. This can lead to cases where inlines seem to vanish (they are shown to the user in the UI, but treated as deleted on submission). Test Plan: There are two affected sequences here: - Create a new inline, type text, cancel, undo. - Create a new inline, type text, cancel, undo, save. The former sequence triggers an "edit" operation. The subsequent "Save" in the second sequence triggers a "save" operation. It's normally impossible in the UI to execute a "save" without executing an "edit" first, but "save" clearly should undelete the comment if you get there somehow, so this change clears the deleted flag in both cases for completeness. - Executed both sequences, saw comment persist in preview, on reload, and after submission. Differential Revision: https://secure.phabricator.com/D21483 --- .../diff/PhabricatorInlineCommentController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index e1b8dc7264..693f66066f 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -189,6 +189,8 @@ abstract class PhabricatorInlineCommentController $inline->setIsEditing(false); if (!$inline->isVoidComment($viewer)) { + $inline->setIsDeleted(0); + $this->saveComment($inline); return $this->buildRenderedCommentResponse( @@ -217,7 +219,10 @@ abstract class PhabricatorInlineCommentController $is_dirty = false; if (!$inline->getIsEditing()) { - $inline->setIsEditing(true); + $inline + ->setIsDeleted(0) + ->setIsEditing(true); + $is_dirty = true; } From 671986592bafc4b2f059319d9e320cab46445ac2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 30 Oct 2020 12:34:19 -0700 Subject: [PATCH 76/80] Add a missing "GROUP BY" to MailQuery when querying for multiple recipients Summary: See . The change in D21400 detects a missing "GROUP BY" in some variations of this query. Specifically, we may join multiple recipient rows (since mail may have multiple recipients) and then fail to group the results. Fix this by adding the "GROUP BY". Additionally, remove the special-cased behavior when no authors or recipients are specified -- it's complicated and not entirely correct (e.g., may produce a "no object" instead of a policy error when querying by ID), and likely predates overheating. Test Plan: - Disabled `metamta.one-mail-per-recipient` in Config. - Generated a message to 2+ recipients. - Viewed the message detail; queried for the message by specifying 2+ recipients. - Viewed the unfiltered list of messages, saw the query overheat. Differential Revision: https://secure.phabricator.com/D21486 --- .../query/PhabricatorMetaMTAMailQuery.php | 55 +++++++++---------- .../policy/PhabricatorPolicyAwareQuery.php | 2 +- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php index a1fad69c0b..903b385ceb 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php @@ -64,24 +64,6 @@ final class PhabricatorMetaMTAMailQuery $this->actorPHIDs); } - if ($this->recipientPHIDs !== null) { - $where[] = qsprintf( - $conn, - 'recipient.dst IN (%Ls)', - $this->recipientPHIDs); - } - - if ($this->actorPHIDs === null && $this->recipientPHIDs === null) { - $viewer = $this->getViewer(); - if (!$viewer->isOmnipotent()) { - $where[] = qsprintf( - $conn, - 'edge.dst = %s OR actorPHID = %s', - $viewer->getPHID(), - $viewer->getPHID()); - } - } - if ($this->createdMin !== null) { $where[] = qsprintf( $conn, @@ -102,26 +84,29 @@ final class PhabricatorMetaMTAMailQuery protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); - if ($this->actorPHIDs === null && $this->recipientPHIDs === null) { + if ($this->shouldJoinRecipients()) { $joins[] = qsprintf( $conn, - 'LEFT JOIN %T edge ON mail.phid = edge.src AND edge.type = %d', + 'JOIN %T recipient + ON mail.phid = recipient.src + AND recipient.type = %d + AND recipient.dst IN (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, - PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST); - } - - if ($this->recipientPHIDs !== null) { - $joins[] = qsprintf( - $conn, - 'LEFT JOIN %T recipient '. - 'ON mail.phid = recipient.src AND recipient.type = %d', - PhabricatorEdgeConfig::TABLE_NAME_EDGE, - PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST); + PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST, + $this->recipientPHIDs); } return $joins; } + private function shouldJoinRecipients() { + if ($this->recipientPHIDs === null) { + return false; + } + + return true; + } + protected function getPrimaryTableAlias() { return 'mail'; } @@ -134,4 +119,14 @@ final class PhabricatorMetaMTAMailQuery return 'PhabricatorMetaMTAApplication'; } + protected function shouldGroupQueryResultRows() { + if ($this->shouldJoinRecipients()) { + if (count($this->recipientPHIDs) > 1) { + return true; + } + } + + return parent::shouldGroupQueryResultRows(); + } + } diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index a770c326f9..c43edaefcb 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -234,7 +234,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { // T11773 for some discussion. $this->isOverheated = false; - // See T13386. If we on an old offset-based paging workflow, we need + // See T13386. If we are on an old offset-based paging workflow, we need // to base the overheating limit on both the offset and limit. $overheat_limit = $need * 10; $total_seen = 0; From c04147328fa3006c75b6a669d6c0e6ecb335be01 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 30 Oct 2020 13:03:34 -0700 Subject: [PATCH 77/80] Fix isValidGitShallowCloneResponse Summary: Changes the heuristic method by which non-zero exit statuses from git-http-backend are found to be due to packfile negotiation during shallow fetches, etc. Instead of checking git-http-backend stderr for a generic "hung up" error message, see if the pack-result response contains a terminating flush packet ("0000"). This should give a greater assurance that the request was handled correctly and the response is complete. Test Plan: Run `GIT_CURL_VERBOSE=1 git fetch --depth 1 https://host.example/source/repo.git HEAD` to ensure it completes and includes two successful POST requests during packfile negotiation (the last one actually receives the packfile). Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, dzduvall Tags: #diffusion Differential Revision: https://secure.phabricator.com/D21484 --- .../controller/DiffusionServeController.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 60d5c1578d..1a1383368f 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -922,18 +922,26 @@ final class DiffusionServeController extends DiffusionController { // This is a pretty funky fix: it would be nice to more precisely detect // that a request is a `--depth N` clone request, but we don't have any code // to decode protocol frames yet. Instead, look for reasonable evidence - // in the error and output that we're looking at a `--depth` clone. + // in the output that we're looking at a `--depth` clone. - // For evidence this isn't completely crazy, see: - // https://github.com/schacon/grack/pull/7 + // A valid x-git-upload-pack-result response during packfile negotiation + // should end with a flush packet ("0000"). As long as that packet + // terminates the response body in the response, we'll assume the response + // is correct and complete. + + // See https://git-scm.com/docs/pack-protocol#_packfile_negotiation $stdout_regexp = '(^Content-Type: application/x-git-upload-pack-result)m'; - $stderr_regexp = '(The remote end hung up unexpectedly)'; $has_pack = preg_match($stdout_regexp, $stdout); - $is_hangup = preg_match($stderr_regexp, $stderr); - return $has_pack && $is_hangup; + if (strlen($stdout) >= 4) { + $has_flush_packet = (substr($stdout, -4) === "0000"); + } else { + $has_flush_packet = false; + } + + return ($has_pack && $has_flush_packet); } private function getCommonEnvironment(PhabricatorUser $viewer) { From ae5a38f3349fdd718033619c4e908137f1634502 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 Nov 2020 10:32:26 -0800 Subject: [PATCH 78/80] Guarantee terms in PhabricatorAuthPasswordEngine are strings Summary: Ref T2312. Numeric strings are read out of arrays as integers, and modern PHP raises appropriate warnings when they're then treated as strings. For now, cast the keys to strings explicitly (we know we inserted only strings). In the future, introduction of a `StringMap` type or similar might be appropriate. Test Plan: - Added "abc.12345.xyz" to the blocklist, changed my VCS password. - Before: fatal when trying to "strpos()" an integer. - After: password change worked correctly. Maniphest Tasks: T2312 Differential Revision: https://secure.phabricator.com/D21487 --- .../auth/engine/PhabricatorAuthPasswordEngine.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php index f763b0987f..a1fec4a6d2 100644 --- a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php @@ -181,6 +181,12 @@ final class PhabricatorAuthPasswordEngine $normal_password = phutil_utf8_strtolower($raw_password); if (strlen($normal_password) >= $minimum_similarity) { foreach ($normal_map as $term => $source) { + + // See T2312. This may be required if the term list includes numeric + // strings like "12345", which will be cast to integers when used as + // array keys. + $term = phutil_string_cast($term); + if (strpos($term, $normal_password) === false && strpos($normal_password, $term) === false) { continue; From bf8707d3a9bf9670ce44fdfe8c7812033cf014a1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 Nov 2020 12:20:10 -0800 Subject: [PATCH 79/80] Add a basic "harbormaster.step.search" API method Summary: Ref T13585. This isn't particularly useful (notably, it does not include custom field values and isn't searchable by build plan PHID) but get the basics into place. Test Plan: Used the web UI to make API calls, reviewed results. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13585 Differential Revision: https://secure.phabricator.com/D21488 --- src/__phutil_library_map__.php | 5 ++ .../HarbormasterBuildStepSearchAPIMethod.php | 18 ++++++ .../HarbormasterBuildStepSearchEngine.php | 58 +++++++++++++++++++ .../configuration/HarbormasterBuildStep.php | 43 +++++++++++++- 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4f6ae109bb..0bb84f38b9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1436,6 +1436,8 @@ phutil_register_library_map(array( 'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php', 'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php', 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', + 'HarbormasterBuildStepSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php', + 'HarbormasterBuildStepSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php', 'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php', 'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php', 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', @@ -7627,6 +7629,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', + 'PhabricatorConduitResultInterface', ), 'HarbormasterBuildStepCoreCustomField' => array( 'HarbormasterBuildStepCustomField', @@ -7639,6 +7642,8 @@ phutil_register_library_map(array( 'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase', 'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildStepSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'HarbormasterBuildStepSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildTarget' => array( diff --git a/src/applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php new file mode 100644 index 0000000000..9305cf0abd --- /dev/null +++ b/src/applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php @@ -0,0 +1,18 @@ +newQuery(); + + return $query; + } + + protected function getURI($path) { + return '/harbormaster/step/'.$path; + } + + protected function getBuiltinQueryNames() { + return array( + 'all' => pht('All Steps'), + ); + } + + 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 $plans, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($plans, 'HarbormasterBuildStep'); + return null; + } + +} diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php index dd0ebdc507..71b050adff 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php @@ -4,7 +4,8 @@ final class HarbormasterBuildStep extends HarbormasterDAO implements PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, - PhabricatorCustomFieldInterface { + PhabricatorCustomFieldInterface, + PhabricatorConduitResultInterface { protected $name; protected $description; @@ -169,5 +170,45 @@ final class HarbormasterBuildStep extends HarbormasterDAO return $this; } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the build step.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('description') + ->setType('remarkup') + ->setDescription(pht('The build step description.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('buildPlanPHID') + ->setType('phid') + ->setDescription( + pht( + 'The PHID of the build plan this build step belongs to.')), + ); + } + + public function getFieldValuesForConduit() { + // T6203: This can be removed once the field becomes non-nullable. + $name = $this->getName(); + $name = phutil_string_cast($name); + + return array( + 'name' => $name, + 'description' => array( + 'raw' => $this->getDescription(), + ), + 'buildPlanPHID' => $this->getBuildPlanPHID(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From 34082efb02c7815ecce80957ecddee93aedd7571 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 Nov 2020 12:22:16 -0800 Subject: [PATCH 80/80] Add a basic "harbormaster.step.edit" API method Summary: Ref T13585. Provide a minimal but technically functional "harbormaster.step.edit" API method. Test Plan: Used the web console to modify the URI for a "Make HTTP Request" build step. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13585 Differential Revision: https://secure.phabricator.com/D21489 --- src/__phutil_library_map__.php | 4 + .../HarbormasterBuildStepEditAPIMethod.php | 20 ++++ .../HarbormasterBuildStepEditEngine.php | 107 ++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 src/applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php create mode 100644 src/applications/harbormaster/editor/HarbormasterBuildStepEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0bb84f38b9..12a5ad9333 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1430,6 +1430,8 @@ phutil_register_library_map(array( 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', 'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php', 'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php', + 'HarbormasterBuildStepEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php', + 'HarbormasterBuildStepEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildStepEditEngine.php', 'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php', 'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php', 'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php', @@ -7636,6 +7638,8 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField', + 'HarbormasterBuildStepEditAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'HarbormasterBuildStepEditEngine' => 'PhabricatorEditEngine', 'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildStepGroup' => 'Phobject', 'HarbormasterBuildStepImplementation' => 'Phobject', diff --git a/src/applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php new file mode 100644 index 0000000000..e29f27cb10 --- /dev/null +++ b/src/applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php @@ -0,0 +1,20 @@ +buildPlan = $build_plan; + return $this; + } + + public function getBuildPlan() { + if ($this->buildPlan === null) { + throw new PhutilInvalidStateException('setBuildPlan'); + } + + return $this->buildPlan; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Harbormaster Build Steps'); + } + + public function getSummaryHeader() { + return pht('Edit Harbormaster Build Step Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Harbormaster build steps.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorHarbormasterApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + + + $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer); + $this->setBuildPlan($plan); + + $plan = $this->getBuildPlan(); + + $step = HarbormasterBuildStep::initializeNewStep($viewer); + + $step->setBuildPlanPHID($plan->getPHID()); + $step->attachBuildPlan($plan); + + return $step; + } + + protected function newObjectQuery() { + return new HarbormasterBuildStepQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Build Step'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Build Step'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Build Step: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Build Step'); + } + + protected function getObjectCreateShortText() { + return pht('Create Build Step'); + } + + protected function getObjectName() { + return pht('Build Step'); + } + + protected function getEditorURI() { + return '/harbormaster/step/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/harbormaster/step/'; + } + + protected function getObjectViewURI($object) { + $id = $object->getID(); + return "/harbormaster/step/{$id}/"; + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + return $fields; + } + +}