1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-02-18 01:38:38 +01:00

Promote 2023.49 to Stable

This commit is contained in:
Aviv Eyal 2023-12-08 18:27:22 +02:00
commit ca72430916
19 changed files with 223 additions and 62 deletions

View file

@ -6,7 +6,7 @@
* *
* This class takes over the PHP error and exception handlers when you call * This class takes over the PHP error and exception handlers when you call
* ##PhutilErrorHandler::initialize()## and forwards all debugging information * ##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 * To use PhutilErrorHandler, which will enhance the messages printed to the
* PHP error log, just initialize it: * PHP error log, just initialize it:
@ -16,7 +16,7 @@
* To additionally install a custom listener which can print error information * To additionally install a custom listener which can print error information
* to some other file or console, register a listener: * 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 * For information on writing an error listener, see
* @{function:phutil_error_listener_example}. Providing a listener is optional, * @{function:phutil_error_listener_example}. Providing a listener is optional,
@ -31,7 +31,7 @@
*/ */
final class PhutilErrorHandler extends Phobject { final class PhutilErrorHandler extends Phobject {
private static $errorListener = null; private static $errorListeners = array();
private static $initialized = false; private static $initialized = false;
private static $traps = array(); private static $traps = array();
@ -68,8 +68,15 @@ final class PhutilErrorHandler extends Phobject {
* @return void * @return void
* @task config * @task config
*/ */
public static function addErrorListener($listener) {
self::$errorListeners[] = $listener;
}
/**
* Deprecated - use `addErrorListener`.
*/
public static function setErrorListener($listener) { 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) || if (($num === E_USER_ERROR) ||
($num === E_USER_WARNING) || ($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(); $trace = debug_backtrace();
array_shift($trace); array_shift($trace);
self::dispatchErrorMessage( self::dispatchErrorMessage(
self::ERROR, $level,
$str, $str,
array( array(
'file' => $file, 'file' => $file,
@ -380,6 +392,7 @@ final class PhutilErrorHandler extends Phobject {
$timestamp = date('Y-m-d H:i:s'); $timestamp = date('Y-m-d H:i:s');
switch ($event) { switch ($event) {
case self::DEPRECATED:
case self::ERROR: case self::ERROR:
$default_message = sprintf( $default_message = sprintf(
'[%s] ERROR %d: %s at [%s:%d]', '[%s] ERROR %d: %s at [%s:%d]',
@ -432,7 +445,7 @@ final class PhutilErrorHandler extends Phobject {
break; break;
} }
if (self::$errorListener) { if (self::$errorListeners) {
static $handling_error; static $handling_error;
if ($handling_error) { if ($handling_error) {
error_log( error_log(
@ -441,7 +454,9 @@ final class PhutilErrorHandler extends Phobject {
return; return;
} }
$handling_error = true; $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; $handling_error = false;
} }
} }

View file

@ -41,7 +41,7 @@ function phlog($value/* , ... */) {
/** /**
* Example @{class:PhutilErrorHandler} error listener callback. When you call * 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. * with the same signature as this one.
* *
* NOTE: @{class:PhutilErrorHandler} handles writing messages to the error * NOTE: @{class:PhutilErrorHandler} handles writing messages to the error

View file

@ -1103,12 +1103,23 @@ final class Filesystem extends Phobject {
} }
return null; 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 { } else {
list($err, $stdout) = exec_manual('which %s', $binary); list($err, $stdout) = exec_manual('which %s', $binary);
return $err === 0 ? trim($stdout) : null;
} }
return $err === 0 ? trim($stdout) : null;
} }

View file

@ -83,7 +83,7 @@ final class PhutilErrorLog
} }
public function onError($event, $value, array $metadata) { 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. // stderr by default. Copy them to stderr.
if ($this->logPath === null) { if ($this->logPath === null) {

View file

@ -229,7 +229,10 @@ final class PhutilOAuth1Future extends FutureProxy {
$consumer_secret = $this->consumerSecret->openEnvelope(); $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) { switch ($this->signatureMethod) {
case 'HMAC-SHA1': case 'HMAC-SHA1':

View file

@ -37,11 +37,12 @@ final class ArcanistComposerLinter extends ArcanistLinter {
} }
private function lintComposerJson($path) { 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( $composer_lock = phutil_json_decode(
Filesystem::readFile(dirname($path).'/composer.lock')); Filesystem::readFile(dirname($path).'/composer.lock'));
if ($composer_hash !== $composer_lock['hash']) { if ($composer_hash !== $composer_lock['content-hash']) {
$this->raiseLintAtPath( $this->raiseLintAtPath(
self::LINT_OUT_OF_DATE, self::LINT_OUT_OF_DATE,
pht( 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));
}
} }

View file

@ -133,7 +133,19 @@ abstract class ArcanistExternalLinter extends ArcanistFutureLinter {
* @task bin * @task bin
*/ */
final public function getBinary() { 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;
} }
/** /**

View file

@ -1,5 +1,6 @@
/* jshint maxerr: 1 */ /* jshint maxerr: 1 */
console.log('foobar') console.log(
{
~~~~~~~~~~ ~~~~~~~~~~
disabled:2:22:E043 disabled:3:1:E043
warning:2:22:W033 error:3:1:E019

View file

@ -3,5 +3,5 @@
</lang> </lang>
</languages> </languages>
~~~~~~~~~~ ~~~~~~~~~~
error:3:16:XML76:LibXML Error error:3:12:XML76:LibXML Error
error:4:1:XML5:LibXML Error error:4:1:XML5:LibXML Error

View file

@ -469,10 +469,12 @@ EOPHP;
$this->fileSymbolMap = $symbol_map; $this->fileSymbolMap = $symbol_map;
// We're done building the cache, so write it out immediately. Note that if ($futures) {
// we've only retained entries for files we found, so this implicitly cleans // We're done building/updating the cache, so write it out immediately.
// out old cache entries. // Note that we've only retained entries for files we found, so this
$this->writeSymbolCache($symbol_map, $source_map); // 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. // Our map is up to date, so either show it on stdout or write it to disk.
$this->librarySymbolMap = $this->buildLibraryMap($symbol_map); $this->librarySymbolMap = $this->buildLibraryMap($symbol_map);

View file

@ -762,7 +762,7 @@ final class ArcanistBundle extends Phobject {
$old_data = $this->getBlob($old_phid, $name); $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 // 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 // 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_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); $this->reserveBytes($new_length * 5 / 4);
if ($new_data === null) { if ($new_data === null) {

View file

@ -11,6 +11,14 @@ final class PlatformSymbols
return 'Phorge'; return 'Phorge';
} }
public static function getPlatformClientPath() {
return 'arcanist/';
}
public static function getPlatformServerPath() {
return 'phorge/';
}
public static function getProductNames() { public static function getProductNames() {
return array( return array(
self::getPlatformClientName(), self::getPlatformClientName(),

View file

@ -41,44 +41,44 @@ final class PhutilCowsay extends Phobject {
$template = $this->template; $template = $this->template;
// Real ".cow" files are Perl scripts which define a variable called // 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 // "$the_cow". We aren't going to interpret Perl, so just get everything
// (and any comments in the file) away. // between the EOC (End Of Cow) tokens. The initial EOC might be in
$template = phutil_split_lines($template, true); // quotes, and might have a semicolon.
$keep = array(); // We apply regexp modifiers
$is_perl_cowfile = false; // * 's' to make . match newlines within the EOC ... EOC block
foreach ($template as $key => $line) { // * 'm' so we can use ^ to match start of line within the multiline string
if (preg_match('/^#/', $line)) { $matches = null;
continue; if (
} preg_match('/\$the_cow/', $template) &&
if (preg_match('/^\s*\\$the_cow/', $line)) { preg_match('/EOC[\'"]?;?.*?^(.*?)^EOC/sm', $template, $matches)
$is_perl_cowfile = true; ) {
continue; $template = $matches[1];
}
if (preg_match('/^\s*EOC\s*$/', $line)) {
continue;
}
$keep[] = $line;
}
$template = implode('', $keep);
// Original .cow files are perl scripts which contain escaped sequences. // Original .cow files are perl scripts which contain escaped sequences.
// We attempt to unescape here by replacing any character preceded by a // We attempt to unescape here by replacing any character preceded by a
// backslash/escape with just that character. // backslash/escape with just that character.
if ($is_perl_cowfile) {
$template = preg_replace( $template = preg_replace(
'/\\\\(.)/', '/\\\\(.)/',
'$1', '$1',
$template); $template);
} else {
// Text template. Just strip away comments.
$template = preg_replace('/^#.*$/', '', $template);
} }
$template = preg_replace_callback( $token_patterns = array(
'/\\$([a-z]+)/', '/\\$([a-z]+)/',
array($this, 'replaceTemplateVariable'), '/\\${([a-z]+)}/',
$template); );
if ($template === false) { foreach ($token_patterns as $token_pattern) {
throw new Exception( $template = preg_replace_callback(
pht( $token_pattern,
'Failed to replace template variables while rendering cow!')); array($this, 'replaceTemplateVariable'),
$template);
if ($template === false) {
throw new Exception(
pht('Failed to replace template variables while rendering cow!'));
}
} }
$lines = $this->text; $lines = $this->text;

View file

@ -1,5 +1,5 @@
# test case for original perl-script cowfile # test case for original perl-script cowfile
$the_cow = $the_cow = <<EOC
$thoughts $thoughts
$thoughts $thoughts
/---\\__/---\\\\ /---\\__/---\\\\
@ -9,6 +9,7 @@ $the_cow =
/ ..--.. \\\\ / ..--.. \\\\
| \\ .... / || | \\ .... / ||
\\---/--\\---// \\---/--\\---//
EOC
~~~~~~~~~~ ~~~~~~~~~~
{ {
"text": "I made a friend!", "text": "I made a friend!",

View file

@ -0,0 +1,11 @@
__________________
< How are my eyes? >
------------------
\
\
__
UooU\.'@@@@@@`.
\__/(@@@@@@@@@@)
(@@@@@@@@)
`YY~~~~YY'
|| ||

View file

@ -0,0 +1,13 @@
$thoughts
$thoughts
__
U${eyes}U\.'@@@@@@`.
\__/(@@@@@@@@@@)
(@@@@@@@@)
`YY~~~~YY'
|| ||
~~~~~~~~~~
{
"text": "How are my eyes?",
"eyes": "oo"
}

View file

@ -0,0 +1,7 @@
__________________
< How are my eyes? >
------------------
\ ,__,
\ (oo)____
(__) )\
||--|| *

View file

@ -0,0 +1,12 @@
$eyes = ".." unless ($eyes);
$the_cow = <<EOC;
$thoughts ,__,
$thoughts ($eyes)____
(__) )\\
$tongue||--|| *
EOC
~~~~~~~~~~
{
"text": "How are my eyes?",
"eyes": "oo"
}

View file

@ -479,8 +479,8 @@ abstract class ArcanistWorkflow extends Phobject {
// If we have `token`, this server supports the simpler, new-style // If we have `token`, this server supports the simpler, new-style
// token-based authentication. Use that instead of all the certificate // token-based authentication. Use that instead of all the certificate
// stuff. // stuff.
$token = idx($credentials, 'token', ''); $token = idx($credentials, 'token');
if (strlen($token)) { if (phutil_nonempty_string($token)) {
$conduit = $this->getConduit(); $conduit = $this->getConduit();
$conduit->setConduitToken($token); $conduit->setConduitToken($token);
@ -2244,8 +2244,8 @@ abstract class ArcanistWorkflow extends Phobject {
protected function getModernUnitDictionary(array $map) { protected function getModernUnitDictionary(array $map) {
$map = $this->getModernCommonDictionary($map); $map = $this->getModernCommonDictionary($map);
$details = idx($map, 'userData', ''); $details = idx($map, 'userData');
if (strlen($details)) { if (phutil_nonempty_string($details)) {
$map['details'] = (string)$details; $map['details'] = (string)$details;
} }
unset($map['userData']); unset($map['userData']);