Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php
index 107d22d2a9..4527983311 100644
--- a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php
@@ -1,142 +1,105 @@
<?php
final class DifferentialParseCommitMessageConduitAPIMethod
extends DifferentialConduitAPIMethod {
private $errors;
public function getAPIMethodName() {
return 'differential.parsecommitmessage';
}
public function getMethodDescription() {
return pht('Parse commit messages for Differential fields.');
}
protected function defineParamTypes() {
return array(
'corpus' => 'required string',
'partial' => 'optional bool',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$corpus = $request->getValue('corpus');
$is_partial = $request->getValue('partial');
- $revision = new DifferentialRevision();
-
$field_list = PhabricatorCustomField::getObjectFields(
- $revision,
+ new DifferentialRevision(),
DifferentialCustomField::ROLE_COMMITMESSAGE);
$field_list->setViewer($viewer);
- $field_map = mpull($field_list->getFields(), null, 'getFieldKeyForConduit');
-
- $this->errors = array();
+ $field_map = mpull($field_list, null, 'getFieldKeyForConduit');
- $label_map = $this->buildLabelMap($field_list);
- $corpus_map = $this->parseCommitMessage($corpus, $label_map);
+ $corpus_map = $this->parseCommitMessage($corpus);
$values = array();
foreach ($corpus_map as $field_key => $text_value) {
$field = idx($field_map, $field_key);
if (!$field) {
throw new Exception(
pht(
'Parser emitted text value for field key "%s", but no such '.
'field exists.',
$field_key));
}
try {
$values[$field_key] = $field->parseValueFromCommitMessage($text_value);
} catch (DifferentialFieldParseException $ex) {
$this->errors[] = pht(
'Error parsing field "%s": %s',
$field->renderCommitMessageLabel(),
$ex->getMessage());
}
}
if (!$is_partial) {
foreach ($field_map as $key => $field) {
try {
$field->validateCommitMessageValue(idx($values, $key));
} catch (DifferentialFieldValidationException $ex) {
$this->errors[] = pht(
'Invalid or missing field "%s": %s',
$field->renderCommitMessageLabel(),
$ex->getMessage());
}
}
}
// grab some extra information about the Differential Revision: field...
$revision_id_field = new DifferentialRevisionIDField();
$revision_id_value = idx(
$corpus_map,
$revision_id_field->getFieldKeyForConduit());
$revision_id_valid_domain = PhabricatorEnv::getProductionURI('');
return array(
'errors' => $this->errors,
'fields' => $values,
'revisionIDFieldInfo' => array(
'value' => $revision_id_value,
'validDomain' => $revision_id_valid_domain,
),
);
}
- private function buildLabelMap(PhabricatorCustomFieldList $field_list) {
- $label_map = array();
-
- foreach ($field_list->getFields() as $key => $field) {
- $labels = $field->getCommitMessageLabels();
- $key = $field->getFieldKeyForConduit();
-
- foreach ($labels as $label) {
- $normal_label = DifferentialCommitMessageParser::normalizeFieldLabel(
- $label);
- if (!empty($label_map[$normal_label])) {
- throw new Exception(
- pht(
- 'Field label "%s" is parsed by two custom fields: "%s" and '.
- '"%s". Each label must be parsed by only one field.',
- $label,
- $key,
- $label_map[$normal_label]));
- }
- $label_map[$normal_label] = $key;
- }
- }
-
- return $label_map;
- }
-
-
- private function parseCommitMessage($corpus, array $label_map) {
- $key_title = id(new DifferentialTitleField())->getFieldKeyForConduit();
- $key_summary = id(new DifferentialSummaryField())->getFieldKeyForConduit();
-
- $parser = id(new DifferentialCommitMessageParser())
- ->setLabelMap($label_map)
- ->setTitleKey($key_title)
- ->setSummaryKey($key_summary);
-
+ private function parseCommitMessage($corpus) {
+ $viewer = $this->getViewer();
+ $parser = DifferentialCommitMessageParser::newStandardParser($viewer);
$result = $parser->parseCorpus($corpus);
+ $this->errors = array();
foreach ($parser->getErrors() as $error) {
$this->errors[] = $error;
}
return $result;
}
}
diff --git a/src/applications/differential/customfield/DifferentialCoreCustomField.php b/src/applications/differential/customfield/DifferentialCoreCustomField.php
index 87d18553ad..7b6d02276d 100644
--- a/src/applications/differential/customfield/DifferentialCoreCustomField.php
+++ b/src/applications/differential/customfield/DifferentialCoreCustomField.php
@@ -1,133 +1,176 @@
<?php
/**
* Base class for Differential fields with storage on the revision object
* itself. This mostly wraps reading/writing field values to and from the
* object.
*/
abstract class DifferentialCoreCustomField
extends DifferentialCustomField {
private $value;
private $fieldError;
+ private $fieldParser;
abstract protected function readValueFromRevision(
DifferentialRevision $revision);
protected function writeValueToRevision(
DifferentialRevision $revision,
$value) {
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
protected function isCoreFieldRequired() {
return false;
}
protected function isCoreFieldValueEmpty($value) {
if (is_array($value)) {
return !$value;
}
return !strlen(trim($value));
}
protected function getCoreFieldRequiredErrorString() {
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
$this->setFieldError(null);
$errors = parent::validateApplicationTransactions(
$editor,
$type,
$xactions);
$transaction = null;
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if ($this->isCoreFieldRequired()) {
if ($this->isCoreFieldValueEmpty($value)) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
$this->getCoreFieldRequiredErrorString(),
$xaction);
$error->setIsMissingFieldError(true);
$errors[] = $error;
$this->setFieldError(pht('Required'));
+ continue;
+ }
+ }
+
+ if (is_string($value)) {
+ $parser = $this->getFieldParser();
+ $result = $parser->parseCorpus($value);
+
+ unset($result['__title__']);
+ unset($result['__summary__']);
+
+ if ($result) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Invalid'),
+ pht(
+ 'The value you have entered in "%s" can not be parsed '.
+ 'unambiguously when rendered in a commit message. Edit the '.
+ 'message so that keywords like "Summary:" and "Test Plan:" do '.
+ 'not appear at the beginning of lines. Parsed keys: %s.',
+ $this->getFieldName(),
+ implode(', ', array_keys($result))),
+ $xaction);
+ $errors[] = $error;
+ $this->setFieldError(pht('Invalid'));
+ continue;
}
}
}
return $errors;
}
+ private function getFieldParser() {
+ if (!$this->fieldParser) {
+ $viewer = $this->getViewer();
+ $parser = DifferentialCommitMessageParser::newStandardParser($viewer);
+
+ // Set custom title and summary keys so we can detect the presence of
+ // "Summary:" in, e.g., a test plan.
+ $parser->setTitleKey('__title__');
+ $parser->setSummaryKey('__summary__');
+
+ $this->fieldParser = $parser;
+ }
+
+ return $this->fieldParser;
+ }
+
public function canDisableField() {
return false;
}
public function shouldAppearInApplicationTransactions() {
return true;
}
public function shouldAppearInEditView() {
return true;
}
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
if ($this->isCoreFieldRequired()) {
$this->setFieldError(true);
}
$this->setValue($this->readValueFromRevision($object));
}
public function getOldValueForApplicationTransactions() {
return $this->readValueFromRevision($this->getObject());
}
public function getNewValueForApplicationTransactions() {
return $this->getValue();
}
public function applyApplicationTransactionInternalEffects(
PhabricatorApplicationTransaction $xaction) {
$this->writeValueToRevision($this->getObject(), $xaction->getNewValue());
}
public function setFieldError($field_error) {
$this->fieldError = $field_error;
return $this;
}
public function getFieldError() {
return $this->fieldError;
}
public function setValue($value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
public function readValueFromCommitMessage($value) {
$this->setValue($value);
return $this;
}
public function renderCommitMessageValue(array $handles) {
return $this->getValue();
}
public function getConduitDictionaryValue() {
return $this->getValue();
}
}
diff --git a/src/applications/differential/parser/DifferentialCommitMessageParser.php b/src/applications/differential/parser/DifferentialCommitMessageParser.php
index fda7ae05b6..ff76b30e51 100644
--- a/src/applications/differential/parser/DifferentialCommitMessageParser.php
+++ b/src/applications/differential/parser/DifferentialCommitMessageParser.php
@@ -1,216 +1,255 @@
<?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 extends Phobject {
private $labelMap;
private $titleKey;
private $summaryKey;
private $errors;
+ public static function newStandardParser(PhabricatorUser $viewer) {
+
+ $key_title = id(new DifferentialTitleField())->getFieldKeyForConduit();
+ $key_summary = id(new DifferentialSummaryField())->getFieldKeyForConduit();
+
+ $field_list = PhabricatorCustomField::getObjectFields(
+ new DifferentialRevision(),
+ DifferentialCustomField::ROLE_COMMITMESSAGE);
+ $field_list->setViewer($viewer);
+
+ $label_map = array();
+
+ foreach ($field_list->getFields() as $field) {
+ $labels = $field->getCommitMessageLabels();
+ $key = $field->getFieldKeyForConduit();
+
+ foreach ($labels as $label) {
+ $normal_label = self::normalizeFieldLabel(
+ $label);
+ if (!empty($label_map[$normal_label])) {
+ throw new Exception(
+ pht(
+ 'Field label "%s" is parsed by two custom fields: "%s" and '.
+ '"%s". Each label must be parsed by only one field.',
+ $label,
+ $key,
+ $label_map[$normal_label]));
+ }
+ $label_map[$normal_label] = $key;
+ }
+ }
+
+ return id(new self())
+ ->setLabelMap($label_map)
+ ->setTitleKey($key_title)
+ ->setSummaryKey($key_summary);
+ }
+
+
/* -( 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 %s, %s and %s to be set before parsing a corpus.',
'labelMap',
'summaryKey',
'titleKey'));
}
$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())
->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;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 6, 4:37 AM (12 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
321599
Default Alt Text
(17 KB)

Event Timeline