mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-08 16:02:39 +01:00
Promote 2023.49 to Stable
This commit is contained in:
commit
ca72430916
19 changed files with 223 additions and 62 deletions
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* This class takes over the PHP error and exception handlers when you call
|
||||
* ##PhutilErrorHandler::initialize()## and forwards all debugging information
|
||||
* to a listener you install with ##PhutilErrorHandler::setErrorListener()##.
|
||||
* to a listener you install with ##PhutilErrorHandler::addErrorListener()##.
|
||||
*
|
||||
* To use PhutilErrorHandler, which will enhance the messages printed to the
|
||||
* PHP error log, just initialize it:
|
||||
|
@ -16,7 +16,7 @@
|
|||
* To additionally install a custom listener which can print error information
|
||||
* to some other file or console, register a listener:
|
||||
*
|
||||
* PhutilErrorHandler::setErrorListener($some_callback);
|
||||
* PhutilErrorHandler::addErrorListener($some_callback);
|
||||
*
|
||||
* For information on writing an error listener, see
|
||||
* @{function:phutil_error_listener_example}. Providing a listener is optional,
|
||||
|
@ -31,7 +31,7 @@
|
|||
*/
|
||||
final class PhutilErrorHandler extends Phobject {
|
||||
|
||||
private static $errorListener = null;
|
||||
private static $errorListeners = array();
|
||||
private static $initialized = false;
|
||||
private static $traps = array();
|
||||
|
||||
|
@ -68,8 +68,15 @@ final class PhutilErrorHandler extends Phobject {
|
|||
* @return void
|
||||
* @task config
|
||||
*/
|
||||
public static function addErrorListener($listener) {
|
||||
self::$errorListeners[] = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated - use `addErrorListener`.
|
||||
*/
|
||||
public static function setErrorListener($listener) {
|
||||
self::$errorListener = $listener;
|
||||
self::addErrorListener($listener);
|
||||
}
|
||||
|
||||
|
||||
|
@ -203,12 +210,17 @@ final class PhutilErrorHandler extends Phobject {
|
|||
|
||||
if (($num === E_USER_ERROR) ||
|
||||
($num === E_USER_WARNING) ||
|
||||
($num === E_USER_NOTICE)) {
|
||||
($num === E_USER_NOTICE) ||
|
||||
($num === E_DEPRECATED)) {
|
||||
|
||||
// See T15554 - we special-case E_DEPRECATED because we don't want them
|
||||
// to kill the process.
|
||||
$level = ($num === E_DEPRECATED) ? self::DEPRECATED : self::ERROR;
|
||||
|
||||
$trace = debug_backtrace();
|
||||
array_shift($trace);
|
||||
self::dispatchErrorMessage(
|
||||
self::ERROR,
|
||||
$level,
|
||||
$str,
|
||||
array(
|
||||
'file' => $file,
|
||||
|
@ -380,6 +392,7 @@ final class PhutilErrorHandler extends Phobject {
|
|||
$timestamp = date('Y-m-d H:i:s');
|
||||
|
||||
switch ($event) {
|
||||
case self::DEPRECATED:
|
||||
case self::ERROR:
|
||||
$default_message = sprintf(
|
||||
'[%s] ERROR %d: %s at [%s:%d]',
|
||||
|
@ -432,7 +445,7 @@ final class PhutilErrorHandler extends Phobject {
|
|||
break;
|
||||
}
|
||||
|
||||
if (self::$errorListener) {
|
||||
if (self::$errorListeners) {
|
||||
static $handling_error;
|
||||
if ($handling_error) {
|
||||
error_log(
|
||||
|
@ -441,7 +454,9 @@ final class PhutilErrorHandler extends Phobject {
|
|||
return;
|
||||
}
|
||||
$handling_error = true;
|
||||
call_user_func(self::$errorListener, $event, $value, $metadata);
|
||||
foreach (self::$errorListeners as $error_listener) {
|
||||
call_user_func($error_listener, $event, $value, $metadata);
|
||||
}
|
||||
$handling_error = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ function phlog($value/* , ... */) {
|
|||
|
||||
/**
|
||||
* Example @{class:PhutilErrorHandler} error listener callback. When you call
|
||||
* `PhutilErrorHandler::setErrorListener()`, you must pass a callback function
|
||||
* `PhutilErrorHandler::addErrorListener()`, you must pass a callback function
|
||||
* with the same signature as this one.
|
||||
*
|
||||
* NOTE: @{class:PhutilErrorHandler} handles writing messages to the error
|
||||
|
|
|
@ -1103,12 +1103,23 @@ final class Filesystem extends Phobject {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
$stdout = head($stdout);
|
||||
|
||||
// These are the only file extensions that can be executed directly
|
||||
// when using proc_open() with 'bypass_shell'.
|
||||
$executable_extensions = ['exe', 'bat', 'cmd', 'com'];
|
||||
|
||||
foreach ($stdout as $line) {
|
||||
$path = trim($line);
|
||||
$ext = pathinfo($path, PATHINFO_EXTENSION);
|
||||
if (in_array($ext, $executable_extensions)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
list($err, $stdout) = exec_manual('which %s', $binary);
|
||||
return $err === 0 ? trim($stdout) : null;
|
||||
}
|
||||
|
||||
return $err === 0 ? trim($stdout) : null;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ final class PhutilErrorLog
|
|||
}
|
||||
|
||||
public function onError($event, $value, array $metadata) {
|
||||
// If we've set "error_log" to a real file, so messages won't be output to
|
||||
// If we've set "error_log" to a real file, messages won't be output to
|
||||
// stderr by default. Copy them to stderr.
|
||||
|
||||
if ($this->logPath === null) {
|
||||
|
|
|
@ -229,7 +229,10 @@ final class PhutilOAuth1Future extends FutureProxy {
|
|||
$consumer_secret = $this->consumerSecret->openEnvelope();
|
||||
}
|
||||
|
||||
$key = urlencode($consumer_secret).'&'.urlencode($this->tokenSecret);
|
||||
$key = urlencode($consumer_secret).'&';
|
||||
if ($this->tokenSecret !== null) {
|
||||
$key .= urlencode($this->tokenSecret);
|
||||
}
|
||||
|
||||
switch ($this->signatureMethod) {
|
||||
case 'HMAC-SHA1':
|
||||
|
|
|
@ -37,11 +37,12 @@ final class ArcanistComposerLinter extends ArcanistLinter {
|
|||
}
|
||||
|
||||
private function lintComposerJson($path) {
|
||||
$composer_hash = md5(Filesystem::readFile(dirname($path).'/composer.json'));
|
||||
$composer_hash = self::getContentHash(
|
||||
Filesystem::readFile(dirname($path).'/composer.json'));
|
||||
$composer_lock = phutil_json_decode(
|
||||
Filesystem::readFile(dirname($path).'/composer.lock'));
|
||||
|
||||
if ($composer_hash !== $composer_lock['hash']) {
|
||||
if ($composer_hash !== $composer_lock['content-hash']) {
|
||||
$this->raiseLintAtPath(
|
||||
self::LINT_OUT_OF_DATE,
|
||||
pht(
|
||||
|
@ -52,4 +53,68 @@ final class ArcanistComposerLinter extends ArcanistLinter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the md5 hash of the sorted content of the composer.json file.
|
||||
*
|
||||
* This function copied from
|
||||
* https://github.com/
|
||||
* composer/composer/blob/1.5.2/src/Composer/Package/Locker.php
|
||||
* and has the following license:
|
||||
*
|
||||
* Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*
|
||||
* @param string $composer_file_contents The contents of the composer file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getContentHash($composer_file_contents) {
|
||||
$content = json_decode($composer_file_contents, true);
|
||||
|
||||
$relevant_keys = array(
|
||||
'name',
|
||||
'version',
|
||||
'require',
|
||||
'require-dev',
|
||||
'conflict',
|
||||
'replace',
|
||||
'provide',
|
||||
'minimum-stability',
|
||||
'prefer-stable',
|
||||
'repositories',
|
||||
'extra',
|
||||
);
|
||||
|
||||
$relevant_content = array();
|
||||
|
||||
foreach (array_intersect($relevant_keys, array_keys($content)) as $key) {
|
||||
$relevant_content[$key] = $content[$key];
|
||||
}
|
||||
if (isset($content['config']['platform'])) {
|
||||
$relevant_content['config']['platform'] = $content['config']['platform'];
|
||||
}
|
||||
|
||||
ksort($relevant_content);
|
||||
|
||||
return md5(json_encode($relevant_content));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -133,7 +133,19 @@ abstract class ArcanistExternalLinter extends ArcanistFutureLinter {
|
|||
* @task bin
|
||||
*/
|
||||
final public function getBinary() {
|
||||
return coalesce($this->bin, $this->getDefaultBinary());
|
||||
$bin = coalesce($this->bin, $this->getDefaultBinary());
|
||||
if (phutil_is_windows()) {
|
||||
// On Windows, we use proc_open with 'bypass_shell' option, which will
|
||||
// resolve %PATH%, but not %PATHEXT% (unless the extension is .exe).
|
||||
// Therefore find the right binary ourselves.
|
||||
// If we can't find it, leave it unresolved, as this string will be
|
||||
// used in some error messages elsewhere.
|
||||
$resolved = Filesystem::resolveBinary($bin);
|
||||
if ($resolved) {
|
||||
return $resolved;
|
||||
}
|
||||
}
|
||||
return $bin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* jshint maxerr: 1 */
|
||||
console.log('foobar')
|
||||
console.log(
|
||||
{
|
||||
~~~~~~~~~~
|
||||
disabled:2:22:E043
|
||||
warning:2:22:W033
|
||||
disabled:3:1:E043
|
||||
error:3:1:E019
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
</lang>
|
||||
</languages>
|
||||
~~~~~~~~~~
|
||||
error:3:16:XML76:LibXML Error
|
||||
error:3:12:XML76:LibXML Error
|
||||
error:4:1:XML5:LibXML Error
|
||||
|
|
|
@ -469,10 +469,12 @@ EOPHP;
|
|||
|
||||
$this->fileSymbolMap = $symbol_map;
|
||||
|
||||
// We're done building the cache, so write it out immediately. Note that
|
||||
// we've only retained entries for files we found, so this implicitly cleans
|
||||
// out old cache entries.
|
||||
$this->writeSymbolCache($symbol_map, $source_map);
|
||||
if ($futures) {
|
||||
// We're done building/updating the cache, so write it out immediately.
|
||||
// Note that we've only retained entries for files we found, so this
|
||||
// implicitly cleans out old cache entries.
|
||||
$this->writeSymbolCache($symbol_map, $source_map);
|
||||
}
|
||||
|
||||
// Our map is up to date, so either show it on stdout or write it to disk.
|
||||
$this->librarySymbolMap = $this->buildLibraryMap($symbol_map);
|
||||
|
|
|
@ -762,7 +762,7 @@ final class ArcanistBundle extends Phobject {
|
|||
$old_data = $this->getBlob($old_phid, $name);
|
||||
}
|
||||
|
||||
$old_length = strlen($old_data);
|
||||
$old_length = phutil_nonempty_string($old_data) ? strlen($old_data) : 0;
|
||||
|
||||
// Here, and below, the binary will be emitted with base85 encoding. This
|
||||
// encoding encodes each 4 bytes of input in 5 bytes of output, so we may
|
||||
|
@ -795,7 +795,7 @@ final class ArcanistBundle extends Phobject {
|
|||
$new_data = $this->getBlob($new_phid, $name);
|
||||
}
|
||||
|
||||
$new_length = strlen($new_data);
|
||||
$new_length = phutil_nonempty_string($new_data) ? strlen($new_data) : 0;
|
||||
$this->reserveBytes($new_length * 5 / 4);
|
||||
|
||||
if ($new_data === null) {
|
||||
|
|
|
@ -11,6 +11,14 @@ final class PlatformSymbols
|
|||
return 'Phorge';
|
||||
}
|
||||
|
||||
public static function getPlatformClientPath() {
|
||||
return 'arcanist/';
|
||||
}
|
||||
|
||||
public static function getPlatformServerPath() {
|
||||
return 'phorge/';
|
||||
}
|
||||
|
||||
public static function getProductNames() {
|
||||
return array(
|
||||
self::getPlatformClientName(),
|
||||
|
|
|
@ -41,44 +41,44 @@ final class PhutilCowsay extends Phobject {
|
|||
$template = $this->template;
|
||||
|
||||
// Real ".cow" files are Perl scripts which define a variable called
|
||||
// "$the_cow". We aren't going to interpret Perl, so strip all this stuff
|
||||
// (and any comments in the file) away.
|
||||
$template = phutil_split_lines($template, true);
|
||||
$keep = array();
|
||||
$is_perl_cowfile = false;
|
||||
foreach ($template as $key => $line) {
|
||||
if (preg_match('/^#/', $line)) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^\s*\\$the_cow/', $line)) {
|
||||
$is_perl_cowfile = true;
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^\s*EOC\s*$/', $line)) {
|
||||
continue;
|
||||
}
|
||||
$keep[] = $line;
|
||||
}
|
||||
$template = implode('', $keep);
|
||||
// "$the_cow". We aren't going to interpret Perl, so just get everything
|
||||
// between the EOC (End Of Cow) tokens. The initial EOC might be in
|
||||
// quotes, and might have a semicolon.
|
||||
// We apply regexp modifiers
|
||||
// * 's' to make . match newlines within the EOC ... EOC block
|
||||
// * 'm' so we can use ^ to match start of line within the multiline string
|
||||
$matches = null;
|
||||
if (
|
||||
preg_match('/\$the_cow/', $template) &&
|
||||
preg_match('/EOC[\'"]?;?.*?^(.*?)^EOC/sm', $template, $matches)
|
||||
) {
|
||||
$template = $matches[1];
|
||||
|
||||
// Original .cow files are perl scripts which contain escaped sequences.
|
||||
// We attempt to unescape here by replacing any character preceded by a
|
||||
// backslash/escape with just that character.
|
||||
if ($is_perl_cowfile) {
|
||||
// Original .cow files are perl scripts which contain escaped sequences.
|
||||
// We attempt to unescape here by replacing any character preceded by a
|
||||
// backslash/escape with just that character.
|
||||
$template = preg_replace(
|
||||
'/\\\\(.)/',
|
||||
'$1',
|
||||
$template);
|
||||
} else {
|
||||
// Text template. Just strip away comments.
|
||||
$template = preg_replace('/^#.*$/', '', $template);
|
||||
}
|
||||
|
||||
$template = preg_replace_callback(
|
||||
$token_patterns = array(
|
||||
'/\\$([a-z]+)/',
|
||||
array($this, 'replaceTemplateVariable'),
|
||||
$template);
|
||||
if ($template === false) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Failed to replace template variables while rendering cow!'));
|
||||
'/\\${([a-z]+)}/',
|
||||
);
|
||||
foreach ($token_patterns as $token_pattern) {
|
||||
$template = preg_replace_callback(
|
||||
$token_pattern,
|
||||
array($this, 'replaceTemplateVariable'),
|
||||
$template);
|
||||
if ($template === false) {
|
||||
throw new Exception(
|
||||
pht('Failed to replace template variables while rendering cow!'));
|
||||
}
|
||||
}
|
||||
|
||||
$lines = $this->text;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# test case for original perl-script cowfile
|
||||
$the_cow =
|
||||
$the_cow = <<EOC
|
||||
$thoughts
|
||||
$thoughts
|
||||
/---\\__/---\\\\
|
||||
|
@ -9,6 +9,7 @@ $the_cow =
|
|||
/ ..--.. \\\\
|
||||
| \\ .... / ||
|
||||
\\---/--\\---//
|
||||
EOC
|
||||
~~~~~~~~~~
|
||||
{
|
||||
"text": "I made a friend!",
|
||||
|
|
11
src/utils/__tests__/cowsay/sheep.expect
Normal file
11
src/utils/__tests__/cowsay/sheep.expect
Normal file
|
@ -0,0 +1,11 @@
|
|||
__________________
|
||||
< How are my eyes? >
|
||||
------------------
|
||||
\
|
||||
\
|
||||
__
|
||||
UooU\.'@@@@@@`.
|
||||
\__/(@@@@@@@@@@)
|
||||
(@@@@@@@@)
|
||||
`YY~~~~YY'
|
||||
|| ||
|
13
src/utils/__tests__/cowsay/sheep.test
Normal file
13
src/utils/__tests__/cowsay/sheep.test
Normal file
|
@ -0,0 +1,13 @@
|
|||
$thoughts
|
||||
$thoughts
|
||||
__
|
||||
U${eyes}U\.'@@@@@@`.
|
||||
\__/(@@@@@@@@@@)
|
||||
(@@@@@@@@)
|
||||
`YY~~~~YY'
|
||||
|| ||
|
||||
~~~~~~~~~~
|
||||
{
|
||||
"text": "How are my eyes?",
|
||||
"eyes": "oo"
|
||||
}
|
7
src/utils/__tests__/cowsay/small.expect
Normal file
7
src/utils/__tests__/cowsay/small.expect
Normal file
|
@ -0,0 +1,7 @@
|
|||
__________________
|
||||
< How are my eyes? >
|
||||
------------------
|
||||
\ ,__,
|
||||
\ (oo)____
|
||||
(__) )\
|
||||
||--|| *
|
12
src/utils/__tests__/cowsay/small.test
Normal file
12
src/utils/__tests__/cowsay/small.test
Normal file
|
@ -0,0 +1,12 @@
|
|||
$eyes = ".." unless ($eyes);
|
||||
$the_cow = <<EOC;
|
||||
$thoughts ,__,
|
||||
$thoughts ($eyes)____
|
||||
(__) )\\
|
||||
$tongue||--|| *
|
||||
EOC
|
||||
~~~~~~~~~~
|
||||
{
|
||||
"text": "How are my eyes?",
|
||||
"eyes": "oo"
|
||||
}
|
|
@ -479,8 +479,8 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
// If we have `token`, this server supports the simpler, new-style
|
||||
// token-based authentication. Use that instead of all the certificate
|
||||
// stuff.
|
||||
$token = idx($credentials, 'token', '');
|
||||
if (strlen($token)) {
|
||||
$token = idx($credentials, 'token');
|
||||
if (phutil_nonempty_string($token)) {
|
||||
$conduit = $this->getConduit();
|
||||
|
||||
$conduit->setConduitToken($token);
|
||||
|
@ -2244,8 +2244,8 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
protected function getModernUnitDictionary(array $map) {
|
||||
$map = $this->getModernCommonDictionary($map);
|
||||
|
||||
$details = idx($map, 'userData', '');
|
||||
if (strlen($details)) {
|
||||
$details = idx($map, 'userData');
|
||||
if (phutil_nonempty_string($details)) {
|
||||
$map['details'] = (string)$details;
|
||||
}
|
||||
unset($map['userData']);
|
||||
|
|
Loading…
Reference in a new issue