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/resources/celerity/map.php b/resources/celerity/map.php index d43cba4925..43a319846c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,13 +9,13 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '0fbedea0', - 'core.pkg.js' => '74ad315f', + 'core.pkg.css' => '704ac9c8', + 'core.pkg.js' => '0c06d967', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '5c459f92', - 'differential.pkg.js' => '218fda21', + 'differential.pkg.js' => '5080baf4', 'diffusion.pkg.css' => '42c75c37', - 'diffusion.pkg.js' => 'a98c0bf7', + 'diffusion.pkg.js' => '78c9885d', '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' => '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', @@ -113,14 +113,18 @@ 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/remarkup.css' => 'c286eaef', + 'rsrc/css/core/core.css' => 'b3ebd90d', + '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', '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' => '66697240', + 'rsrc/css/fuel/fuel-handle-list.css' => '2c4cbeca', + 'rsrc/css/fuel/fuel-map.css' => 'd6e31510', + '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', @@ -133,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' => 'd7723ecc', + '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', @@ -381,7 +385,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', @@ -392,7 +396,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' => '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', @@ -475,7 +479,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', @@ -569,13 +573,17 @@ return array( 'differential-revision-history-css' => '8aa3eac5', 'differential-revision-list-css' => '93d2df7d', 'differential-table-of-contents-css' => 'bba788b9', - 'diffusion-css' => 'b54c77b0', + 'diffusion-css' => 'e46232d6', 'diffusion-icons-css' => '23b31a1b', 'diffusion-readme-css' => 'b68a76e4', 'diffusion-repository-css' => 'b89e8c6c', 'diviner-shared-css' => '4bd263b0', 'font-fontawesome' => '3883938a', 'font-lato' => '23631304', + 'fuel-grid-css' => '66697240', + 'fuel-handle-list-css' => '2c4cbeca', + 'fuel-map-css' => 'd6e31510', + 'fuel-menu-css' => '21f5d199', 'global-drag-and-drop-css' => '1d2713a4', 'harbormaster-css' => '8dfe16b2', 'herald-css' => '648d39e2', @@ -613,11 +621,11 @@ 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', - 'javelin-behavior-diffusion-commit-graph' => 'ef836bf2', + 'javelin-behavior-diffusion-commit-graph' => 'ac10c917', 'javelin-behavior-diffusion-locate-file' => '87428eb2', 'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123', 'javelin-behavior-document-engine' => '243d6c22', @@ -773,12 +781,12 @@ 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', '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', @@ -800,7 +808,7 @@ return array( 'phabricator-object-selector-css' => 'ee77366f', 'phabricator-phtize' => '2f1db1ed', 'phabricator-prefab' => '5793d835', - 'phabricator-remarkup-css' => 'c286eaef', + 'phabricator-remarkup-css' => '24d48a73', 'phabricator-search-results-css' => '9ea70ace', 'phabricator-shaped-request' => '995f5102', 'phabricator-slowvote-css' => '1694baed', @@ -869,7 +877,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' => 'af98a277', 'phui-oi-simple-ui-css' => '6a30fa46', 'phui-pager-css' => 'd022c7ad', 'phui-pinboard-view-css' => '1f08f5d8', @@ -1005,13 +1013,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - '0cf79f45' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - ), '0d2490ce' => array( 'javelin-install', ), @@ -1231,7 +1232,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', @@ -1245,13 +1253,6 @@ return array( 'phuix-button-view', 'javelin-external-editor-link-engine', ), - '3ae89b20' => array( - 'phui-workcard-view-css', - ), - '3b4899b0' => array( - 'javelin-behavior', - 'phabricator-prefab', - ), '3dc5ad43' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1924,6 +1925,18 @@ return array( 'javelin-dom', 'phabricator-notification', ), + 'ac10c917' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), + 'ac2b1e01' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + ), 'ad258e28' => array( 'javelin-behavior', 'javelin-dom', @@ -2176,11 +2189,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/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/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); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bbe89e01d4..1c5fc71d2b 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', @@ -782,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', @@ -861,12 +861,8 @@ 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', - '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', @@ -877,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', @@ -1063,7 +1061,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', @@ -1310,6 +1307,17 @@ 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', + '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', + 'FuelMenuView' => 'view/fuel/FuelMenuView.php', + 'FuelView' => 'view/fuel/FuelView.php', 'FundBacker' => 'applications/fund/storage/FundBacker.php', 'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php', 'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php', @@ -1422,12 +1430,16 @@ 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', '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', @@ -1549,6 +1561,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', @@ -2172,6 +2185,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', @@ -2281,7 +2295,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', @@ -4582,7 +4595,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', @@ -4655,8 +4667,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', @@ -5959,9 +5969,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', @@ -6867,6 +6879,7 @@ phutil_register_library_map(array( 'DiffusionCommitEditEngine' => 'PhabricatorEditEngine', 'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine', 'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine', + 'DiffusionCommitGraphView' => 'DiffusionView', 'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasRevisionRelationship' => 'DiffusionCommitRelationship', @@ -6879,7 +6892,6 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitHookRejectException' => 'Exception', 'DiffusionCommitListController' => 'DiffusionController', - 'DiffusionCommitListView' => 'AphrontView', 'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField', @@ -6961,12 +6973,8 @@ phutil_register_library_map(array( 'DiffusionGitWireProtocolCapabilities' => 'Phobject', 'DiffusionGitWireProtocolRef' => 'Phobject', 'DiffusionGitWireProtocolRefList' => 'Phobject', - 'DiffusionGraphController' => 'DiffusionController', 'DiffusionHistoryController' => 'DiffusionController', - 'DiffusionHistoryListView' => 'DiffusionHistoryView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionHistoryTableView' => 'DiffusionHistoryView', - 'DiffusionHistoryView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField', @@ -6977,6 +6985,8 @@ phutil_register_library_map(array( 'DiffusionIdentityViewController' => 'DiffusionController', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionInternalCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'DiffusionInternalCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', @@ -7162,7 +7172,6 @@ phutil_register_library_map(array( 'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', - 'DiffusionTagTableView' => 'DiffusionView', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', @@ -7455,6 +7464,17 @@ phutil_register_library_map(array( 'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', + 'FuelComponentView' => 'FuelView', + 'FuelGridCellView' => 'FuelComponentView', + 'FuelGridRowView' => 'FuelView', + 'FuelGridView' => 'FuelComponentView', + 'FuelHandleListItemView' => 'FuelView', + 'FuelHandleListView' => 'FuelComponentView', + 'FuelMapItemView' => 'AphrontView', + 'FuelMapView' => 'FuelComponentView', + 'FuelMenuItemView' => 'FuelView', + 'FuelMenuView' => 'FuelComponentView', + 'FuelView' => 'AphrontView', 'FundBacker' => array( 'FundDAO', 'PhabricatorPolicyInterface', @@ -7614,18 +7634,23 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', + 'PhabricatorConduitResultInterface', ), 'HarbormasterBuildStepCoreCustomField' => array( 'HarbormasterBuildStepCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField', + 'HarbormasterBuildStepEditAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'HarbormasterBuildStepEditEngine' => 'PhabricatorEditEngine', 'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildStepGroup' => 'Phobject', 'HarbormasterBuildStepImplementation' => 'Phobject', 'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase', 'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildStepSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'HarbormasterBuildStepSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildTarget' => array( @@ -7758,6 +7783,7 @@ phutil_register_library_map(array( 'HeraldBuildableState' => 'HeraldState', 'HeraldCallWebhookAction' => 'HeraldAction', 'HeraldCommentAction' => 'HeraldAction', + 'HeraldCommentContentField' => 'HeraldField', 'HeraldCommitAdapter' => array( 'HeraldAdapter', 'HarbormasterBuildableAdapterInterface', @@ -8479,6 +8505,7 @@ phutil_register_library_map(array( 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorAWSSESFuture' => 'PhutilAWSFuture', 'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase', 'PhabricatorAccessLog' => 'Phobject', 'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -8603,7 +8630,6 @@ phutil_register_library_map(array( 'PhabricatorAuditController' => 'PhabricatorController', 'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuditInlineComment' => 'PhabricatorInlineComment', - 'PhabricatorAuditListView' => 'AphrontView', 'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow', 'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -11315,7 +11341,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', - 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow', @@ -11412,8 +11437,6 @@ phutil_register_library_map(array( 'PhabricatorConduitResultInterface', ), 'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO', - 'PhabricatorRepositoryURINormalizer' => 'Phobject', - 'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 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/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index 35db1270ce..37e8d563b4 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -221,9 +221,9 @@ final class PhabricatorCommitSearchEngine $bucket = $this->getResultBucket($query); - $template = id(new PhabricatorAuditListView()) + $template = id(new DiffusionCommitGraphView()) ->setViewer($viewer) - ->setShowDrafts(true); + ->setShowAuditors(true); $views = array(); if ($bucket) { @@ -235,37 +235,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 fb56e7cd55..0000000000 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ /dev/null @@ -1,179 +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(); - } - - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $phids[] = $author_phid; - } - } - - $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(); - - $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); - - 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/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; 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(); } 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(); 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/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/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/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/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index b7ea61b7a9..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; } } } @@ -1327,6 +1343,10 @@ final class DifferentialChangesetParser extends Phobject { $not_covered = 0; foreach ($this->new as $k => $new) { + if ($new === null) { + continue; + } + if (!$new['line']) { continue; } 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; } 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']; } } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index d4e685326a..7449867351 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -747,9 +747,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(); } 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..b1f51e77e7 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,15 +55,23 @@ 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; } + protected function getRevisionActionMetadata( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + return array(); + } + public static function loadAllActions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) @@ -105,17 +115,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 @@ -144,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/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/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/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 169e41dec5..32a3ab4a73 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -6,21 +6,63 @@ final class DifferentialRevisionRequestReviewTransaction const TRANSACTIONTYPE = 'differential.revision.request'; const ACTIONKEY = 'request-review'; - protected function getRevisionActionLabel() { + 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'); } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + 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) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { // See PHI975. When the action stack will promote the revision out of // draft, change the button text from "Submit Quietly". @@ -72,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) { @@ -105,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; + } + } 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.'); } 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/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/conduit/DiffusionQueryCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php index c070d358df..4a1c83b400 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(); - } } } @@ -96,48 +89,22 @@ 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(), ); - 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(); } 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/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/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index e069fb7779..473e8911ac 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')); @@ -183,7 +182,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 +481,6 @@ final class DiffusionCommitController extends DiffusionController { $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn( array( @@ -625,33 +622,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->getAuthorEpoch(); + $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( @@ -743,52 +757,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, @@ -843,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/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/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/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index f0fec0dd2d..fb35d9b6ad 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -35,11 +35,29 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); - $history_list = id(new DiffusionHistoryListView()) + $history_list = id(new DiffusionCommitGraphView()) ->setViewer($viewer) ->setDiffusionRequest($drequest) ->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). + + // 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/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/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(); 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) { 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..06dd4d5ffb 100644 --- a/src/applications/diffusion/data/DiffusionCommitRef.php +++ b/src/applications/diffusion/data/DiffusionCommitRef.php @@ -10,28 +10,48 @@ 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 function setHashes(array $hashes) { + assert_instances_of($hashes, 'DiffusionCommitHash'); $this->hashes = $hashes; return $this; } 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/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) { 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/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(), ); diff --git a/src/applications/diffusion/view/DiffusionCommitGraphView.php b/src/applications/diffusion/view/DiffusionCommitGraphView.php new file mode 100644 index 0000000000..30c252712c --- /dev/null +++ b/src/applications/diffusion/view/DiffusionCommitGraphView.php @@ -0,0 +1,642 @@ +history = $history; + return $this; + } + + public function getHistory() { + return $this->history; + } + + public function setCommits(array $commits) { + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); + $this->commits = $commits; + return $this; + } + + public function getCommits() { + 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; + } + + 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 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() { + $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(); + } + } + } + + $commits = $this->getCommitMap(); + + foreach ($commits as $commit) { + $author_phid = $commit->getAuthorDisplayPHID(); + if ($author_phid !== null) { + $phids[] = $author_phid; + } + } + + if ($show_auditors) { + foreach ($commits as $commit) { + $audits = $commit->getAudits(); + foreach ($audits as $auditor) { + $phids[] = $auditor->getAuditorPHID(); + } + } + } + + $handles = $viewer->loadHandles($phids); + + $views = array(); + + $items = $this->newHistoryItems(); + foreach ($items as $hash => $item) { + $content = array(); + + $commit = $item['commit']; + + $commit_description = $this->getCommitDescription($commit); + $commit_link = $this->getCommitURI($hash); + + $short_hash = $this->getCommitObjectName($hash); + $is_disabled = $this->getCommitIsDisabled($commit); + + $item_view = id(new PHUIObjectItemView()) + ->setViewer($viewer) + ->setHeader($commit_description) + ->setObjectName($short_hash) + ->setHref($commit_link) + ->setDisabled($is_disabled); + + $this->addBrowseAction($item_view, $hash); + + if ($show_builds) { + $this->addBuildAction($item_view, $hash); + } + + $this->addAuditAction($item_view, $hash); + + if ($show_auditors) { + $auditor_list = $item_view->newMapView(); + if ($commit) { + $auditors = $this->newAuditorList($commit, $handles); + $auditor_list->newItem() + ->setName(pht('Auditors')) + ->setValue($auditors); + } + } + + $property_list = $item_view->newMapView(); + + if ($commit) { + $author_view = $this->getCommitAuthorView($commit); + if ($author_view) { + $property_list->newItem() + ->setName(pht('Author')) + ->setValue($author_view); + } + } + + if ($show_revisions) { + if ($commit) { + $revisions = $this->getRevisions($commit); + if ($revisions) { + $list_view = $handles->newSublist(mpull($revisions, 'getPHID')) + ->newListView(); + + $property_list->newItem() + ->setName(pht('Revisions')) + ->setValue($list_view); + } + } + } + + $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; + $header = $item_date; + } else { + $header = null; + } + + $item_view = $views[$hash]; + + $list_view = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->addItem($item_view); + + if ($header !== null) { + $list_view->setHeader($header); + } + + $rows[] = $list_view; + } + + return $rows; + } + + public function render() { + $rows = $this->newObjectItemRows(); + + $graph = $this->newGraphView(); + foreach ($rows as $idx => $row) { + $cells = array(); + + if ($graph) { + $cells[] = phutil_tag( + 'td', + array( + 'class' => 'diffusion-commit-graph-path-cell', + ), + $graph[$idx]); + } + + $cells[] = phutil_tag( + 'td', + array( + 'class' => 'diffusion-commit-graph-content-cell', + ), + $row); + + $rows[$idx] = phutil_tag('tr', array(), $cells); + } + + $table = phutil_tag( + 'table', + array( + 'class' => 'diffusion-commit-graph-table', + ), + $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 shouldShowAuditors() { + return $this->getShowAuditors(); + } + + private function newHistoryItems() { + $items = array(); + + $history = $this->getHistory(); + if ($history !== null) { + foreach ($history as $history_item) { + $commit_hash = $history_item->getCommitIdentifier(); + + $items[$commit_hash] = array( + 'epoch' => $history_item->getEpoch(), + 'hash' => $commit_hash, + 'commit' => $this->getCommit($commit_hash), + ); + } + } else { + $commits = $this->getCommitMap(); + foreach ($commits as $commit) { + $commit_hash = $commit->getCommitIdentifier(); + + $items[$commit_hash] = array( + 'epoch' => $commit->getEpoch(), + 'hash' => $commit_hash, + '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($hash) { + $repository = $this->getRepository(); + + if ($repository) { + return $repository->getCommitURI($hash); + } + + $commit = $this->getCommit($hash); + if ($commit) { + return $commit->getURI(); + } + + return null; + } + + private function getCommitObjectName($hash) { + $repository = $this->getRepository(); + + if ($repository) { + return $repository->formatCommitName( + $hash, + $is_local = true); + } + + $commit = $this->getCommit($hash); + if ($commit) { + return $commit->getDisplayName(); + } + + return null; + } + + 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(); + + $author_phid = $commit->getAuthorDisplayPHID(); + if ($author_phid) { + return $viewer->loadHandles(array($author_phid)) + ->newListView(); + } + + return $commit->newCommitAuthorView($viewer); + } + + private function getCommit($hash) { + $commit_map = $this->getCommitMap(); + return idx($commit_map, $hash); + } + + private function getCommitMap() { + if ($this->commitMap === null) { + $commit_list = $this->newCommitList(); + $this->commitMap = mpull($commit_list, null, 'getCommitIdentifier'); + } + + return $this->commitMap; + } + + 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, + 'commit' => $hash, + )); + + $menu_item = $item->newMenuItem() + ->setName(pht('Browse Repository')) + ->setURI($uri); + + $menu_item->newIcon() + ->setIcon('fa-folder-open-o') + ->setColor('bluegrey'); + } + + private function addBuildAction(PHUIObjectItemView $item, $hash) { + $is_disabled = true; + + $buildable = null; + + $commit = $this->getCommit($hash); + if ($commit) { + $buildable = $this->getBuildable($commit); + } + + 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; + } + + $menu_item = $item->newMenuItem() + ->setName($name) + ->setURI($uri) + ->setDisabled(($uri === null)); + + $menu_item->newIcon() + ->setIcon($icon) + ->setColor($color); + } + + private function addAuditAction(PHUIObjectItemView $item_view, $hash) { + $commit = $this->getCommit($hash); + + if ($commit) { + $status = $commit->getAuditStatusObject(); + + $text = $status->getName(); + $icon = $status->getIcon(); + + $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'; + $icon = 'fa-times'; + $uri = null; + + $is_disabled = true; + } + + $menu_item = $item_view->newMenuItem() + ->setName($text) + ->setURI($uri) + ->setBackgroundColor($color) + ->setDisabled($is_disabled); + + $menu_item->newIcon() + ->setIcon($icon) + ->setColor($color); + } + + private function getBuildable(PhabricatorRepositoryCommit $commit) { + $buildable_map = $this->getBuildableMap(); + return idx($buildable_map, $commit->getPHID()); + } + + private function getBuildableMap() { + if ($this->buildableMap === null) { + $commits = $this->getCommitMap(); + $buildables = $this->loadBuildables($commits); + $this->buildableMap = $buildables; + } + + return $this->buildableMap; + } + + 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->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) + ->withRepository($repository) + ->withIdentifiers($identifiers) + ->needCommitData(true) + ->needIdentities(true) + ->execute(); + + 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) + ->newListView(); + + return $auditor_list; + } + +} 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/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; - } - -} 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); - } - -} 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/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(); - } - -} 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(); } 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/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() { 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; + } + } 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/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; + } + +} 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/query/HarbormasterBuildStepSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php new file mode 100644 index 0000000000..b866a1dbc9 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php @@ -0,0 +1,58 @@ +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/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( 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/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(); + } + } 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; + } + +} 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; } 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/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/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/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/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, 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/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/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()); 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/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/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/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(); } 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/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( 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(); - } - -} 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/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index b0f87eb9ff..555351d41a 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/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index d568c755c5..ca6fd7c484 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': @@ -2243,6 +2241,45 @@ 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 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 b5c2fde82a..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); } @@ -377,52 +382,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 +470,109 @@ 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->getAuthorString(); + } + + private function getRawCommitterStringForDisplay() { + $data = $this->getCommitData(); + return $data->getCommitterString(); + } + + 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 )----------------------------------------- */ public function getCapabilities() { @@ -841,12 +903,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 2f2fc3986f..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( @@ -92,4 +93,99 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { return array_values($holds); } + public function getAuthorString() { + $ref = $this->getCommitRef(); + + $author = $ref->getAuthor(); + if (strlen($author)) { + return $author; + } + + $author = phutil_string_cast($this->authorName); + if (strlen($author)) { + return $author; + } + + return null; + } + + public function getAuthorDisplayName() { + return $this->getCommitRef()->getAuthorName(); + } + + public function getAuthorEmail() { + return $this->getCommitRef()->getAuthorEmail(); + } + + public function getAuthorEpoch() { + $epoch = $this->getCommitRef()->getAuthorEpoch(); + + if ($epoch) { + return (int)$epoch; + } + + return null; + } + + public function getCommitterString() { + $ref = $this->getCommitRef(); + + $committer = $ref->getCommitter(); + if (strlen($committer)) { + return $committer; + } + + return $this->getCommitDetailString('committer'); + } + + public function getCommitterDisplayName() { + return $this->getCommitRef()->getCommitterName(); + } + + public function getCommitterEmail() { + return $this->getCommitRef()->getCommitterEmail(); + } + + private function getCommitDetailString($key) { + $string = $this->getCommitDetail($key); + $string = phutil_string_cast($string); + + if (strlen($string)) { + return $string; + } + + 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/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; + } + } diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index 9d3b2793f8..e1990eaec3 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -124,4 +124,29 @@ 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/PhabricatorRepositoryCommitPublishWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php index 0b0a194806..9d70e3db1c 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,43 @@ 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; + } + + // 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( + PhabricatorUser $actor, + PhabricatorRepository $repository, + PhabricatorRepositoryCommit $commit) { + $xactions = array( $this->newAuditTransactions($commit), $this->newPublishTransactions($commit), @@ -63,7 +95,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 +107,7 @@ final class PhabricatorRepositoryCommitPublishWorker } $editor = $commit->getApplicationTransactionEditor() - ->setActor($viewer) + ->setActor($actor) ->setActingAsPHID($acting_phid) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) @@ -116,9 +148,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'), )); @@ -391,4 +423,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 e589b52142..ea277a84c9 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -14,32 +14,14 @@ 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())); - } + $data = $this->loadCommitData($commit); + $data->setCommitRef($ref); - $ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data'])); - $this->updateCommitData($ref); + $this->updateCommitData($commit, $data); } if ($this->shouldQueueFollowupTasks()) { @@ -59,15 +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(); - $hashes = $ref->getHashes(); $has_committer = (bool)strlen($committer); - $viewer = PhabricatorUser::getOmnipotentUser(); - $identity_engine = id(new DiffusionRepositoryIdentityEngine()) ->setViewer($viewer) ->setSourcePHID($commit->getPHID()); @@ -88,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)); @@ -137,13 +114,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); } @@ -151,148 +121,12 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit->setAuthorIdentityPHID($author_identity->getPHID()); $commit->setSummary($data->getSummary()); + $commit->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)) { - $actor = PhabricatorUser::getOmnipotentUser(); - $this->closeRevisions($actor, $ref, $commit, $data); - $this->closeTasks($actor, $ref, $commit, $data); - } else { - $hold_reasons = $publisher->getCommitHoldReasons($commit); - $data->setCommitDetail('holdReasons', $hold_reasons); - } - $data->save(); $commit->writeImportStatusFlag( PhabricatorRepositoryCommit::IMPORTED_MESSAGE); } - private function closeRevisions( - PhabricatorUser $actor, - DiffusionCommitRef $ref, - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { - - $differential = 'PhabricatorDifferentialApplication'; - if (!PhabricatorApplication::isClassInstalled($differential)) { - return; - } - - $repository = $commit->getRepository(); - - $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, - DiffusionCommitRef $ref, - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { - - $maniphest = 'PhabricatorManiphestApplication'; - if (!PhabricatorApplication::isClassInstalled($maniphest)) { - return; - } - - $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/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 37c6dc5c2c..7dc666d107 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -976,8 +976,14 @@ 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) { @@ -988,7 +994,7 @@ return; } - $export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY; + $export_key = PhabricatorExportFormatSetting::SETTINGKEY; $preferences = PhabricatorUserPreferences::loadUserPreferences($viewer); $editor = id(new PhabricatorUserPreferencesEditor()) 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/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, 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/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 36aef29f07..c32412fe61 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -4971,8 +4971,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; @@ -4994,8 +4993,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; } 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 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()'), diff --git a/src/infrastructure/daemon/PhutilDaemonHandle.php b/src/infrastructure/daemon/PhutilDaemonHandle.php index 5030f5330c..3792c3b773 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() { @@ -327,13 +350,13 @@ 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); + } } } @@ -345,10 +368,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) { @@ -440,7 +465,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 +489,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) { 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: 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 '. 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; 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; } 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); 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( '', diff --git a/src/infrastructure/diff/view/PHUIDiffGraphView.php b/src/infrastructure/diff/view/PHUIDiffGraphView.php index 85741faab7..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,6 +215,7 @@ final class PHUIDiffGraphView extends Phobject { 'diffusion-commit-graph', array( 'count' => $count, + 'height' => $this->getHeight(), )); return $graph; 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', 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/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/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; 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); } 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)); } 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..b4e4b6ef5a --- /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 $this->newComponentTag( + '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/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/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/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/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 @@ +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/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 b5ad5a7fd6..4bb9c3ea6a 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -29,6 +29,8 @@ final class PHUIObjectItemView extends AphrontTagView { private $coverImage; private $description; private $clickable; + private $mapViews = array(); + private $menu; private $selectableName; private $selectableValue; @@ -190,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; } @@ -212,6 +214,21 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function newMenuItem() { + if (!$this->menu) { + $this->menu = new FuelMenuView(); + } + + return $this->menu->newItem(); + } + + public function newMapView() { + $list = id(new FuelMapView()) + ->addClass('fuel-map-property-list'); + $this->mapViews[] = $list; + return $list; + } + /** * This method has been deprecated, use @{method:setImageIcon} instead. * @@ -598,6 +615,20 @@ final class PHUIObjectItemView extends AphrontTagView { ''); } + $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( 'div', array( @@ -606,6 +637,7 @@ final class PHUIObjectItemView extends AphrontTagView { array( $subhead, $attrs, + $map_views, $this->renderChildren(), )); @@ -784,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( 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/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index 20f269dd3d..c80a0506ab 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; @@ -110,10 +102,6 @@ color: {$darkbluetext}; } -.diffusion-history-list .diffusion-differential-tag { - margin-left: 4px; -} - /* - Branch Styles ----------------------------------------------------------*/ .diffusion-branch-list .phui-oi-attribute a { @@ -237,3 +225,18 @@ 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-table .phui-oi-list-header { + color: {$darkgreytext}; + font-weight: bold; + padding: 4px 4px; + font-size: {$normalfontsize}; +} 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; diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 6e1c4b3627..bb698f3ac1 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -494,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; @@ -513,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; diff --git a/webroot/rsrc/css/fuel/fuel-grid.css b/webroot/rsrc/css/fuel/fuel-grid.css new file mode 100644 index 0000000000..16acb3eed7 --- /dev/null +++ b/webroot/rsrc/css/fuel/fuel-grid.css @@ -0,0 +1,31 @@ +/** + * @provides fuel-grid-css + */ + +.device-desktop .fuel-grid, +.device-tablet .fuel-grid-tablet > .fuel-grid { + display: table; + table-layout: fixed; +} + +.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-tablet .fuel-grid-tablet > .fuel-grid > .fuel-grid-body > + .fuel-grid-row { + display: table-row; +} + +.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; +} + +.fuel-grid-property-list > .fuel-grid { + width: 100%; +} 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-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; +} diff --git a/webroot/rsrc/css/fuel/fuel-menu.css b/webroot/rsrc/css/fuel/fuel-menu.css new file mode 100644 index 0000000000..513804361a --- /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.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; +} + +.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 85f83802e7..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 @@ -725,3 +725,20 @@ ul.phui-oi-list-view .phui-oi-selectable padding: 8px 0; background: linear-gradient({$lightbluebackground}, #fff 66%, #fff); } + +.phui-oi-menu { + background: {$lightbluebackground}; + border-style: solid; + border-color: {$thinblueborder}; + padding: 4px; +} + +.device-desktop .phui-oi-menu, +.device-tablet .phui-oi-menu { + width: 200px; + border-width: 0 0 0 1px; +} + +.device-phone .phui-oi-menu { + border-width: 1px 0 0; +} 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)); } }, diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index 5c4591b542..e8946ca9b4 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.height) { + h = config.height; + } else { + h = JX.Vector.getDim(nodes[ii].parentNode).y; + } + 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); } 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'; }