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

differential.parsecommitmessage

Test plan: meta
This commit is contained in:
epriestley 2011-02-06 13:42:00 -08:00
parent 99aee37866
commit 787eb778b1
8 changed files with 575 additions and 0 deletions

View file

@ -0,0 +1,11 @@
<<Enter Revision Title>>
Summary:
Test Plan:
Reviewers:
CC:
###############################################################################

View file

@ -65,6 +65,7 @@ phutil_register_library_map(array(
'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPIRequest' => 'applications/conduit/protocol/request',
'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect',
'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff',
'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage',
'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty',
'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload',
'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find',
@ -94,6 +95,8 @@ phutil_register_library_map(array(
'DifferentialCommentMail' => 'applications/differential/mail/comment', 'DifferentialCommentMail' => 'applications/differential/mail/comment',
'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview', 'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview',
'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave',
'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage',
'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception',
'DifferentialController' => 'applications/differential/controller/base', 'DifferentialController' => 'applications/differential/controller/base',
'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDAO' => 'applications/differential/storage/base',
'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiff' => 'applications/differential/storage/diff',
@ -268,6 +271,7 @@ phutil_register_library_map(array(
'CelerityResourceController' => 'AphrontController', 'CelerityResourceController' => 'AphrontController',
'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod',
'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod',
'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod',

View file

@ -0,0 +1,68 @@
<?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 ConduitAPI_differential_parsecommitmessage_Method
extends ConduitAPIMethod {
public function getMethodDescription() {
return "Parse commit messages for Differential fields.";
}
public function defineParamTypes() {
return array(
'corpus' => 'required string',
);
}
public function defineReturnType() {
return 'nonempty dict';
}
public function defineErrorTypes() {
return array(
);
}
protected function execute(ConduitAPIRequest $request) {
$corpus = $request->getValue('corpus');
try {
$message = DifferentialCommitMessage::newFromRawCorpus($corpus);
} catch (DifferentialCommitMessageParserException $ex) {
return array(
'error' => $ex->getMessage(),
);
}
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(),
),
);
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/conduit/method/base');
phutil_require_module('phabricator', 'applications/differential/parser/commitmessage');
phutil_require_source('ConduitAPI_differential_parsecommitmessage_Method.php');

View file

@ -0,0 +1,432 @@
<?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 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 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;
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);
} else {
$users = array();
}
if ($need_mail) {
$mail = id(new PhabricatorMetaMTAMailingList())->loadAllWhere(
'email in (%Ls)',
$need_mail);
} 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': ".$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': ".$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': ".$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',
'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 = '/^(?<field>'.$field_names.'):(?<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':
$data = preg_replace('/\s+/', ' ', $data);
break;
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]);
}
}
if (isset($fields['Reviewers']) && isset($fields['Reviewed By'])) {
$this->fail(
null,
"Commit message contains both 'Reviewers:' and 'Reviewed By:' ".
"fields.");
}
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

@ -0,0 +1,16 @@
<?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');

View file

@ -0,0 +1,21 @@
<?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 DifferentialCommitMessageParserException extends Exception {
}

View file

@ -0,0 +1,10 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_source('DifferentialCommitMessageParserException.php');