mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 16:22:43 +01:00
differential.parsecommitmessage
Test plan: meta
This commit is contained in:
parent
99aee37866
commit
787eb778b1
8 changed files with 575 additions and 0 deletions
11
resources/git/commit-template.txt
Normal file
11
resources/git/commit-template.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
<<Enter Revision Title>>
|
||||
|
||||
Summary:
|
||||
|
||||
Test Plan:
|
||||
|
||||
Reviewers:
|
||||
|
||||
CC:
|
||||
|
||||
###############################################################################
|
|
@ -65,6 +65,7 @@ phutil_register_library_map(array(
|
|||
'ConduitAPIRequest' => 'applications/conduit/protocol/request',
|
||||
'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect',
|
||||
'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_file_upload_Method' => 'applications/conduit/method/file/upload',
|
||||
'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find',
|
||||
|
@ -94,6 +95,8 @@ 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',
|
||||
'DifferentialController' => 'applications/differential/controller/base',
|
||||
'DifferentialDAO' => 'applications/differential/storage/base',
|
||||
'DifferentialDiff' => 'applications/differential/storage/diff',
|
||||
|
@ -268,6 +271,7 @@ phutil_register_library_map(array(
|
|||
'CelerityResourceController' => 'AphrontController',
|
||||
'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_user_find_Method' => 'ConduitAPIMethod',
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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');
|
432
src/applications/differential/parser/commitmessage/DifferentialCommitMessage.php
Executable file
432
src/applications/differential/parser/commitmessage/DifferentialCommitMessage.php
Executable 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('DifferentialCommitMessageParserException.php');
|
Loading…
Reference in a new issue