Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2889600
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
48 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php b/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
index cf005eae0d..1654214cb7 100644
--- a/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
@@ -1,159 +1,159 @@
<?php
final class ConduitConnectConduitAPIMethod extends ConduitAPIMethod {
public function getAPIMethodName() {
return 'conduit.connect';
}
public function shouldRequireAuthentication() {
return false;
}
public function shouldAllowUnguardedWrites() {
return true;
}
public function getMethodDescription() {
return 'Connect a session-based client.';
}
public function defineParamTypes() {
return array(
'client' => 'required string',
'clientVersion' => 'required int',
'clientDescription' => 'optional string',
'user' => 'optional string',
'authToken' => 'optional int',
'authSignature' => 'optional string',
'host' => 'deprecated',
);
}
public function defineReturnType() {
return 'dict<string, any>';
}
public function defineErrorTypes() {
return array(
'ERR-BAD-VERSION' =>
'Client/server version mismatch. Upgrade your server or downgrade '.
'your client.',
'NEW-ARC-VERSION' =>
'Client/server version mismatch. Upgrade your client.',
'ERR-UNKNOWN-CLIENT' =>
'Client is unknown.',
'ERR-INVALID-USER' =>
'The username you are attempting to authenticate with is not valid.',
'ERR-INVALID-CERTIFICATE' =>
'Your authentication certificate for this server is invalid.',
'ERR-INVALID-TOKEN' =>
"The challenge token you are authenticating with is outside of the ".
"allowed time range. Either your system clock is out of whack or ".
"you're executing a replay attack.",
'ERR-NO-CERTIFICATE' => 'This server requires authentication.',
);
}
protected function execute(ConduitAPIRequest $request) {
$client = $request->getValue('client');
$client_version = (int)$request->getValue('clientVersion');
$client_description = (string)$request->getValue('clientDescription');
$client_description = id(new PhutilUTF8StringTruncator())
- ->setMaximumCodepoints(255)
+ ->setMaximumBytes(255)
->truncateString($client_description);
$username = (string)$request->getValue('user');
// Log the connection, regardless of the outcome of checks below.
$connection = new PhabricatorConduitConnectionLog();
$connection->setClient($client);
$connection->setClientVersion($client_version);
$connection->setClientDescription($client_description);
$connection->setUsername($username);
$connection->save();
switch ($client) {
case 'arc':
$server_version = 6;
$supported_versions = array(
$server_version => true,
// Client version 5 introduced "user.query" call
4 => true,
// Client version 6 introduced "diffusion.getlintmessages" call
5 => true,
);
if (empty($supported_versions[$client_version])) {
if ($server_version < $client_version) {
$ex = new ConduitException('ERR-BAD-VERSION');
$ex->setErrorDescription(
"Your 'arc' client version is '{$client_version}', which ".
"is newer than the server version, '{$server_version}'. ".
"Upgrade your Phabricator install.");
} else {
$ex = new ConduitException('NEW-ARC-VERSION');
$ex->setErrorDescription(
"A new version of arc is available! You need to upgrade ".
"to connect to this server (you are running version ".
"{$client_version}, the server is running version ".
"{$server_version}).");
}
throw $ex;
}
break;
default:
// Allow new clients by default.
break;
}
$token = $request->getValue('authToken');
$signature = $request->getValue('authSignature');
$user = id(new PhabricatorUser())->loadOneWhere('username = %s', $username);
if (!$user) {
throw new ConduitException('ERR-INVALID-USER');
}
$session_key = null;
if ($token && $signature) {
$threshold = 60 * 15;
$now = time();
if (abs($token - $now) > $threshold) {
throw id(new ConduitException('ERR-INVALID-TOKEN'))
->setErrorDescription(
pht(
'The request you submitted is signed with a timestamp, but that '.
'timestamp is not within %s of the current time. The '.
'signed timestamp is %s (%s), and the current server time is '.
'%s (%s). This is a difference of %s seconds, but the '.
'timestamp must differ from the server time by no more than '.
'%s seconds. Your client or server clock may not be set '.
'correctly.',
phutil_format_relative_time($threshold),
$token,
date('r', $token),
$now,
date('r', $now),
($token - $now),
$threshold));
}
$valid = sha1($token.$user->getConduitCertificate());
if ($valid != $signature) {
throw new ConduitException('ERR-INVALID-CERTIFICATE');
}
$session_key = id(new PhabricatorAuthSessionEngine())->establishSession(
PhabricatorAuthSession::TYPE_CONDUIT,
$user->getPHID(),
$partial = false);
} else {
throw new ConduitException('ERR-NO-CERTIFICATE');
}
return array(
'connectionID' => $connection->getID(),
'sessionKey' => $session_key,
'userPHID' => $user->getPHID(),
);
}
}
diff --git a/src/applications/differential/parser/DifferentialCommitMessageParser.php b/src/applications/differential/parser/DifferentialCommitMessageParser.php
index 18aa1ceb98..5e3c14aa34 100644
--- a/src/applications/differential/parser/DifferentialCommitMessageParser.php
+++ b/src/applications/differential/parser/DifferentialCommitMessageParser.php
@@ -1,214 +1,214 @@
<?php
/**
* Parses commit messages (containing relatively freeform text with textual
* field labels) into a dictionary of fields.
*
* $parser = id(new DifferentialCommitMessageParser())
* ->setLabelMap($label_map)
* ->setTitleKey($key_title)
* ->setSummaryKey($key_summary);
*
* $fields = $parser->parseCorpus($corpus);
* $errors = $parser->getErrors();
*
* This is used by Differential to parse messages entered from the command line.
*
* @task config Configuring the Parser
* @task parse Parsing Messages
* @task support Support Methods
* @task internal Internals
*/
final class DifferentialCommitMessageParser {
private $labelMap;
private $titleKey;
private $summaryKey;
private $errors;
/* -( Configuring the Parser )--------------------------------------------- */
/**
* @task config
*/
public function setLabelMap(array $label_map) {
$this->labelMap = $label_map;
return $this;
}
/**
* @task config
*/
public function setTitleKey($title_key) {
$this->titleKey = $title_key;
return $this;
}
/**
* @task config
*/
public function setSummaryKey($summary_key) {
$this->summaryKey = $summary_key;
return $this;
}
/* -( Parsing Messages )--------------------------------------------------- */
/**
* @task parse
*/
public function parseCorpus($corpus) {
$this->errors = array();
$label_map = $this->labelMap;
$key_title = $this->titleKey;
$key_summary = $this->summaryKey;
if (!$key_title || !$key_summary || ($label_map === null)) {
throw new Exception(
pht(
'Expected labelMap, summaryKey and titleKey to be set before '.
'parsing a corpus.'));
}
$label_regexp = $this->buildLabelRegexp($label_map);
// NOTE: We're special casing things here to make the "Title:" label
// optional in the message.
$field = $key_title;
$seen = array();
$lines = explode("\n", trim($corpus));
$field_map = array();
foreach ($lines as $key => $line) {
$match = null;
if (preg_match($label_regexp, $line, $match)) {
$lines[$key] = trim($match['text']);
$field = $label_map[self::normalizeFieldLabel($match['field'])];
if (!empty($seen[$field])) {
$this->errors[] = pht(
'Field "%s" occurs twice in commit message!',
$field);
}
$seen[$field] = true;
}
$field_map[$key] = $field;
}
$fields = array();
foreach ($lines as $key => $line) {
$fields[$field_map[$key]][] = $line;
}
// This is a piece of special-cased magic which allows you to omit the
// field labels for "title" and "summary". If the user enters a large block
// of text at the beginning of the commit message with an empty line in it,
// treat everything before the blank line as "title" and everything after
// as "summary".
if (isset($fields[$key_title]) && empty($fields[$key_summary])) {
$lines = $fields[$key_title];
for ($ii = 0; $ii < count($lines); $ii++) {
if (strlen(trim($lines[$ii])) == 0) {
break;
}
}
if ($ii != count($lines)) {
$fields[$key_title] = array_slice($lines, 0, $ii);
$summary = array_slice($lines, $ii);
if (strlen(trim(implode("\n", $summary)))) {
$fields[$key_summary] = $summary;
}
}
}
// Implode all the lines back into chunks of text.
foreach ($fields as $name => $lines) {
$data = rtrim(implode("\n", $lines));
$data = ltrim($data, "\n");
$fields[$name] = $data;
}
// This is another piece of special-cased magic which allows you to
// enter a ridiculously long title, or just type a big block of stream
// of consciousness text, and have some sort of reasonable result conjured
// from it.
if (isset($fields[$key_title])) {
$terminal = '...';
$title = $fields[$key_title];
$short = id(new PhutilUTF8StringTruncator())
- ->setMaximumGlyphs(250)
+ ->setMaximumBytes(250)
->setTerminator($terminal)
->truncateString($title);
if ($short != $title) {
// If we shortened the title, split the rest into the summary, so
// we end up with a title like:
//
// Title title tile title title...
//
// ...and a summary like:
//
// ...title title title.
//
// Summary summary summary summary.
$summary = idx($fields, $key_summary, '');
$offset = strlen($short) - strlen($terminal);
$remainder = ltrim(substr($fields[$key_title], $offset));
$summary = '...'.$remainder."\n\n".$summary;
$summary = rtrim($summary, "\n");
$fields[$key_title] = $short;
$fields[$key_summary] = $summary;
}
}
return $fields;
}
/**
* @task parse
*/
public function getErrors() {
return $this->errors;
}
/* -( Support Methods )---------------------------------------------------- */
/**
* @task support
*/
public static function normalizeFieldLabel($label) {
return phutil_utf8_strtolower($label);
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
private function buildLabelRegexp(array $label_map) {
$field_labels = array_keys($label_map);
foreach ($field_labels as $key => $label) {
$field_labels[$key] = preg_quote($label, '/');
}
$field_labels = implode('|', $field_labels);
$field_pattern = '/^(?P<field>'.$field_labels.'):(?P<text>.*)$/i';
return $field_pattern;
}
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
index 3f4295e45e..85c7d47b65 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -1,454 +1,454 @@
<?php
final class HarbormasterBuild extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface {
protected $buildablePHID;
protected $buildPlanPHID;
protected $buildStatus;
protected $buildGeneration;
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
private $buildTargets = self::ATTACHABLE;
private $unprocessedCommands = self::ATTACHABLE;
/**
* Not currently being built.
*/
const STATUS_INACTIVE = 'inactive';
/**
* Pending pick up by the Harbormaster daemon.
*/
const STATUS_PENDING = 'pending';
/**
* Current building the buildable.
*/
const STATUS_BUILDING = 'building';
/**
* The build has passed.
*/
const STATUS_PASSED = 'passed';
/**
* The build has failed.
*/
const STATUS_FAILED = 'failed';
/**
* The build encountered an unexpected error.
*/
const STATUS_ERROR = 'error';
/**
* The build has been stopped.
*/
const STATUS_STOPPED = 'stopped';
/**
* The build has been deadlocked.
*/
const STATUS_DEADLOCKED = 'deadlocked';
/**
* Get a human readable name for a build status constant.
*
* @param const Build status constant.
* @return string Human-readable name.
*/
public static function getBuildStatusName($status) {
switch ($status) {
case self::STATUS_INACTIVE:
return pht('Inactive');
case self::STATUS_PENDING:
return pht('Pending');
case self::STATUS_BUILDING:
return pht('Building');
case self::STATUS_PASSED:
return pht('Passed');
case self::STATUS_FAILED:
return pht('Failed');
case self::STATUS_ERROR:
return pht('Unexpected Error');
case self::STATUS_STOPPED:
return pht('Paused');
case self::STATUS_DEADLOCKED:
return pht('Deadlocked');
default:
return pht('Unknown');
}
}
public static function getBuildStatusIcon($status) {
switch ($status) {
case self::STATUS_INACTIVE:
case self::STATUS_PENDING:
return PHUIStatusItemView::ICON_OPEN;
case self::STATUS_BUILDING:
return PHUIStatusItemView::ICON_RIGHT;
case self::STATUS_PASSED:
return PHUIStatusItemView::ICON_ACCEPT;
case self::STATUS_FAILED:
return PHUIStatusItemView::ICON_REJECT;
case self::STATUS_ERROR:
return PHUIStatusItemView::ICON_MINUS;
case self::STATUS_STOPPED:
return PHUIStatusItemView::ICON_MINUS;
case self::STATUS_DEADLOCKED:
return PHUIStatusItemView::ICON_WARNING;
default:
return PHUIStatusItemView::ICON_QUESTION;
}
}
public static function getBuildStatusColor($status) {
switch ($status) {
case self::STATUS_INACTIVE:
return 'dark';
case self::STATUS_PENDING:
case self::STATUS_BUILDING:
return 'blue';
case self::STATUS_PASSED:
return 'green';
case self::STATUS_FAILED:
case self::STATUS_ERROR:
case self::STATUS_DEADLOCKED:
return 'red';
case self::STATUS_STOPPED:
return 'dark';
default:
return 'bluegrey';
}
}
public static function initializeNewBuild(PhabricatorUser $actor) {
return id(new HarbormasterBuild())
->setBuildStatus(self::STATUS_INACTIVE)
->setBuildGeneration(0);
}
public function delete() {
$this->openTransaction();
$this->deleteUnprocessedCommands();
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'buildStatus' => 'text32',
'buildGeneration' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_buildable' => array(
'columns' => array('buildablePHID'),
),
'key_plan' => array(
'columns' => array('buildPlanPHID'),
),
'key_status' => array(
'columns' => array('buildStatus'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildPHIDType::TYPECONST);
}
public function attachBuildable(HarbormasterBuildable $buildable) {
$this->buildable = $buildable;
return $this;
}
public function getBuildable() {
return $this->assertAttached($this->buildable);
}
public function getName() {
if ($this->getBuildPlan()) {
return $this->getBuildPlan()->getName();
}
return pht('Build');
}
public function attachBuildPlan(
HarbormasterBuildPlan $build_plan = null) {
$this->buildPlan = $build_plan;
return $this;
}
public function getBuildPlan() {
return $this->assertAttached($this->buildPlan);
}
public function getBuildTargets() {
return $this->assertAttached($this->buildTargets);
}
public function attachBuildTargets(array $targets) {
$this->buildTargets = $targets;
return $this;
}
public function isBuilding() {
return $this->getBuildStatus() === self::STATUS_PENDING ||
$this->getBuildStatus() === self::STATUS_BUILDING;
}
public function createLog(
HarbormasterBuildTarget $build_target,
$log_source,
$log_type) {
$log_source = id(new PhutilUTF8StringTruncator())
- ->setMaximumCodepoints(250)
+ ->setMaximumBytes(250)
->truncateString($log_source);
$log = HarbormasterBuildLog::initializeNewBuildLog($build_target)
->setLogSource($log_source)
->setLogType($log_type)
->save();
return $log;
}
public function createArtifact(
HarbormasterBuildTarget $build_target,
$artifact_key,
$artifact_type) {
$artifact =
HarbormasterBuildArtifact::initializeNewBuildArtifact($build_target);
$artifact->setArtifactKey(
$this->getPHID(),
$this->getBuildGeneration(),
$artifact_key);
$artifact->setArtifactType($artifact_type);
$artifact->save();
return $artifact;
}
public function loadArtifact($name) {
$artifact = id(new HarbormasterBuildArtifactQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withArtifactKeys(
$this->getPHID(),
$this->getBuildGeneration(),
array($name))
->executeOne();
if ($artifact === null) {
throw new Exception('Artifact not found!');
}
return $artifact;
}
public function retrieveVariablesFromBuild() {
$results = array(
'buildable.diff' => null,
'buildable.revision' => null,
'buildable.commit' => null,
'repository.callsign' => null,
'repository.vcs' => null,
'repository.uri' => null,
'step.timestamp' => null,
'build.id' => null,
);
$buildable = $this->getBuildable();
$object = $buildable->getBuildableObject();
$object_variables = $object->getBuildVariables();
$results = $object_variables + $results;
$results['step.timestamp'] = time();
$results['build.id'] = $this->getID();
return $results;
}
public static function getAvailableBuildVariables() {
$objects = id(new PhutilSymbolLoader())
->setAncestorClass('HarbormasterBuildableInterface')
->loadObjects();
$variables = array();
$variables[] = array(
'step.timestamp' => pht('The current UNIX timestamp.'),
'build.id' => pht('The ID of the current build.'),
'target.phid' => pht('The PHID of the current build target.'),
);
foreach ($objects as $object) {
$variables[] = $object->getAvailableBuildVariables();
}
$variables = array_mergev($variables);
return $variables;
}
public function isComplete() {
switch ($this->getBuildStatus()) {
case self::STATUS_PASSED:
case self::STATUS_FAILED:
case self::STATUS_ERROR:
case self::STATUS_STOPPED:
return true;
}
return false;
}
public function isStopped() {
return ($this->getBuildStatus() == self::STATUS_STOPPED);
}
/* -( Build Commands )----------------------------------------------------- */
private function getUnprocessedCommands() {
return $this->assertAttached($this->unprocessedCommands);
}
public function attachUnprocessedCommands(array $commands) {
$this->unprocessedCommands = $commands;
return $this;
}
public function canRestartBuild() {
return !$this->isRestarting();
}
public function canStopBuild() {
return !$this->isComplete() &&
!$this->isStopped() &&
!$this->isStopping();
}
public function canResumeBuild() {
return $this->isStopped() &&
!$this->isResuming();
}
public function isStopping() {
$is_stopping = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_STOP:
$is_stopping = true;
break;
case HarbormasterBuildCommand::COMMAND_RESUME:
case HarbormasterBuildCommand::COMMAND_RESTART:
$is_stopping = false;
break;
}
}
return $is_stopping;
}
public function isResuming() {
$is_resuming = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
case HarbormasterBuildCommand::COMMAND_RESUME:
$is_resuming = true;
break;
case HarbormasterBuildCommand::COMMAND_STOP:
$is_resuming = false;
break;
}
}
return $is_resuming;
}
public function isRestarting() {
$is_restarting = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
$is_restarting = true;
break;
}
}
return $is_restarting;
}
public function deleteUnprocessedCommands() {
foreach ($this->getUnprocessedCommands() as $key => $command_object) {
$command_object->delete();
unset($this->unprocessedCommands[$key]);
}
return $this;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new HarbormasterBuildTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getBuildable()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBuildable()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('A build inherits policies from its buildable.');
}
}
diff --git a/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php b/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php
index c82eac5954..2515b185cc 100644
--- a/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php
+++ b/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php
@@ -1,103 +1,103 @@
<?php
final class PhameCreatePostConduitAPIMethod extends PhameConduitAPIMethod {
public function getAPIMethodName() {
return 'phame.createpost';
}
public function getMethodDescription() {
return pht('Create a phame post.');
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function defineParamTypes() {
return array(
'blogPHID' => 'required phid',
'title' => 'required string',
'body' => 'required string',
'phameTitle' => 'optional string',
'bloggerPHID' => 'optional phid',
'isDraft' => 'optional bool',
);
}
public function defineReturnType() {
return 'list<dict>';
}
public function defineErrorTypes() {
return array(
'ERR-INVALID-PARAMETER' =>
pht('Missing or malformed parameter.'),
'ERR-INVALID-BLOG' =>
pht('Invalid blog PHID or user can not post to blog.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$user = $request->getUser();
$blog_phid = $request->getValue('blogPHID');
$title = $request->getValue('title');
$body = $request->getValue('body');
$exception_description = array();
if (!$blog_phid) {
$exception_description[] = pht('No blog phid.');
}
if (!strlen($title)) {
$exception_description[] = pht('No post title.');
}
if (!strlen($body)) {
$exception_description[] = pht('No post body.');
}
if ($exception_description) {
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(implode("\n", $exception_description));
}
$blogger_phid = $request->getValue('bloggerPHID');
if ($blogger_phid) {
$blogger = id(new PhabricatorPeopleQuery())
->setViewer($user)
->withPHIDs(array($blogger_phid))
->executeOne();
} else {
$blogger = $user;
}
$blog = id(new PhameBlogQuery())
->setViewer($blogger)
->withPHIDs(array($blog_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_JOIN,
))
->executeOne();
if (!$blog) {
throw new ConduitException('ERR-INVALID-BLOG');
}
$post = PhamePost::initializePost($blogger, $blog);
$is_draft = $request->getValue('isDraft', false);
if (!$is_draft) {
$post->setDatePublished(time());
$post->setVisibility(PhamePost::VISIBILITY_PUBLISHED);
}
$post->setTitle($title);
$phame_title = $request->getValue(
'phameTitle',
id(new PhutilUTF8StringTruncator())
- ->setMaximumCodepoints(64)
+ ->setMaximumBytes(64)
->truncateString($title));
$post->setPhameTitle(PhabricatorSlug::normalize($phame_title));
$post->setBody($body);
$post->save();
return $post->toDictionary();
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php
index bc8cc42dc2..204f153b5a 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php
@@ -1,73 +1,73 @@
<?php
final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO {
/**
* NOTE: We denormalize this into the commit table; make sure the sizes
* match up.
*/
const SUMMARY_MAX_LENGTH = 80;
protected $commitID;
protected $authorName = '';
protected $commitMessage = '';
protected $commitDetails = array();
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'commitDetails' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'authorName' => 'text',
'commitMessage' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'commitID' => array(
'columns' => array('commitID'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function getSummary() {
$message = $this->getCommitMessage();
return self::summarizeCommitMessage($message);
}
public static function summarizeCommitMessage($message) {
$summary = phutil_split_lines($message, $retain_endings = false);
$summary = head($summary);
$summary = id(new PhutilUTF8StringTruncator())
- ->setMaximumCodepoints(self::SUMMARY_MAX_LENGTH)
+ ->setMaximumBytes(self::SUMMARY_MAX_LENGTH)
->truncateString($summary);
return $summary;
}
public function getCommitDetail($key, $default = null) {
return idx($this->commitDetails, $key, $default);
}
public function setCommitDetail($key, $value) {
$this->commitDetails[$key] = $value;
return $this;
}
public function toDictionary() {
return array(
'commitID' => $this->commitID,
'authorName' => $this->authorName,
'commitMessage' => $this->commitMessage,
'commitDetails' => json_encode($this->commitDetails),
);
}
public static function newFromDictionary(array $dict) {
return id(new PhabricatorRepositoryCommitData())
->loadFromArray($dict);
}
}
diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
index 84da0a293c..39ed2b5711 100644
--- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
+++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
@@ -1,547 +1,547 @@
<?php
abstract class PhabricatorRepositoryCommitMessageParserWorker
extends PhabricatorRepositoryCommitParserWorker {
final protected function updateCommitData(DiffusionCommitRef $ref) {
$commit = $this->commit;
$author = $ref->getAuthor();
$message = $ref->getMessage();
$committer = $ref->getCommitter();
$hashes = $ref->getHashes();
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
$data = new PhabricatorRepositoryCommitData();
}
$data->setCommitID($commit->getID());
$data->setAuthorName(id(new PhutilUTF8StringTruncator())
- ->setMaximumCodepoints(255)
+ ->setMaximumBytes(255)
->truncateString((string)$author));
$data->setCommitDetail('authorName', $ref->getAuthorName());
$data->setCommitDetail('authorEmail', $ref->getAuthorEmail());
$data->setCommitDetail(
'authorPHID',
$this->resolveUserPHID($commit, $author));
$data->setCommitMessage($message);
if (strlen($committer)) {
$data->setCommitDetail('committer', $committer);
$data->setCommitDetail('committerName', $ref->getCommitterName());
$data->setCommitDetail('committerEmail', $ref->getCommitterEmail());
$data->setCommitDetail(
'committerPHID',
$this->resolveUserPHID($commit, $committer));
}
$repository = $this->repository;
$author_phid = $data->getCommitDetail('authorPHID');
$committer_phid = $data->getCommitDetail('committerPHID');
$user = new PhabricatorUser();
if ($author_phid) {
$user = $user->loadOneWhere(
'phid = %s',
$author_phid);
}
$differential_app = 'PhabricatorDifferentialApplication';
$revision_id = null;
$low_level_query = null;
if (PhabricatorApplication::isClassInstalled($differential_app)) {
$low_level_query = id(new DiffusionLowLevelCommitFieldsQuery())
->setRepository($repository)
->withCommitRef($ref);
$field_values = $low_level_query->execute();
$revision_id = idx($field_values, 'revisionID');
if (!empty($field_values['reviewedByPHIDs'])) {
$data->setCommitDetail(
'reviewerPHID',
reset($field_values['reviewedByPHIDs']));
}
$data->setCommitDetail('differential.revisionID', $revision_id);
}
if ($author_phid != $commit->getAuthorPHID()) {
$commit->setAuthorPHID($author_phid);
}
$commit->setSummary($data->getSummary());
$commit->save();
// Figure out if we're going to try to "autoclose" related objects (e.g.,
// close linked tasks and related revisions) and, if not, record why we
// aren't. Autoclose can be disabled for various reasons at the repository
// or commit levels.
$autoclose_reason = $repository->shouldSkipAutocloseCommit($commit);
$data->setCommitDetail('autocloseReason', $autoclose_reason);
$should_autoclose = $repository->shouldAutocloseCommit($commit);
// When updating related objects, we'll act under an omnipotent user to
// ensure we can see them, but take actions as either the committer or
// author (if we recognize their accounts) or the Diffusion application
// (if we do not).
$actor = PhabricatorUser::getOmnipotentUser();
$acting_as_phid = nonempty(
$committer_phid,
$author_phid,
id(new PhabricatorDiffusionApplication())->getPHID());
$conn_w = id(new DifferentialRevision())->establishConnection('w');
$reverts_refs = id(new DifferentialCustomFieldRevertsParser())
->parseCorpus($message);
$reverts = array_mergev(ipull($reverts_refs, 'monograms'));
if ($reverts) {
$reverted_commits = id(new DiffusionCommitQuery())
->setViewer($actor)
->withIdentifiers($reverts)
->execute();
$reverted_commit_phids = mpull($reverted_commits, 'getPHID', 'getPHID');
// NOTE: Skip any write attempts if a user cleverly implies a commit
// reverts itself.
unset($reverted_commit_phids[$commit->getPHID()]);
$editor = new PhabricatorEdgeEditor();
foreach ($reverted_commit_phids as $commit_phid) {
try {
$editor->addEdge(
$commit->getPHID(),
DiffusionCommitRevertsCommitEdgeType::EDGECONST,
$commit_phid);
} catch (PhabricatorEdgeCycleException $ex) {
continue;
}
}
$editor->save();
}
// NOTE: The `differential_commit` table has a unique ID on `commitPHID`,
// preventing more than one revision from being associated with a commit.
// Generally this is good and desirable, but with the advent of hash
// tracking we may end up in a situation where we match several different
// revisions. We just kind of ignore this and pick one, we might want to
// revisit this and do something differently. (If we match several revisions
// someone probably did something very silly, though.)
$revision = null;
if ($revision_id) {
$revision_query = id(new DifferentialRevisionQuery())
->withIDs(array($revision_id))
->setViewer($actor)
->needReviewerStatus(true)
->needActiveDiffs(true);
$revision = $revision_query->executeOne();
if ($revision) {
if (!$data->getCommitDetail('precommitRevisionStatus')) {
$data->setCommitDetail(
'precommitRevisionStatus',
$revision->getStatus());
}
$commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST;
id(new PhabricatorEdgeEditor())
->addEdge($commit->getPHID(), $commit_drev, $revision->getPHID())
->save();
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)',
DifferentialRevision::TABLE_COMMIT,
$revision->getID(),
$commit->getPHID());
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
$should_close = ($revision->getStatus() != $status_closed) &&
$should_autoclose;
if ($should_close) {
$commit_close_xaction = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_ACTION)
->setNewValue(DifferentialAction::ACTION_CLOSE)
->setMetadataValue('isCommitClose', true);
$commit_close_xaction->setMetadataValue(
'commitPHID',
$commit->getPHID());
$commit_close_xaction->setMetadataValue(
'committerPHID',
$committer_phid);
$commit_close_xaction->setMetadataValue(
'committerName',
$data->getCommitDetail('committer'));
$commit_close_xaction->setMetadataValue(
'authorPHID',
$author_phid);
$commit_close_xaction->setMetadataValue(
'authorName',
$data->getAuthorName());
if ($low_level_query) {
$commit_close_xaction->setMetadataValue(
'revisionMatchData',
$low_level_query->getRevisionMatchData());
$data->setCommitDetail(
'revisionMatchData',
$low_level_query->getRevisionMatchData());
}
$diff = $this->generateFinalDiff($revision, $acting_as_phid);
$vs_diff = $this->loadChangedByCommit($revision, $diff);
$changed_uri = null;
if ($vs_diff) {
$data->setCommitDetail('vsDiff', $vs_diff->getID());
$changed_uri = PhabricatorEnv::getProductionURI(
'/D'.$revision->getID().
'?vs='.$vs_diff->getID().
'&id='.$diff->getID().
'#toc');
}
$xactions = array();
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_UPDATE)
->setIgnoreOnNoEffect(true)
->setNewValue($diff->getPHID())
->setMetadataValue('isCommitUpdate', true);
$xactions[] = $commit_close_xaction;
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_DAEMON,
array());
$editor = id(new DifferentialTransactionEditor())
->setActor($actor)
->setActingAsPHID($acting_as_phid)
->setContinueOnMissingFields(true)
->setContentSource($content_source)
->setChangedPriorToCommitURI($changed_uri)
->setIsCloseByCommit(true);
try {
$editor->applyTransactions($revision, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
// NOTE: We've marked transactions other than the CLOSE transaction
// as ignored when they don't have an effect, so this means that we
// lost a race to close the revision. That's perfectly fine, we can
// just continue normally.
}
}
}
}
if ($should_autoclose) {
$this->closeTasks(
$actor,
$acting_as_phid,
$repository,
$commit,
$message);
}
$data->save();
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_MESSAGE);
}
private function generateFinalDiff(
DifferentialRevision $revision,
$actor_phid) {
$viewer = PhabricatorUser::getOmnipotentUser();
$drequest = DiffusionRequest::newFromDictionary(array(
'user' => $viewer,
'repository' => $this->repository,
));
$raw_diff = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.rawdiffquery',
array(
'commit' => $this->commit->getCommitIdentifier(),
));
// TODO: Support adds, deletes and moves under SVN.
if (strlen($raw_diff)) {
$changes = id(new ArcanistDiffParser())->parseDiff($raw_diff);
} else {
// This is an empty diff, maybe made with `git commit --allow-empty`.
// NOTE: These diffs have the same tree hash as their ancestors, so
// they may attach to revisions in an unexpected way. Just let this
// happen for now, although it might make sense to special case it
// eventually.
$changes = array();
}
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes)
->setRepositoryPHID($this->repository->getPHID())
->setAuthorPHID($actor_phid)
->setCreationMethod('commit')
->setSourceControlSystem($this->repository->getVersionControlSystem())
->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP)
->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP)
->setDateCreated($this->commit->getEpoch())
->setDescription(
'Commit r'.
$this->repository->getCallsign().
$this->commit->getCommitIdentifier());
// TODO: This is not correct in SVN where one repository can have multiple
// Arcanist projects.
$arcanist_project = id(new PhabricatorRepositoryArcanistProject())
->loadOneWhere('repositoryID = %d LIMIT 1', $this->repository->getID());
if ($arcanist_project) {
$diff->setArcanistProjectPHID($arcanist_project->getPHID());
}
$parents = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.commitparentsquery',
array(
'commit' => $this->commit->getCommitIdentifier(),
));
if ($parents) {
$diff->setSourceControlBaseRevision(head($parents));
}
// TODO: Attach binary files.
return $diff->save();
}
private function loadChangedByCommit(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$repository = $this->repository;
$vs_diff = id(new DifferentialDiffQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withRevisionIDs(array($revision->getID()))
->needChangesets(true)
->setLimit(1)
->executeOne();
if (!$vs_diff) {
return null;
}
if ($vs_diff->getCreationMethod() == 'commit') {
return null;
}
$vs_changesets = array();
foreach ($vs_diff->getChangesets() as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $vs_diff);
$path = ltrim($path, '/');
$vs_changesets[$path] = $changeset;
}
$changesets = array();
foreach ($diff->getChangesets() as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff);
$path = ltrim($path, '/');
$changesets[$path] = $changeset;
}
if (array_fill_keys(array_keys($changesets), true) !=
array_fill_keys(array_keys($vs_changesets), true)) {
return $vs_diff;
}
$file_phids = array();
foreach ($vs_changesets as $changeset) {
$metadata = $changeset->getMetadata();
$file_phid = idx($metadata, 'new:binary-phid');
if ($file_phid) {
$file_phids[$file_phid] = $file_phid;
}
}
$files = array();
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
}
foreach ($changesets as $path => $changeset) {
$vs_changeset = $vs_changesets[$path];
$file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid');
if ($file_phid) {
if (!isset($files[$file_phid])) {
return $vs_diff;
}
$drequest = DiffusionRequest::newFromDictionary(array(
'user' => PhabricatorUser::getOmnipotentUser(),
'initFromConduit' => false,
'repository' => $this->repository,
'commit' => $this->commit->getCommitIdentifier(),
'path' => $path,
));
$corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest)
->setViewer(PhabricatorUser::getOmnipotentUser())
->loadFileContent()
->getCorpus();
if ($files[$file_phid]->loadFileData() != $corpus) {
return $vs_diff;
}
} else {
$context = implode("\n", $changeset->makeChangesWithContext());
$vs_context = implode("\n", $vs_changeset->makeChangesWithContext());
// We couldn't just compare $context and $vs_context because following
// diffs will be considered different:
//
// -(empty line)
// -echo 'test';
// (empty line)
//
// (empty line)
// -echo "test";
// -(empty line)
$hunk = id(new DifferentialHunkModern())->setChanges($context);
$vs_hunk = id(new DifferentialHunkModern())->setChanges($vs_context);
if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() ||
$hunk->makeNewFile() != $vs_hunk->makeNewFile()) {
return $vs_diff;
}
}
}
return null;
}
private function resolveUserPHID(
PhabricatorRepositoryCommit $commit,
$user_name) {
return id(new DiffusionResolveUserQuery())
->withCommit($commit)
->withName($user_name)
->execute();
}
private function closeTasks(
PhabricatorUser $actor,
$acting_as,
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit,
$message) {
$maniphest = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalled($maniphest)) {
return;
}
$prefixes = ManiphestTaskStatus::getStatusPrefixMap();
$suffixes = ManiphestTaskStatus::getStatusSuffixMap();
$matches = id(new ManiphestCustomFieldStatusParser())
->parseCorpus($message);
$task_statuses = 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_statuses[$task_id] = $status;
}
}
if (!$task_statuses) {
return;
}
$tasks = id(new ManiphestTaskQuery())
->setViewer($actor)
->withIDs(array_keys($task_statuses))
->needProjectPHIDs(true)
->execute();
foreach ($tasks as $task_id => $task) {
$xactions = array();
$edge_type = ManiphestTaskHasCommitEdgeType::EDGECONST;
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $edge_type)
->setNewValue(
array(
'+' => array(
$commit->getPHID() => $commit->getPHID(),
),
));
$status = $task_statuses[$task_id];
if ($status) {
if ($task->getStatus() != $status) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_STATUS)
->setNewValue($status);
$commit_name = $repository->formatCommitName(
$commit->getCommitIdentifier());
$status_message = pht(
'Closed by commit %s.',
$commit_name);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ManiphestTransactionComment())
->setContent($status_message));
}
}
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_DAEMON,
array());
$editor = id(new ManiphestTransactionEditor())
->setActor($actor)
->setActingAsPHID($acting_as)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setUnmentionablePHIDMap(
array($commit->getPHID() => $commit->getPHID()))
->setContentSource($content_source);
$editor->applyTransactions($task, $xactions);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 12:24 (3 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1124522
Default Alt Text
(48 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment