@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Perform commit message parsing and construction with new CustomFields

Summary: Ref T2222. Ref T3886. Converts parsing and construction of commit messages to be driven by CustomField.

Test Plan:
This is a huge, messy change. I've made an effort to test it exhasutively, but suspect I probably missed a few behaviors. Roughly:

- Enumerted all current fields (fields implementing `shouldAppearOnCommitMessage()`) and tried to test them one by one.
- Used `arc diff --edit` repeatedly to manipulate each field (this workflow hits both the parse and construct steps).
- Used `arc amend --show` to examine construct output (this does not activate the "edit" mode).

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3886, T2222

Differential Revision: https://secure.phabricator.com/D8449

+918 -114
+10
src/__phutil_library_map__.php
··· 326 326 'DifferentialArcanistProjectFieldSpecification' => 'applications/differential/field/specification/DifferentialArcanistProjectFieldSpecification.php', 327 327 'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php', 328 328 'DifferentialAsanaRepresentationFieldSpecification' => 'applications/differential/field/specification/DifferentialAsanaRepresentationFieldSpecification.php', 329 + 'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php', 329 330 'DifferentialAuditorsFieldSpecification' => 'applications/differential/field/specification/DifferentialAuditorsFieldSpecification.php', 330 331 'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php', 331 332 'DifferentialAuthorFieldSpecification' => 'applications/differential/field/specification/DifferentialAuthorFieldSpecification.php', ··· 360 361 'DifferentialCommitMessageParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCommitMessageParserTestCase.php', 361 362 'DifferentialCommitsField' => 'applications/differential/customfield/DifferentialCommitsField.php', 362 363 'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/DifferentialCommitsFieldSpecification.php', 364 + 'DifferentialConflictsField' => 'applications/differential/customfield/DifferentialConflictsField.php', 363 365 'DifferentialConflictsFieldSpecification' => 'applications/differential/field/specification/DifferentialConflictsFieldSpecification.php', 364 366 'DifferentialController' => 'applications/differential/controller/DifferentialController.php', 365 367 'DifferentialCoreCustomField' => 'applications/differential/customfield/DifferentialCoreCustomField.php', ··· 403 405 'DifferentialFieldValidationException' => 'applications/differential/field/exception/DifferentialFieldValidationException.php', 404 406 'DifferentialFreeformFieldSpecification' => 'applications/differential/field/specification/DifferentialFreeformFieldSpecification.php', 405 407 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 408 + 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', 406 409 'DifferentialGitSVNIDFieldSpecification' => 'applications/differential/field/specification/DifferentialGitSVNIDFieldSpecification.php', 407 410 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 408 411 'DifferentialHostFieldSpecification' => 'applications/differential/field/specification/DifferentialHostFieldSpecification.php', ··· 454 457 'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php', 455 458 'DifferentialRevertPlanFieldSpecification' => 'applications/differential/field/specification/DifferentialRevertPlanFieldSpecification.php', 456 459 'DifferentialReviewRequestMail' => 'applications/differential/mail/DifferentialReviewRequestMail.php', 460 + 'DifferentialReviewedByField' => 'applications/differential/customfield/DifferentialReviewedByField.php', 457 461 'DifferentialReviewedByFieldSpecification' => 'applications/differential/field/specification/DifferentialReviewedByFieldSpecification.php', 458 462 'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php', 459 463 'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php', ··· 465 469 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 466 470 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 467 471 'DifferentialRevisionEditor' => 'applications/differential/editor/DifferentialRevisionEditor.php', 472 + 'DifferentialRevisionIDField' => 'applications/differential/customfield/DifferentialRevisionIDField.php', 468 473 'DifferentialRevisionIDFieldParserTestCase' => 'applications/differential/field/specification/__tests__/DifferentialRevisionIDFieldParserTestCase.php', 469 474 'DifferentialRevisionIDFieldSpecification' => 'applications/differential/field/specification/DifferentialRevisionIDFieldSpecification.php', 470 475 'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php', ··· 2898 2903 'DifferentialArcanistProjectFieldSpecification' => 'DifferentialFieldSpecification', 2899 2904 'DifferentialAsanaRepresentationField' => 'DifferentialCustomField', 2900 2905 'DifferentialAsanaRepresentationFieldSpecification' => 'DifferentialFieldSpecification', 2906 + 'DifferentialAuditorsField' => 'DifferentialStoredCustomField', 2901 2907 'DifferentialAuditorsFieldSpecification' => 'DifferentialFieldSpecification', 2902 2908 'DifferentialAuthorField' => 'DifferentialCustomField', 2903 2909 'DifferentialAuthorFieldSpecification' => 'DifferentialFieldSpecification', ··· 2926 2932 'DifferentialCommitMessageParserTestCase' => 'PhabricatorTestCase', 2927 2933 'DifferentialCommitsField' => 'DifferentialCustomField', 2928 2934 'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification', 2935 + 'DifferentialConflictsField' => 'DifferentialCustomField', 2929 2936 'DifferentialConflictsFieldSpecification' => 'DifferentialFieldSpecification', 2930 2937 'DifferentialController' => 'PhabricatorController', 2931 2938 'DifferentialCoreCustomField' => 'DifferentialCustomField', ··· 2971 2978 'DifferentialFieldSpecificationIncompleteException' => 'Exception', 2972 2979 'DifferentialFieldValidationException' => 'Exception', 2973 2980 'DifferentialFreeformFieldSpecification' => 'DifferentialFieldSpecification', 2981 + 'DifferentialGitSVNIDField' => 'DifferentialCustomField', 2974 2982 'DifferentialGitSVNIDFieldSpecification' => 'DifferentialFieldSpecification', 2975 2983 'DifferentialHostField' => 'DifferentialCustomField', 2976 2984 'DifferentialHostFieldSpecification' => 'DifferentialFieldSpecification', ··· 3017 3025 'DifferentialRevertPlanField' => 'DifferentialStoredCustomField', 3018 3026 'DifferentialRevertPlanFieldSpecification' => 'DifferentialFieldSpecification', 3019 3027 'DifferentialReviewRequestMail' => 'DifferentialMail', 3028 + 'DifferentialReviewedByField' => 'DifferentialCoreCustomField', 3020 3029 'DifferentialReviewedByFieldSpecification' => 'DifferentialFieldSpecification', 3021 3030 'DifferentialReviewersField' => 'DifferentialCoreCustomField', 3022 3031 'DifferentialReviewersFieldSpecification' => 'DifferentialFieldSpecification', ··· 3035 3044 'DifferentialRevisionDetailView' => 'AphrontView', 3036 3045 'DifferentialRevisionEditController' => 'DifferentialController', 3037 3046 'DifferentialRevisionEditor' => 'PhabricatorEditor', 3047 + 'DifferentialRevisionIDField' => 'DifferentialCustomField', 3038 3048 'DifferentialRevisionIDFieldParserTestCase' => 'PhabricatorTestCase', 3039 3049 'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification', 3040 3050 'DifferentialRevisionLandController' => 'DifferentialController',
+98 -67
src/applications/differential/conduit/ConduitAPI_differential_getcommitmessage_Method.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group conduit 5 - */ 6 3 final class ConduitAPI_differential_getcommitmessage_Method 7 4 extends ConduitAPIMethod { 8 5 ··· 36 33 $revision = id(new DifferentialRevisionQuery()) 37 34 ->withIDs(array($id)) 38 35 ->setViewer($viewer) 39 - ->needRelationships(true) 40 36 ->needReviewerStatus(true) 37 + ->needActiveDiffs(true) 41 38 ->executeOne(); 42 - 43 39 if (!$revision) { 44 40 throw new ConduitException('ERR_NOT_FOUND'); 45 41 } 46 42 } else { 47 43 $revision = DifferentialRevision::initializeNewRevision($viewer); 44 + $revision->attachReviewerStatus(array()); 45 + $revision->attachActiveDiff(null); 48 46 } 49 47 50 - 51 48 $is_edit = $request->getValue('edit'); 52 49 $is_create = ($is_edit == 'create'); 53 50 54 - $aux_fields = DifferentialFieldSelector::newSelector() 55 - ->getFieldSpecifications(); 56 - 57 - $pro_tips = array(); 51 + $field_list = PhabricatorCustomField::getObjectFields( 52 + $revision, 53 + ($is_edit 54 + ? DifferentialCustomField::ROLE_COMMITMESSAGEEDIT 55 + : DifferentialCustomField::ROLE_COMMITMESSAGE)); 58 56 59 - foreach ($aux_fields as $key => $aux_field) { 60 - $aux_field->setUser($viewer); 61 - $aux_field->setRevision($revision); 62 - $pro_tips[] = $aux_field->getCommitMessageTips(); 63 - if (!$aux_field->shouldAppearOnCommitMessage()) { 64 - unset($aux_fields[$key]); 65 - } 66 - } 57 + $field_list 58 + ->setViewer($viewer) 59 + ->readFieldsFromStorage($revision); 67 60 68 - $aux_fields = DifferentialAuxiliaryField::loadFromStorage( 69 - $revision, 70 - $aux_fields); 71 - $aux_fields = mpull($aux_fields, null, 'getCommitMessageKey'); 61 + $field_map = mpull($field_list->getFields(), null, 'getFieldKeyForConduit'); 72 62 73 63 if ($is_edit) { 74 - $fields = $request->getValue('fields'); 75 - if (!is_array($fields)) { 76 - $fields = array(); 77 - } 64 + $fields = $request->getValue('fields', array()); 78 65 foreach ($fields as $field => $value) { 79 - 80 - $aux_field = idx($aux_fields, $field); 81 - if (!$aux_field) { 66 + $custom_field = idx($field_map, $field); 67 + if (!$custom_field) { 82 68 throw new Exception( 83 - "Commit message includes field '{$field}' which does not ". 84 - "correspond to any configured field."); 69 + pht( 70 + 'Commit message includes field "%s", but this field does not '. 71 + 'correspond to a known field.', 72 + $field)); 85 73 } 86 - 87 74 if ($is_create || 88 - $aux_field->shouldOverwriteWhenCommitMessageIsEdited()) { 89 - $aux_field->setValueFromParsedCommitMessage($value); 75 + $custom_field->shouldOverwriteWhenCommitMessageIsEdited()) { 76 + $custom_field->readValueFromCommitMessage($value); 90 77 } 91 78 } 92 79 } 93 80 81 + $phids = array(); 82 + foreach ($field_list->getFields() as $key => $field) { 83 + $field_phids = $field->getRequiredHandlePHIDsForCommitMessage(); 84 + if (!is_array($field_phids)) { 85 + throw new Exception( 86 + pht( 87 + 'Custom field "%s" was expected to return an array of handle '. 88 + 'PHIDs required for commit message rendering, but returned "%s" '. 89 + 'instead.', 90 + $field->getFieldKey(), 91 + gettype($field_phids))); 92 + } 93 + $phids[$key] = $field_phids; 94 + } 94 95 95 - $aux_phids = array(); 96 - foreach ($aux_fields as $field_key => $field) { 97 - $aux_phids[$field_key] = $field->getRequiredHandlePHIDsForCommitMessage(); 98 - } 99 - $phids = array_unique(array_mergev($aux_phids)); 100 - $handles = id(new PhabricatorHandleQuery()) 101 - ->setViewer($request->getUser()) 102 - ->withPHIDs($phids) 103 - ->execute(); 104 - foreach ($aux_fields as $field_key => $field) { 105 - $field->setHandles(array_select_keys($handles, $aux_phids[$field_key])); 96 + $all_phids = array_mergev($phids); 97 + if ($all_phids) { 98 + $all_handles = id(new PhabricatorHandleQuery()) 99 + ->setViewer($viewer) 100 + ->withPHIDs($all_phids) 101 + ->execute(); 102 + } else { 103 + $all_handles = array(); 106 104 } 107 105 106 + $key_title = id(new DifferentialTitleField())->getFieldKey(); 107 + $default_title = DifferentialTitleField::getDefaultTitle(); 108 108 109 109 $commit_message = array(); 110 - foreach ($aux_fields as $field_key => $field) { 111 - $value = $field->renderValueForCommitMessage($is_edit); 112 - $label = $field->renderLabelForCommitMessage(); 110 + foreach ($field_list->getFields() as $key => $field) { 111 + $handles = array_select_keys($all_handles, $phids[$key]); 112 + 113 + $label = $field->renderCommitMessageLabel(); 114 + $value = $field->renderCommitMessageValue($handles); 115 + 116 + if (!is_string($value) && !is_null($value)) { 117 + throw new Exception( 118 + pht( 119 + 'Custom field "%s" was expected to render a string or null value, '. 120 + 'but rendered a "%s" instead.', 121 + $field->getFieldKey(), 122 + gettype($value))); 123 + } 124 + 125 + $is_title = ($key == $key_title); 126 + 113 127 if (!strlen($value)) { 114 - if ($field_key === 'title') { 115 - $commit_message[] = 116 - DifferentialTitleFieldSpecification::getDefaultRevisionTitle(); 128 + if ($is_title) { 129 + $commit_message[] = $default_title; 117 130 } else { 118 - if ($field->shouldAppearOnCommitMessageTemplate() && $is_edit) { 131 + if ($is_edit && $field->shouldAppearInCommitMessageTemplate()) { 119 132 $commit_message[] = $label.': '; 120 133 } 121 134 } 122 135 } else { 123 - if ($field_key === 'title') { 136 + if ($is_title) { 124 137 $commit_message[] = $value; 125 138 } else { 126 139 $value = str_replace( ··· 137 150 } 138 151 139 152 if ($is_edit) { 140 - $pro_tips = array_mergev($pro_tips); 153 + $tip = $this->getProTip($field_list); 154 + if ($tip !== null) { 155 + $commit_message[] = "\n".$tip; 156 + } 157 + } 141 158 142 - if (!empty($pro_tips)) { 143 - shuffle($pro_tips); 144 - $pro_tip = "Tip: ".$pro_tips[0]; 145 - $pro_tip = wordwrap($pro_tip, 78, "\n", true); 159 + $commit_message = implode("\n\n", $commit_message); 160 + 161 + return $commit_message; 162 + } 146 163 147 - $lines = explode("\n", $pro_tip); 164 + private function getProTip() { 165 + // Any field can provide tips, whether it normally appears on commit 166 + // messages or not. 167 + $field_list = PhabricatorCustomField::getObjectFields( 168 + new DifferentialRevision(), 169 + PhabricatorCustomField::ROLE_DEFAULT); 148 170 149 - foreach ($lines as $key => $line) { 150 - $lines[$key] = "# ".$line; 151 - } 171 + $tips = array(); 172 + foreach ($field_list->getFields() as $key => $field) { 173 + $tips[] = $field->getProTips(); 174 + } 175 + $tips = array_mergev($tips); 152 176 153 - $pro_tip = implode("\n", $lines); 154 - $commit_message[] = $pro_tip; 155 - } 177 + if (!$tips) { 178 + return null; 156 179 } 157 180 158 - $commit_message = implode("\n\n", $commit_message); 181 + shuffle($tips); 159 182 160 - return $commit_message; 183 + $tip = pht('Tip: %s', head($tips)); 184 + $tip = wordwrap($tip, 78, "\n", true); 185 + 186 + $lines = explode("\n", $tip); 187 + foreach ($lines as $key => $line) { 188 + $lines[$key] = "# ".$line; 189 + } 190 + 191 + return implode("\n", $lines); 161 192 } 162 193 163 194 }
+53 -44
src/applications/differential/conduit/ConduitAPI_differential_parsecommitmessage_Method.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group conduit 5 - */ 6 3 final class ConduitAPI_differential_parsecommitmessage_Method 7 4 extends ConduitAPIMethod { 8 5 9 6 private $errors; 10 7 11 8 public function getMethodDescription() { 12 - return "Parse commit messages for Differential fields."; 9 + return pht("Parse commit messages for Differential fields."); 13 10 } 14 11 15 12 public function defineParamTypes() { ··· 24 21 } 25 22 26 23 public function defineErrorTypes() { 27 - return array( 28 - ); 24 + return array(); 29 25 } 30 26 31 27 protected function execute(ConduitAPIRequest $request) { 28 + $viewer = $request->getUser(); 32 29 $corpus = $request->getValue('corpus'); 33 30 $is_partial = $request->getValue('partial'); 34 31 35 - $aux_fields = DifferentialFieldSelector::newSelector() 36 - ->getFieldSpecifications(); 32 + $revision = new DifferentialRevision(); 33 + 34 + $field_list = PhabricatorCustomField::getObjectFields( 35 + $revision, 36 + DifferentialCustomField::ROLE_COMMITMESSAGE); 37 + $field_list->setViewer($viewer); 38 + $field_map = mpull($field_list->getFields(), null, 'getFieldKeyForConduit'); 37 39 38 - foreach ($aux_fields as $key => $aux_field) { 39 - $aux_field->setUser($request->getUser()); 40 - if (!$aux_field->shouldAppearOnCommitMessage()) { 41 - unset($aux_fields[$key]); 42 - } 43 - } 40 + $this->errors = array(); 44 41 45 - $aux_fields = mpull($aux_fields, null, 'getCommitMessageKey'); 42 + $label_map = $this->buildLabelMap($field_list); 43 + $corpus_map = $this->parseCommitMessage($corpus, $label_map); 46 44 47 - $this->errors = array(); 45 + $values = array(); 46 + foreach ($corpus_map as $field_key => $text_value) { 47 + $field = idx($field_map, $field_key); 48 48 49 - // Build a map from labels (like "Test Plan") to field keys 50 - // (like "testPlan"). 51 - $label_map = $this->buildLabelMap($aux_fields); 52 - $field_map = $this->parseCommitMessage($corpus, $label_map); 49 + if (!$field) { 50 + throw new Exception( 51 + pht( 52 + 'Parser emitted text value for field key "%s", but no such '. 53 + 'field exists.', 54 + $field_key)); 55 + } 53 56 54 - $fields = array(); 55 - foreach ($field_map as $field_key => $field_value) { 56 - $field = $aux_fields[$field_key]; 57 57 try { 58 - $fields[$field_key] = $field->parseValueFromCommitMessage($field_value); 59 - $field->setValueFromParsedCommitMessage($fields[$field_key]); 58 + $values[$field_key] = $field->parseValueFromCommitMessage($text_value); 60 59 } catch (DifferentialFieldParseException $ex) { 61 - $field_label = $field->renderLabelForCommitMessage(); 62 - $this->errors[] = 63 - "Error parsing field '{$field_label}': ".$ex->getMessage(); 60 + $this->errors[] = pht( 61 + 'Error parsing field "%s": %s', 62 + $field->renderCommitMessageLabel(), 63 + $ex->getMessage()); 64 64 } 65 65 } 66 66 67 67 if (!$is_partial) { 68 - foreach ($aux_fields as $field_key => $aux_field) { 68 + foreach ($field_map as $key => $field) { 69 69 try { 70 - $aux_field->validateField(); 70 + $field->validateCommitMessageValue(idx($values, $key)); 71 71 } catch (DifferentialFieldValidationException $ex) { 72 - $field_label = $aux_field->renderLabelForCommitMessage(); 73 - $this->errors[] = 74 - "Invalid or missing field '{$field_label}': ". 75 - $ex->getMessage(); 72 + $this->errors[] = pht( 73 + 'Invalid or missing field "%s": %s', 74 + $field->renderCommitMessageLabel(), 75 + $ex->getMessage()); 76 76 } 77 77 } 78 78 } 79 79 80 80 return array( 81 81 'errors' => $this->errors, 82 - 'fields' => $fields, 82 + 'fields' => $values, 83 83 ); 84 84 } 85 85 86 - private function buildLabelMap(array $aux_fields) { 87 - assert_instances_of($aux_fields, 'DifferentialFieldSpecification'); 86 + private function buildLabelMap(PhabricatorCustomFieldList $field_list) { 88 87 $label_map = array(); 89 - foreach ($aux_fields as $key => $aux_field) { 90 - $labels = $aux_field->getSupportedCommitMessageLabels(); 88 + 89 + foreach ($field_list->getFields() as $key => $field) { 90 + $labels = $field->getCommitMessageLabels(); 91 + $key = $field->getFieldKeyForConduit(); 92 + 91 93 foreach ($labels as $label) { 92 94 $normal_label = DifferentialCommitMessageParser::normalizeFieldLabel( 93 95 $label); 94 96 if (!empty($label_map[$normal_label])) { 95 - $previous = $label_map[$normal_label]; 96 97 throw new Exception( 97 - "Field label '{$label}' is parsed by two fields: '{$key}' and ". 98 - "'{$previous}'. Each label must be parsed by only one field."); 98 + pht( 99 + 'Field label "%s" is parsed by two custom fields: "%s" and '. 100 + '"%s". Each label must be parsed by only one field.', 101 + $label, 102 + $key, 103 + $label_map[$normal_label])); 99 104 } 100 105 $label_map[$normal_label] = $key; 101 106 } 102 107 } 108 + 103 109 return $label_map; 104 110 } 105 111 106 112 107 113 private function parseCommitMessage($corpus, array $label_map) { 114 + $key_title = id(new DifferentialTitleField())->getFieldKeyForConduit(); 115 + $key_summary = id(new DifferentialSummaryField())->getFieldKeyForConduit(); 116 + 108 117 $parser = id(new DifferentialCommitMessageParser()) 109 118 ->setLabelMap($label_map) 110 - ->setTitleKey('title') 111 - ->setSummaryKey('summary'); 119 + ->setTitleKey($key_title) 120 + ->setSummaryKey($key_summary); 112 121 113 122 $result = $parser->parseCorpus($corpus); 114 123
+53
src/applications/differential/customfield/DifferentialAuditorsField.php
··· 1 + <?php 2 + 3 + final class DifferentialAuditorsField 4 + extends DifferentialStoredCustomField { 5 + 6 + public function getFieldKey() { 7 + return 'phabricator:auditors'; 8 + } 9 + 10 + public function getFieldName() { 11 + return pht('Auditors'); 12 + } 13 + 14 + public function getFieldDescription() { 15 + return pht('Allows commits to trigger audits explicitly.'); 16 + } 17 + 18 + public function getValueForStorage() { 19 + return json_encode($this->getValue()); 20 + } 21 + 22 + public function setValueFromStorage($value) { 23 + $this->setValue(phutil_json_decode($value)); 24 + return $this; 25 + } 26 + 27 + public function shouldAppearInCommitMessage() { 28 + return true; 29 + } 30 + 31 + public function shouldAllowEditInCommitMessage() { 32 + return true; 33 + } 34 + 35 + public function getRequiredHandlePHIDsForCommitMessage() { 36 + return nonempty($this->getValue(), array()); 37 + } 38 + 39 + public function parseCommitMessageValue($value) { 40 + return $this->parseObjectList( 41 + $value, 42 + array( 43 + PhabricatorPeoplePHIDTypeUser::TYPECONST, 44 + PhabricatorProjectPHIDTypeProject::TYPECONST, 45 + )); 46 + } 47 + 48 + public function renderCommitMessageValue(array $handles) { 49 + return $this->renderObjectList($handles); 50 + } 51 + 52 + 53 + }
+27
src/applications/differential/customfield/DifferentialBlameRevisionField.php
··· 7 7 return 'phabricator:blame-revision'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'blameRevision'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Blame Revision'); 12 16 } ··· 82 86 '%s updated the blame revision for %s.', 83 87 $xaction->renderHandleLink($author_phid), 84 88 $xaction->renderHandleLink($object_phid)); 89 + } 90 + 91 + public function shouldAppearInCommitMessage() { 92 + return true; 93 + } 94 + 95 + public function shouldAllowEditInCommitMessage() { 96 + return true; 97 + } 98 + 99 + public function shouldOverwriteWhenCommitMessageIsEdited() { 100 + return true; 101 + } 102 + 103 + public function getCommitMessageLabels() { 104 + return array( 105 + 'Blame Revision', 106 + 'Blame Rev', 107 + ); 108 + } 109 + 110 + public function renderCommitMessageValue() { 111 + return $this->getValue(); 85 112 } 86 113 87 114 }
+45
src/applications/differential/customfield/DifferentialConflictsField.php
··· 1 + <?php 2 + 3 + /** 4 + * This field doesn't do anything, it just parses the "Conflicts:" field which 5 + * `git` can insert after a merge, so we don't squish the field value into 6 + * some other field. 7 + */ 8 + final class DifferentialConflictsField 9 + extends DifferentialCustomField { 10 + 11 + public function getFieldKey() { 12 + return 'differential:conflicts'; 13 + } 14 + 15 + public function getFieldKeyForConduit() { 16 + return 'conflicts'; 17 + } 18 + 19 + public function getFieldName() { 20 + return pht('Conflicts'); 21 + } 22 + 23 + public function getFieldDescription() { 24 + return pht( 25 + 'Parses the "Conflicts" field which Git can inject into commit '. 26 + 'messages.'); 27 + } 28 + 29 + public function canDisableField() { 30 + return false; 31 + } 32 + 33 + public function shouldAppearInCommitMessage() { 34 + return true; 35 + } 36 + 37 + public function shouldAllowEditInCommitMessage() { 38 + return false; 39 + } 40 + 41 + public function renderCommitMessageValue(array $handles) { 42 + return null; 43 + } 44 + 45 + }
+9
src/applications/differential/customfield/DifferentialCoreCustomField.php
··· 117 117 return $this->value; 118 118 } 119 119 120 + public function readValueFromCommitMessage($value) { 121 + $this->setValue($value); 122 + return $this; 123 + } 124 + 125 + public function renderCommitMessageValue(array $handles) { 126 + return $this->getValue(); 127 + } 128 + 120 129 }
+194 -2
src/applications/differential/customfield/DifferentialCustomField.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task commitmessage Integration with Commit Messages 5 + */ 3 6 abstract class DifferentialCustomField 4 7 extends PhabricatorCustomField { 5 8 6 - public function getRequiredDiffPropertiesForRevisionView() { 7 - return array(); 9 + const ROLE_COMMITMESSAGE = 'differential:commitmessage'; 10 + const ROLE_COMMITMESSAGEEDIT = 'differential:commitmessageedit'; 11 + 12 + /** 13 + * TODO: It would be nice to remove this, but a lot of different code is 14 + * bound together by it. Until everything is modernized, retaining the old 15 + * field keys is the only reasonable way to update things one piece 16 + * at a time. 17 + */ 18 + public function getFieldKeyForConduit() { 19 + return $this->getFieldKey(); 20 + } 21 + 22 + public function shouldEnableForRole($role) { 23 + switch ($role) { 24 + case self::ROLE_COMMITMESSAGE: 25 + return $this->shouldAppearInCommitMessage(); 26 + case self::ROLE_COMMITMESSAGEEDIT: 27 + return $this->shouldAppearInCommitMessage() && 28 + $this->shouldAllowEditInCommitMessage(); 29 + } 30 + 31 + return parent::shouldEnableForRole($role); 8 32 } 9 33 10 34 protected function renderHandleList(array $handles) { ··· 18 42 } 19 43 20 44 return phutil_implode_html(phutil_tag('br'), $out); 45 + } 46 + 47 + public function getRequiredDiffPropertiesForRevisionView() { 48 + if ($this->getProxy()) { 49 + return $this->getProxy()->getRequiredDiffPropertiesForRevisionView(); 50 + } 51 + return array(); 52 + } 53 + 54 + protected function parseObjectList($value, array $types) { 55 + return id(new PhabricatorObjectListQuery()) 56 + ->setViewer($this->getViewer()) 57 + ->setAllowedTypes($types) 58 + ->setObjectList($value) 59 + ->execute(); 60 + } 61 + 62 + protected function renderObjectList(array $handles) { 63 + if (!$handles) { 64 + return null; 65 + } 66 + 67 + $out = array(); 68 + foreach ($handles as $handle) { 69 + if ($handle->getPolicyFiltered()) { 70 + $out[] = $handle->getPHID(); 71 + } else if ($handle->isComplete()) { 72 + $out[] = $handle->getObjectName(); 73 + } 74 + } 75 + 76 + return implode(', ', $out); 77 + } 78 + 79 + 80 + /* -( Integration with Commit Messages )----------------------------------- */ 81 + 82 + 83 + /** 84 + * @task commitmessage 85 + */ 86 + public function shouldAppearInCommitMessage() { 87 + if ($this->getProxy()) { 88 + return $this->getProxy()->shouldAppearInCommitMessage(); 89 + } 90 + return false; 91 + } 92 + 93 + 94 + /** 95 + * @task commitmessage 96 + */ 97 + public function shouldAppearInCommitMessageTemplate() { 98 + if ($this->getProxy()) { 99 + return $this->getProxy()->shouldAppearInCommitMessageTemplate(); 100 + } 101 + return false; 102 + } 103 + 104 + 105 + /** 106 + * @task commitmessage 107 + */ 108 + public function shouldAllowEditInCommitMessage() { 109 + if ($this->getProxy()) { 110 + return $this->getProxy()->shouldAllowEditInCommitMessage(); 111 + } 112 + return true; 113 + } 114 + 115 + 116 + /** 117 + * @task commitmessage 118 + */ 119 + public function getProTips() { 120 + if ($this->getProxy()) { 121 + return $this->getProxy()->getProTips(); 122 + } 123 + return array(); 124 + } 125 + 126 + 127 + /** 128 + * @task commitmessage 129 + */ 130 + public function getCommitMessageLabels() { 131 + if ($this->getProxy()) { 132 + return $this->getProxy()->getCommitMessageLabels(); 133 + } 134 + return array($this->renderCommitMessageLabel()); 135 + } 136 + 137 + 138 + /** 139 + * @task commitmessage 140 + */ 141 + public function parseValueFromCommitMessage($value) { 142 + if ($this->getProxy()) { 143 + return $this->getProxy()->parseValueFromCommitMessage($value); 144 + } 145 + return $value; 146 + } 147 + 148 + 149 + /** 150 + * @task commitmessage 151 + */ 152 + public function readValueFromCommitMessage($value) { 153 + if ($this->getProxy()) { 154 + $this->getProxy()->readValueFromCommitMessage($value); 155 + return $this; 156 + } 157 + return $this; 158 + } 159 + 160 + 161 + /** 162 + * @task commitmessage 163 + */ 164 + public function shouldOverwriteWhenCommitMessageIsEdited() { 165 + if ($this->getProxy()) { 166 + return $this->getProxy()->shouldOverwriteWhenCommitMessageIsEdited(); 167 + } 168 + return false; 169 + } 170 + 171 + 172 + /** 173 + * @task commitmessage 174 + */ 175 + public function getRequiredHandlePHIDsForCommitMessage() { 176 + if ($this->getProxy()) { 177 + return $this->getProxy()->getRequiredHandlePHIDsForCommitMessage(); 178 + } 179 + return array(); 180 + } 181 + 182 + 183 + /** 184 + * @task commitmessage 185 + */ 186 + public function renderCommitMessageLabel() { 187 + if ($this->getProxy()) { 188 + return $this->getProxy()->renderCommitMessageLabel(); 189 + } 190 + return $this->getFieldName(); 191 + } 192 + 193 + 194 + /** 195 + * @task commitmessage 196 + */ 197 + public function renderCommitMessageValue(array $handles) { 198 + if ($this->getProxy()) { 199 + return $this->getProxy()->renderCommitMessageValue($handles); 200 + } 201 + throw new PhabricatorCustomFieldImplementationIncompleteException($this); 202 + } 203 + 204 + 205 + /** 206 + * @task commitmessage 207 + */ 208 + public function validateCommitMessageValue($value) { 209 + if ($this->getProxy()) { 210 + return $this->getProxy()->validateCommitMessageValue($value); 211 + } 212 + return; 21 213 } 22 214 23 215 }
+8
src/applications/differential/customfield/DifferentialDependsOnField.php
··· 33 33 return $this->renderHandleList($handles); 34 34 } 35 35 36 + public function getProTips() { 37 + return array( 38 + pht( 39 + 'Create a dependendency between revisions by writing '. 40 + '"Depends on D123" in your summary.'), 41 + ); 42 + } 43 + 36 44 }
+45
src/applications/differential/customfield/DifferentialGitSVNIDField.php
··· 1 + <?php 2 + 3 + /** 4 + * This field doesn't do anything, it just parses the "git-svn-id" field which 5 + * `git svn` inserts into commit messages so that we don't end up mangling 6 + * some other field. 7 + */ 8 + final class DifferentialGitSVNIDField 9 + extends DifferentialCustomField { 10 + 11 + public function getFieldKey() { 12 + return 'differential:git-svn-id'; 13 + } 14 + 15 + public function getFieldKeyForConduit() { 16 + return 'gitSVNID'; 17 + } 18 + 19 + public function getFieldName() { 20 + return pht('git-svn-id'); 21 + } 22 + 23 + public function getFieldDescription() { 24 + return pht( 25 + 'Parses the "git-svn-id" field which Git/SVN can inject into commit '. 26 + 'messages.'); 27 + } 28 + 29 + public function canDisableField() { 30 + return false; 31 + } 32 + 33 + public function shouldAppearInCommitMessage() { 34 + return true; 35 + } 36 + 37 + public function shouldAllowEditInCommitMessage() { 38 + return false; 39 + } 40 + 41 + public function renderCommitMessageValue(array $handles) { 42 + return null; 43 + } 44 + 45 + }
+32
src/applications/differential/customfield/DifferentialJIRAIssuesField.php
··· 9 9 return 'phabricator:jira-issues'; 10 10 } 11 11 12 + public function getFieldKeyForConduit() { 13 + return 'jira.issues'; 14 + } 15 + 12 16 public function getValueForStorage() { 13 17 return json_encode($this->getValue()); 14 18 } ··· 230 234 } 231 235 232 236 $editor->save(); 237 + } 238 + 239 + public function shouldAppearInCommitMessage() { 240 + return true; 241 + } 242 + 243 + public function shouldAppearInCommitMessageTemplate() { 244 + return true; 245 + } 246 + 247 + public function getCommitMessageLabels() { 248 + return array( 249 + 'JIRA', 250 + 'JIRA Issues', 251 + 'JIRA Issue', 252 + ); 253 + } 254 + 255 + public function parseValueFromCommitMessage($value) { 256 + return preg_split('/[\s,]+/', $value, $limit = -1, PREG_SPLIT_NO_EMPTY); 257 + } 258 + 259 + public function renderCommitMessageValue(array $handles) { 260 + $value = $this->getValue(); 261 + if (!$value) { 262 + return null; 263 + } 264 + return implode(', ', $value); 233 265 } 234 266 235 267 }
+47
src/applications/differential/customfield/DifferentialManiphestTasksField.php
··· 7 7 return 'differential:maniphest-tasks'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'maniphestTaskPHIDs'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Maniphest Tasks'); 12 16 } ··· 24 28 } 25 29 26 30 public function getRequiredHandlePHIDsForPropertyView() { 31 + if (!$this->getObject()->getPHID()) { 32 + return array(); 33 + } 34 + 27 35 return PhabricatorEdgeQuery::loadDestinationPHIDs( 28 36 $this->getObject()->getPHID(), 29 37 PhabricatorEdgeConfig::TYPE_DREV_HAS_RELATED_TASK); ··· 31 39 32 40 public function renderPropertyViewValue(array $handles) { 33 41 return $this->renderHandleList($handles); 42 + } 43 + 44 + public function shouldAppearInCommitMessage() { 45 + return true; 46 + } 47 + 48 + public function shouldAllowEditInCommitMessage() { 49 + return true; 50 + } 51 + 52 + public function getCommitMessageLabels() { 53 + return array( 54 + 'Maniphest Task', 55 + 'Maniphest Tasks', 56 + ); 57 + } 58 + 59 + public function parseValueFromCommitMessage($value) { 60 + return $this->parseObjectList( 61 + $value, 62 + array( 63 + ManiphestPHIDTypeTask::TYPECONST, 64 + )); 65 + } 66 + 67 + public function getRequiredHandlePHIDsForCommitMessage() { 68 + return $this->getRequiredHandlePHIDsForPropertyView(); 69 + } 70 + 71 + public function renderCommitMessageValue(array $handles) { 72 + return $this->renderObjectList($handles); 73 + } 74 + 75 + public function getProTips() { 76 + return array( 77 + pht( 78 + 'Write "Fixes T123" in your summary to automatically close the '. 79 + 'corresponding task when this change lands.'), 80 + ); 34 81 } 35 82 36 83 }
+9
src/applications/differential/customfield/DifferentialProjectReviewersField.php
··· 52 52 } 53 53 return $reviewers; 54 54 } 55 + 56 + public function getProTips() { 57 + return array( 58 + pht( 59 + 'You can add a project as a subscriber or reviewer by writing '. 60 + '"#projectname" in the appropriate field.'), 61 + ); 62 + } 63 + 55 64 }
+12
src/applications/differential/customfield/DifferentialRevertPlanField.php
··· 7 7 return 'phabricator:revert-plan'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'revertPlan'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Revert Plan'); 12 16 } ··· 129 133 public function getApplicationTransactionRemarkupBlocks( 130 134 PhabricatorApplicationTransaction $xaction) { 131 135 return array($xaction->getNewValue()); 136 + } 137 + 138 + public function shouldAppearInCommitMessage() { 139 + return true; 140 + } 141 + 142 + public function renderCommitMessageValue(array $handles) { 143 + return $this->getValue(); 132 144 } 133 145 134 146 }
+58
src/applications/differential/customfield/DifferentialReviewedByField.php
··· 1 + <?php 2 + 3 + final class DifferentialReviewedByField 4 + extends DifferentialCoreCustomField { 5 + 6 + public function getFieldKey() { 7 + return 'differential:reviewed-by'; 8 + } 9 + 10 + public function getFieldKeyForConduit() { 11 + return 'reviewedByPHIDs'; 12 + } 13 + 14 + public function getFieldName() { 15 + return pht('Reviewed By'); 16 + } 17 + 18 + public function getFieldDescription() { 19 + return pht('Records accepting reviewers in the durable message.'); 20 + } 21 + 22 + public function shouldAppearInApplicationTransactions() { 23 + return false; 24 + } 25 + 26 + public function shouldAppearInEditView() { 27 + return false; 28 + } 29 + 30 + protected function readValueFromRevision( 31 + DifferentialRevision $revision) { 32 + 33 + $phids = array(); 34 + foreach ($revision->getReviewerStatus() as $reviewer) { 35 + switch ($reviewer->getStatus()) { 36 + case DifferentialReviewerStatus::STATUS_ACCEPTED: 37 + case DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER: 38 + $phids[] = $reviewer->getReviewerPHID(); 39 + break; 40 + } 41 + } 42 + 43 + return $phids; 44 + } 45 + 46 + public function shouldAppearInCommitMessage() { 47 + return true; 48 + } 49 + 50 + public function getRequiredHandlePHIDsForCommitMessage() { 51 + return $this->getValue(); 52 + } 53 + 54 + public function renderCommitMessageValue(array $handles) { 55 + return $this->renderObjectList($handles); 56 + } 57 + 58 + }
+47
src/applications/differential/customfield/DifferentialReviewersField.php
··· 7 7 return 'differential:reviewers'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'reviewerPHIDs'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Reviewers'); 12 16 } ··· 118 122 } 119 123 return $reviewers; 120 124 } 125 + 126 + public function shouldAppearInCommitMessage() { 127 + return true; 128 + } 129 + 130 + public function shouldAppearInCommitMessageTemplate() { 131 + return true; 132 + } 133 + 134 + public function getCommitMessageLabels() { 135 + return array( 136 + 'Reviewer', 137 + 'Reviewers', 138 + ); 139 + } 140 + 141 + public function parseValueFromCommitMessage($value) { 142 + return $this->parseObjectList( 143 + $value, 144 + array( 145 + PhabricatorPeoplePHIDTypeUser::TYPECONST, 146 + PhabricatorProjectPHIDTypeProject::TYPECONST, 147 + )); 148 + } 149 + 150 + public function getRequiredHandlePHIDsForCommitMessage() { 151 + return mpull($this->getValue(), 'getReviewerPHID'); 152 + } 153 + 154 + public function readValueFromCommitMessage($value) { 155 + $reviewers = array(); 156 + foreach ($value as $phid) { 157 + $reviewers[] = new DifferentialReviewer($phid, array()); 158 + } 159 + $this->setValue($reviewers); 160 + 161 + return $this; 162 + } 163 + 164 + public function renderCommitMessageValue(array $handles) { 165 + return $this->renderObjectList($handles); 166 + } 167 + 121 168 }
+41
src/applications/differential/customfield/DifferentialRevisionIDField.php
··· 1 + <?php 2 + 3 + final class DifferentialRevisionIDField 4 + extends DifferentialCustomField { 5 + 6 + public function getFieldKey() { 7 + return 'revisionID'; 8 + } 9 + 10 + public function getFieldName() { 11 + return pht('Differential Revision'); 12 + } 13 + 14 + public function getFieldDescription() { 15 + return pht( 16 + 'Ties commits to revisions and provides a permananent link between '. 17 + 'them.'); 18 + } 19 + 20 + public function canDisableField() { 21 + return false; 22 + } 23 + 24 + public function shouldAppearInCommitMessage() { 25 + return true; 26 + } 27 + 28 + public function shouldAllowEditInCommitMessage() { 29 + return false; 30 + } 31 + 32 + public function parseValueFromCommitMessage($value) { 33 + return DifferentialRevisionIDFieldSpecification::parseRevisionIDFromURI( 34 + $value); 35 + } 36 + 37 + public function renderCommitMessageValue(array $handles) { 38 + return PhabricatorEnv::getProductionURI('/D'.$this->getObject()->getID()); 39 + } 40 + 41 + }
+47
src/applications/differential/customfield/DifferentialSubscribersField.php
··· 7 7 return 'differential:subscribers'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'ccPHIDs'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Subscribers'); 12 16 } ··· 17 21 18 22 protected function readValueFromRevision( 19 23 DifferentialRevision $revision) { 24 + if (!$revision->getPHID()) { 25 + return array(); 26 + } 27 + 20 28 return PhabricatorSubscribersQuery::loadSubscribersForPHID( 21 29 $revision->getPHID()); 22 30 } ··· 44 52 45 53 public function getApplicationTransactionType() { 46 54 return PhabricatorTransactions::TYPE_SUBSCRIBERS; 55 + } 56 + 57 + public function shouldAppearInCommitMessage() { 58 + return true; 59 + } 60 + 61 + public function shouldAllowEditInCommitMessage() { 62 + return true; 63 + } 64 + 65 + public function shouldAppearInCommitMessageTemplate() { 66 + return true; 67 + } 68 + 69 + public function getCommitMessageLabels() { 70 + return array( 71 + 'CC', 72 + 'CCs', 73 + 'Subscriber', 74 + 'Subscribers', 75 + ); 76 + } 77 + 78 + public function parseValueFromCommitMessage($value) { 79 + return $this->parseObjectList( 80 + $value, 81 + array( 82 + PhabricatorPeoplePHIDTypeUser::TYPECONST, 83 + PhabricatorProjectPHIDTypeProject::TYPECONST, 84 + PhabricatorMailingListPHIDTypeList::TYPECONST, 85 + )); 86 + } 87 + 88 + public function getRequiredHandlePHIDsForCommitMessage() { 89 + return $this->getValue(); 90 + } 91 + 92 + public function renderCommitMessageValue(array $handles) { 93 + return $this->renderObjectList($handles); 47 94 } 48 95 49 96 }
+16
src/applications/differential/customfield/DifferentialSummaryField.php
··· 7 7 return 'differential:summary'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'summary'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Summary'); 12 16 } ··· 121 125 public function getApplicationTransactionRemarkupBlocks( 122 126 PhabricatorApplicationTransaction $xaction) { 123 127 return array($xaction->getNewValue()); 128 + } 129 + 130 + public function shouldAppearInCommitMessage() { 131 + return true; 132 + } 133 + 134 + public function shouldAppearInCommitMessageTemplate() { 135 + return true; 136 + } 137 + 138 + public function shouldOverwriteWhenCommitMessageIsEdited() { 139 + return true; 124 140 } 125 141 126 142 }
+34 -1
src/applications/differential/customfield/DifferentialTestPlanField.php
··· 7 7 return 'differential:test-plan'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'testPlan'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Test Plan'); 12 16 } ··· 36 40 37 41 protected function getCoreFieldRequiredErrorString() { 38 42 return pht( 39 - 'You must provide a test plan: describe the actions you performed '. 43 + 'You must provide a test plan. Describe the actions you performed '. 40 44 'to verify the behvaior of this change.'); 41 45 } 42 46 ··· 137 141 PhabricatorApplicationTransaction $xaction) { 138 142 return array($xaction->getNewValue()); 139 143 } 144 + 145 + public function shouldAppearInCommitMessage() { 146 + return true; 147 + } 148 + 149 + public function shouldAppearInCommitMessageTemplate() { 150 + return true; 151 + } 152 + 153 + public function shouldOverwriteWhenCommitMessageIsEdited() { 154 + return true; 155 + } 156 + 157 + public function getCommitMessageLabels() { 158 + return array( 159 + 'Test Plan', 160 + 'Testplan', 161 + 'Tested', 162 + 'Tests', 163 + ); 164 + } 165 + 166 + public function validateCommitMessageValue($value) { 167 + if (!strlen($value) && $this->isCoreFieldRequired()) { 168 + throw new DifferentialFieldValidationException( 169 + $this->getCoreFieldRequiredErrorString()); 170 + } 171 + } 172 + 140 173 141 174 }
+33
src/applications/differential/customfield/DifferentialTitleField.php
··· 7 7 return 'differential:title'; 8 8 } 9 9 10 + public function getFieldKeyForConduit() { 11 + return 'title'; 12 + } 13 + 10 14 public function getFieldName() { 11 15 return pht('Title'); 12 16 } 13 17 14 18 public function getFieldDescription() { 15 19 return pht('Stores the revision title.'); 20 + } 21 + 22 + public static function getDefaultTitle() { 23 + return pht('<<Replace this line with your Revision Title>>'); 16 24 } 17 25 18 26 protected function readValueFromRevision( ··· 87 95 '%s created %s.', 88 96 $xaction->renderHandleLink($author_phid), 89 97 $xaction->renderHandleLink($object_phid)); 98 + } 99 + } 100 + 101 + public function shouldAppearInCommitMessage() { 102 + return true; 103 + } 104 + 105 + public function shouldOverwriteWhenCommitMessageIsEdited() { 106 + return true; 107 + } 108 + 109 + public function validateCommitMessageValue($value) { 110 + if (!strlen($value)) { 111 + throw new DifferentialFieldValidationException( 112 + pht( 113 + "You must provide a revision title in the first line ". 114 + "of your commit message.")); 115 + } 116 + 117 + if (preg_match('/^<<.*>>$/', $value)) { 118 + throw new DifferentialFieldValidationException( 119 + pht( 120 + 'Replace the line "%s" with a human-readable revision title which '. 121 + 'describes the changes you are making.', 122 + self::getDefaultTitle())); 90 123 } 91 124 } 92 125