1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-19 03:50:54 +01:00

Drive Differential commit message parsing through extensible fields

Summary:
I think this is the last major step -- use the fields to parse commit messages,
not a hard-coded list of stuff. This adds two primary methods to fields, one to
get all the labels they'll parse (so we can do "CC" and "CCs" and treat them as
the same field) and one to parse the string into a canonical representation
(e.g., lookup reviewers and such).

You'll need to impelement the one block of task-specific stuff I removed in
Facebook's task field:

  list($pre_comment) = split(' -- ', $data);
  $data = array_filter(preg_split('/[^\d]+/', $pre_comment));
  foreach ($data as $k => $v) {
    $data[$k] = (int)$v;
  }
  $data = array_unique($data);
  break;

Otherwise I think this is clean.

Test Plan:
  - Called the conduit method with various commit messages, parsed fields/errors
seemed correct.
  - "arc diff"'d this diff onto localhost, then updated it.
  - "arc amend"'d this diff.

Reviewers: jungejason, tuomaspelkonen, aran

Reviewed By: jungejason

CC: aran, jungejason, epriestley

Differential Revision: 829
This commit is contained in:
epriestley 2011-08-18 12:08:18 -07:00
parent 735120b842
commit 0be3db03ee
19 changed files with 322 additions and 487 deletions

View file

@ -153,8 +153,6 @@ phutil_register_library_map(array(
'DifferentialCommentMail' => 'applications/differential/mail/comment',
'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview',
'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave',
'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage',
'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception',
'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/commits',
'DifferentialController' => 'applications/differential/controller/base',
'DifferentialDAO' => 'applications/differential/storage/base',
@ -169,6 +167,7 @@ phutil_register_library_map(array(
'DifferentialExceptionMail' => 'applications/differential/mail/exception',
'DifferentialExportPatchFieldSpecification' => 'applications/differential/field/specification/exportpatch',
'DifferentialFieldDataNotAvailableException' => 'applications/differential/field/exception/notavailable',
'DifferentialFieldParseException' => 'applications/differential/field/exception/parse',
'DifferentialFieldSelector' => 'applications/differential/field/selector/base',
'DifferentialFieldSpecification' => 'applications/differential/field/specification/base',
'DifferentialFieldSpecificationIncompleteException' => 'applications/differential/field/exception/incomplete',

View file

@ -44,29 +44,132 @@ class ConduitAPI_differential_parsecommitmessage_Method
protected function execute(ConduitAPIRequest $request) {
$corpus = $request->getValue('corpus');
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
if (!$aux_field->shouldAppearOnCommitMessage()) {
unset($aux_fields[$key]);
}
}
$aux_fields = mpull($aux_fields, null, 'getCommitMessageKey');
// Build a map from labels (like "Test Plan") to field keys
// (like "testPlan").
$label_map = $this->buildLabelMap($aux_fields);
$field_map = $this->parseCommitMessage($corpus, $label_map);
$fields = array();
$errors = array();
foreach ($field_map as $field_key => $field_value) {
$field = $aux_fields[$field_key];
try {
$message = DifferentialCommitMessage::newFromRawCorpus($corpus);
} catch (DifferentialCommitMessageParserException $ex) {
$fields[$field_key] = $field->parseValueFromCommitMessage($field_value);
} catch (DifferentialFieldParseException $ex) {
$field_label = $field->renderLabelForCommitMessage();
$errors[] = "Error parsing field '{$field_label}': ".$ex->getMessage();
}
}
// TODO: This is for backcompat only, remove once Arcanist gets updated.
$error = head($errors);
return array(
'error' => $ex->getMessage(),
'error' => $error,
'errors' => $errors,
'fields' => $fields,
);
}
return array(
'error' => null,
'fields' => array(
'title' => $message->getTitle(),
'summary' => $message->getSummary(),
'testPlan' => $message->getTestPlan(),
'blameRevision' => $message->getBlameRevision(),
'revertPlan' => $message->getRevertPlan(),
'reviewerPHIDs' => $message->getReviewerPHIDs(),
'reviewedByPHIDs' => $message->getReviewedByPHIDs(),
'ccPHIDs' => $message->getCCPHIDs(),
'revisionID' => $message->getRevisionID(),
'gitSVNID' => $message->getGitSVNID(),
'tasks' => $message->getTasks(),
),
);
private function buildLabelMap(array $aux_fields) {
$label_map = array();
foreach ($aux_fields as $key => $aux_field) {
$labels = $aux_field->getSupportedCommitMessageLabels();
foreach ($labels as $label) {
$normal_label = strtolower($label);
if (!empty($label_map[$normal_label])) {
$previous = $label_map[$normal_label];
throw new Exception(
"Field label '{$label}' is parsed by two fields: '{$key}' and ".
"'{$previous}'. Each label must be parsed by only one field.");
}
$label_map[$normal_label] = $key;
}
}
return $label_map;
}
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;
}
private function parseCommitMessage($corpus, array $label_map) {
$label_regexp = $this->buildLabelRegexp($label_map);
// Note, deliberately not populating $seen with 'title' because it is
// optional to include the 'Title:' label. We're doing a little special
// casing to consume the first line as the title regardless of whether you
// label it as such or not.
$field = '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[strtolower($match['field'])];
if (!empty($seen[$field])) {
throw new Exception(
"Field '{$field}' occurs twice in commit message!");
}
$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['title']) && empty($fields['summary'])) {
$lines = $fields['title'];
for ($ii = 0; $ii < count($lines); $ii++) {
if (strlen(trim($lines[$ii])) == 0) {
break;
}
}
if ($ii != count($lines)) {
$fields['title'] = array_slice($lines, 0, $ii);
$fields['summary'] = array_slice($lines, $ii);
}
}
// 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;
}
return $fields;
}
}

View file

@ -7,7 +7,9 @@
phutil_require_module('phabricator', 'applications/conduit/method/base');
phutil_require_module('phabricator', 'applications/differential/parser/commitmessage');
phutil_require_module('phabricator', 'applications/differential/field/selector/base');
phutil_require_module('phutil', 'utils');
phutil_require_source('ConduitAPI_differential_parsecommitmessage_Method.php');

View file

@ -16,6 +16,6 @@
* limitations under the License.
*/
class DifferentialCommitMessageParserException extends Exception {
final class DifferentialFieldParseException extends Exception {
}

View file

@ -7,4 +7,4 @@
phutil_require_source('DifferentialCommitMessageParserException.php');
phutil_require_source('DifferentialFieldParseException.php');

View file

@ -337,6 +337,9 @@ abstract class DifferentialFieldSpecification {
* them) or discarded (if your field implements neither, e.g. is just a
* display field).
*
* The value you receive will either be null or something you originally
* returned from @{method:parseValueFromCommitMessage}.
*
* You must implement this method if you return true from
* @{method:shouldAppearOnCommitMessage}.
*
@ -413,6 +416,48 @@ abstract class DifferentialFieldSpecification {
throw new DifferentialFieldSpecificationIncompleteException($this);
}
/**
* Return one or more labels which this field parses in commit messages. For
* example, you might parse all of "Task", "Tasks" and "Task Numbers" or
* similar. This is just to make it easier to get commit messages to parse
* when users are typing in the fields manually as opposed to using a
* template, by accepting alternate spellings / pluralizations / etc. By
* default, only the label returned from @{method:renderLabelForCommitMessage}
* is parsed.
*
* @return list List of supported labels that this field can parse from commit
* messages.
* @task commit
*/
public function getSupportedCommitMessageLabels() {
return array($this->renderLabelForCommitMessage());
}
/**
* Parse a raw text block from a commit message into a canonical
* representation of the field value. For example, the "CC" field accepts a
* comma-delimited list of usernames and emails and parses them into valid
* PHIDs, emitting a PHID list.
*
* If you encounter errors (like a nonexistent username) while parsing,
* you should throw a @{class:DifferentialFieldParseException}.
*
* Generally, this method should accept whatever you return from
* @{method:renderValueForCommitMessage} and parse it back into a sensible
* representation.
*
* You must implement this method if you return true from
* @{method:shouldAppearOnCommitMessage}.
*
* @param string
* @return mixed The canonical representation of the field value. For example,
* you should lookup usernames and object references.
* @task commit
*/
public function parseValueFromCommitMessage($value) {
throw new DifferentialFieldSpecificationIncompleteException($this);
}
/* -( Loading Additional Data )-------------------------------------------- */
@ -482,7 +527,6 @@ abstract class DifferentialFieldSpecification {
return $this->getRequiredHandlePHIDs();
}
/**
* Specify which diff properties this field needs to load.
*
@ -493,6 +537,82 @@ abstract class DifferentialFieldSpecification {
return array();
}
/**
* Parse a list of users into a canonical PHID list.
*
* @param string Raw list of comma-separated user names.
* @return list List of corresponding PHIDs.
* @task load
*/
protected function parseCommitMessageUserList($value) {
return $this->parseCommitMessageObjectList($value, $mailables = false);
}
/**
* Parse a list of mailable objects into a canonical PHID list.
*
* @param string Raw list of comma-separated mailable names.
* @return list List of corresponding PHIDs.
* @task load
*/
protected function parseCommitMessageMailableList($value) {
return $this->parseCommitMessageObjectList($value, $mailables = true);
}
/**
* Parse and lookup a list of object names, converting them to PHIDs.
*
* @param string Raw list of comma-separated object names.
* @return list List of corresponding PHIDs.
* @task load
*/
private function parseCommitMessageObjectList($value, $include_mailables) {
$value = array_unique(array_filter(preg_split('/[\s,]+/', $value)));
if (!$value) {
return array();
}
$object_map = array();
$users = id(new PhabricatorUser())->loadAllWhere(
'(username IN (%Ls)) OR (email IN (%Ls))',
$value,
$value);
$object_map += mpull($users, 'getPHID', 'getUsername');
$object_map += mpull($users, 'getPHID', 'getEmail');
if ($include_mailables) {
$mailables = id(new PhabricatorMetaMTAMailingList())->loadAllWhere(
'(email IN (%Ls)) OR (name IN (%Ls))',
$value,
$value);
$object_map += mpull($mailables, 'getPHID', 'getName');
$object_map += mpull($mailables, 'getPHID', 'getEmail');
}
$invalid = array();
$results = array();
foreach ($value as $name) {
if (empty($object_map[$name])) {
$invalid[] = $name;
} else {
$results[] = $object_map[$name];
}
}
if ($invalid) {
$invalid = implode(', ', $invalid);
$what = $include_mailables
? "users and mailing lists"
: "users";
throw new DifferentialFieldParseException(
"Commit message references nonexistent {$what}: {$invalid}.");
}
return array_unique($results);
}
/* -( Contextual Data )---------------------------------------------------- */

View file

@ -8,6 +8,11 @@
phutil_require_module('phabricator', 'applications/differential/field/exception/incomplete');
phutil_require_module('phabricator', 'applications/differential/field/exception/notavailable');
phutil_require_module('phabricator', 'applications/differential/field/exception/parse');
phutil_require_module('phabricator', 'applications/metamta/storage/mailinglist');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phutil', 'utils');
phutil_require_source('DifferentialFieldSpecification.php');

View file

@ -103,4 +103,15 @@ final class DifferentialBlameRevisionFieldSpecification
return $this->value;
}
public function getSupportedCommitMessageLabels() {
return array(
'Blame Revision',
'Blame Rev',
);
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}

View file

@ -118,4 +118,15 @@ final class DifferentialCCsFieldSpecification
return implode(', ', $names);
}
public function getSupportedCommitMessageLabels() {
return array(
'CC',
'CCs',
);
}
public function parseValueFromCommitMessage($value) {
return $this->parseCommitMessageMailableList($value);
}
}

View file

@ -46,4 +46,8 @@ final class DifferentialGitSVNIDFieldSpecification
return null;
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}

View file

@ -99,8 +99,20 @@ final class DifferentialRevertPlanFieldSpecification
return 'Revert Plan';
}
public function renderValueForCommitMessage($is_edit) {
return $this->value;
}
public function getSupportedCommitMessageLabels() {
return array(
'Revert Plan',
'Revert',
);
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}

View file

@ -89,4 +89,8 @@ final class DifferentialReviewedByFieldSpecification
return implode(', ', $names);
}
public function parseValueFromCommitMessage($value) {
return $this->parseCommitMessageUserList($value);
}
}

View file

@ -129,4 +129,15 @@ final class DifferentialReviewersFieldSpecification
return implode(', ', $names);
}
public function getSupportedCommitMessageLabels() {
return array(
'Reviewer',
'Reviewers',
);
}
public function parseValueFromCommitMessage($value) {
return $this->parseCommitMessageUserList($value);
}
}

View file

@ -46,5 +46,8 @@ final class DifferentialRevisionIDFieldSpecification
return $this->id;
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}

View file

@ -70,4 +70,8 @@ final class DifferentialSummaryFieldSpecification
return $this->summary;
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}

View file

@ -81,5 +81,8 @@ final class DifferentialTestPlanFieldSpecification
return $this->plan;
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}

View file

@ -82,4 +82,8 @@ final class DifferentialTitleFieldSpecification
return $this->title;
}
public function parseValueFromCommitMessage($value) {
return preg_replace('/\s*\n\s*/', ' ', $value);
}
}

View file

@ -1,445 +0,0 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DifferentialCommitMessage {
protected $rawCorpus;
protected $title;
protected $summary;
protected $testPlan;
protected $blameRevision;
protected $revertPlan;
protected $reviewerNames = array();
protected $reviewerPHIDs;
protected $reviewedByNames = array();
protected $reviewedByPHIDs;
protected $ccNames = array();
protected $ccPHIDs;
protected $revisionID;
protected $gitSVNID;
protected $tasks = array();
protected function __construct() {
}
public function getReviewerPHIDs() {
return $this->reviewerPHIDs;
}
public function getReviewedByPHIDs() {
return $this->reviewedByPHIDs;
}
public function getCCPHIDs() {
return $this->ccPHIDs;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setRevisionID($revision_id) {
$this->revisionID = $revision_id;
return $this;
}
public function getRevisionID() {
return $this->revisionID;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
return $this->summary;
}
public function setTestPlan($test_plan) {
$this->testPlan = $test_plan;
return $this;
}
public function getTestPlan() {
return $this->testPlan;
}
public function setBlameRevision($blame_revision) {
$this->blameRevision = $blame_revision;
return $this;
}
public function getBlameRevision() {
return $this->blameRevision;
}
public function setRevertPlan($revert_plan) {
$this->revertPlan = $revert_plan;
return $this;
}
public function getRevertPlan() {
return $this->revertPlan;
}
public function setReviewerNames($reviewer_names) {
$this->reviewerNames = $reviewer_names;
return $this;
}
public function getReviewerNames() {
return $this->reviewerNames;
}
public function setCCNames($cc_names) {
$this->ccNames = $cc_names;
return $this;
}
public function getCCNames() {
return $this->ccNames;
}
public function setReviewedByNames($reviewed_by_names) {
$this->reviewedByNames = $reviewed_by_names;
return $this;
}
public function getReviewedByNames() {
return $this->reviewedByNames;
}
public function setGitSVNID($git_svn_id) {
$this->gitSVNID = $git_svn_id;
return $this;
}
public function getGitSVNID() {
return $this->gitSVNID;
}
public function setReviewerPHIDs(array $phids) {
$this->reviewerPHIDs = $phids;
return $this;
}
public function setReviewedByPHIDs(array $phids) {
$this->reviewedByPHIDs = $phids;
return $this;
}
public function setCCPHIDs(array $phids) {
$this->ccPHIDs = $phids;
return $this;
}
public function setTasks(array $tasks) {
$this->tasks = $tasks;
return $this;
}
public function getTasks() {
return $this->tasks;
}
public static function newFromRawCorpus($raw_corpus) {
$message = new DifferentialCommitMessage();
$message->setRawCorpus($raw_corpus);
$fields = $message->parseFields($raw_corpus);
foreach ($fields as $field => $data) {
switch ($field) {
case 'Title':
$message->setTitle($data);
break;
case 'Differential Revision':
$message->setRevisionID($data);
break;
case 'Summary':
$message->setSummary($data);
break;
case 'Test Plan':
$message->setTestPlan($data);
break;
case 'Blame Revision':
$message->setBlameRevision($data);
break;
case 'Revert Plan';
$message->setRevertPlan($data);
break;
case 'Reviewers':
$message->setReviewerNames($data);
break;
case 'Reviewed By':
$message->setReviewedByNames($data);
break;
case 'CC':
$message->setCCNames($data);
break;
case 'git-svn-id':
$message->setGitSVNID($data);
break;
case 'Commenters':
// Just drop this.
break;
case 'Tasks':
$message->setTasks($data);
break;
default:
throw new Exception("Unrecognized field '{$field}'.");
}
}
$need_users = array_merge(
$message->getReviewerNames(),
$message->getReviewedByNames(),
$message->getCCNames());
$need_mail = $message->getCCNames();
if ($need_users) {
$users = id(new PhabricatorUser())->loadAllWhere(
'(username IN (%Ls)) OR (email IN (%Ls))',
$need_users,
$need_users);
$users = mpull($users, 'getPHID', 'getUsername') +
mpull($users, 'getPHID', 'getEmail');
} else {
$users = array();
}
if ($need_mail) {
$mail = id(new PhabricatorMetaMTAMailingList())->loadAllWhere(
'(email in (%Ls)) OR (name IN (%Ls))',
$need_mail,
$need_mail);
$mail = mpull($mail, 'getPHID', 'getName') +
mpull($mail, 'getPHID', 'getEmail');
} else {
$mail = array();
}
$reviewer_phids = array();
foreach ($message->getReviewerNames() as $name) {
$phid = idx($users, $name);
if (!$phid) {
throw new DifferentialCommitMessageParserException(
"Commit message references nonexistent 'Reviewer' value '".$name."'");
}
$reviewer_phids[] = $phid;
}
$message->setReviewerPHIDs($reviewer_phids);
$reviewed_by_phids = array();
foreach ($message->getReviewedByNames() as $name) {
$phid = idx($users, $name);
if (!$phid) {
throw new DifferentialCommitMessageParserException(
"Commit message references nonexistent 'Reviewed by' value '".
$name."'");
}
$reviewed_by_phids[] = $phid;
}
$message->setReviewedByPHIDs($reviewed_by_phids);
$cc_phids = array();
foreach ($message->getCCNames() as $name) {
$phid = idx($users, $name);
if (!$phid) {
$phid = idx($mail, $name);
}
if (!$phid) {
throw new DifferentialCommitMessageParserException(
"Commit message references nonexistent 'CC' value '".$name."'");
}
$cc_phids[] = $phid;
}
$message->setCCPHIDs($cc_phids);
return $message;
}
public function setRawCorpus($raw_corpus) {
$this->rawCorpus = $raw_corpus;
return $this;
}
public function getRawCorpus() {
return $this->rawCorpus;
}
protected function parseFields($message) {
$field_spec = array(
'Differential Revision' => 'Differential Revision',
'Title' => 'Title',
'Summary' => 'Summary',
'Test Plan' => 'Test Plan',
'Blame Rev' => 'Blame Revision',
'Blame Revision' => 'Blame Revision',
'Reviewed By' => 'Reviewed By',
'Reviewers' => 'Reviewers',
'CC' => 'CC',
'Revert' => 'Revert Plan',
'Revert Plan' => 'Revert Plan',
'Task ID' => 'Tasks',
'Task IDs' => 'Tasks',
'Tasks' => 'Tasks',
'git-svn-id' => 'git-svn-id',
// This appears only in "arc amend"-ed messages, just discard it.
'Commenters' => 'Commenters',
);
$field_names = array_keys($field_spec);
foreach ($field_names as $key => $name) {
$field_names[$key] = preg_quote($name, '/');
}
$field_names = implode('|', $field_names);
$field_pattern = '/^(?P<field>'.$field_names.'):(?P<text>.*)$/i';
foreach ($field_spec as $key => $value) {
$field_spec[strtolower($key)] = $value;
}
$message = trim($message);
$lines = explode("\n", $message);
$this->rawInput = $lines;
if (!$message) {
$this->fail(
null,
"Your commit message is empty.");
}
$field = 'Title';
// Note, deliberately not populating $seen with 'Title' because it is
// optional to include the 'Title:' header.
$seen = array();
$field_map = array();
foreach ($lines as $key => $line) {
$match = null;
if (preg_match($field_pattern, $line, $match)) {
$lines[$key] = trim($match['text']);
$field = $field_spec[strtolower($match['field'])];
if (!empty($seen[$field])) {
$this->fail(
$key,
"Field '{$field}' occurs twice in commit message.");
}
$seen[$field] = true;
}
$field_map[$key] = $field;
}
$fields = array();
foreach ($lines as $key => $line) {
$fields[$field_map[$key]][] = $line;
}
foreach ($fields as $name => $lines) {
if ($name == 'Title') {
// If the user enters a title and then a blank line without a summary,
// treat the first line as the title and the rest as the summary.
if (!isset($fields['Summary'])) {
$ii = 0;
for ($ii = 0; $ii < count($lines); $ii++) {
if (strlen(trim($lines[$ii])) == 0) {
break;
}
}
if ($ii != count($lines)) {
$fields['Title'] = array_slice($lines, 0, $ii);
$fields['Summary'] = array_slice($lines, $ii);
}
}
}
}
foreach ($fields as $name => $lines) {
$data = rtrim(implode("\n", $lines));
$data = ltrim($data, "\n");
switch ($name) {
case 'Title':
$data = preg_replace('/\s*\n\s*/', ' ', $data);
break;
case 'Tasks':
list($pre_comment) = split(' -- ', $data);
$data = array_filter(preg_split('/[^\d]+/', $pre_comment));
foreach ($data as $k => $v) {
$data[$k] = (int)$v;
}
$data = array_unique($data);
break;
case 'Blame Revision':
case 'Differential Revision':
$data = (int)preg_replace('/[^\d]/', '', $data);
break;
case 'CC':
case 'Reviewers':
case 'Reviewed By':
$data = array_filter(preg_split('/[\s,]+/', $data));
break;
}
if (is_array($data)) {
$data = array_values($data);
}
if ($data) {
$fields[$name] = $data;
} else {
unset($fields[$name]);
}
}
return $fields;
}
protected function fail($line, $reason) {
if ($line !== null) {
$lines = $this->rawInput;
$min = max(0, $line - 3);
$max = min(count($lines) - 1, $line + 3);
$reason .= "\n\n";
$len = strlen($max);
for ($ii = $min; $ii <= $max; $ii++) {
$reason .= sprintf(
"%8.8s % {$len}d %s\n",
$ii == $line ? '>>>' : '',
$ii + 1,
$lines[$ii]);
}
}
throw new DifferentialCommitMessageParserException($reason);
}
}

View file

@ -1,16 +0,0 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/differential/parser/commitmessage/exception');
phutil_require_module('phabricator', 'applications/metamta/storage/mailinglist');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phutil', 'utils');
phutil_require_source('DifferentialCommitMessage.php');