mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-16 11:52:40 +01:00
2d9d2f1aed
Summary: - In practice, 'edit' has two modes, 'create' and 'edit'. These seem like they should map to "create a revision" and "update a revision", but they are completely different. - We use the "create" mode: - When creating a message from the working copy. - When creating a message from a file. - When creating a message from a commit. - When creating a message from a user template. - When creating a message from an "--edit"! - We use the "edit" mode: - ONLY when updating a revision with `arc diff --verbatim`. - The only difference is in which fields may be overwritten. Under "create", all fields may be overwritten. Under "edit", only safe fields may be overwritten. - The "Differential Revision" field currently does not render in either edit mode. This is wrong. Even though it can not be updated in the "edit" mode, it should still render in both modes. This is the only material change this revision makes. - Without this change, when we "create" a new message from a working copy and the working copy has a "Differential Revision" field, we incorrectly discard it. - The only field which does not render on edit modes now is "Reviewed by" (not "Reviewers"), which is correct, since we do not read the value. Test Plan: Ran "arc diff" to create/update revisions. Ran "arc diff --verbatim" to create/update revisions with implicit edits (with D2411). Ran "arc diff --edit" to update revisions with explicit edits. Reviewers: jungejason, btrahan Reviewed by: jungejason CC: vrana, aran Differential Revision: https://secure.phabricator.com/D2412
857 lines
27 KiB
PHP
857 lines
27 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Copyright 2012 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.
|
|
*/
|
|
|
|
/**
|
|
* Describes and implements the behavior for a custom field on Differential
|
|
* revisions. Along with other configuration, you can extend this class to add
|
|
* custom fields to Differential revisions and commit messages.
|
|
*
|
|
* Generally, you should implement all methods from the storage task and then
|
|
* the methods from one or more interface tasks.
|
|
*
|
|
* @task storage Field Storage
|
|
* @task edit Extending the Revision Edit Interface
|
|
* @task view Extending the Revision View Interface
|
|
* @task list Extending the Revision List Interface
|
|
* @task conduit Extending the Conduit View Interface
|
|
* @task commit Extending Commit Messages
|
|
* @task load Loading Additional Data
|
|
* @task context Contextual Data
|
|
*/
|
|
abstract class DifferentialFieldSpecification {
|
|
|
|
private $revision;
|
|
private $diff;
|
|
private $handles;
|
|
private $diffProperties;
|
|
private $user;
|
|
|
|
|
|
/* -( Storage )------------------------------------------------------------ */
|
|
|
|
|
|
/**
|
|
* Return a unique string used to key storage of this field's value, like
|
|
* "mycompany.fieldname" or similar. You can return null (the default) to
|
|
* indicate that this field does not use any storage. This is appropriate for
|
|
* display fields, like @{class:DifferentialLinesFieldSpecification}. If you
|
|
* implement this, you must also implement @{method:getValueForStorage} and
|
|
* @{method:setValueFromStorage}.
|
|
*
|
|
* @return string|null Unique key which identifies this field in auxiliary
|
|
* field storage. Maximum length is 32. Alternatively,
|
|
* null (default) to indicate that this field does not
|
|
* use auxiliary field storage.
|
|
* @task storage
|
|
*/
|
|
public function getStorageKey() {
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a serialized representation of the field value, appropriate for
|
|
* storing in auxiliary field storage. You must implement this method if
|
|
* you implement @{method:getStorageKey}.
|
|
*
|
|
* @return string Serialized field value.
|
|
* @task storage
|
|
*/
|
|
public function getValueForStorage() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the field's value given a serialized storage value. This is called
|
|
* when the field is loaded; if no data is available, the value will be
|
|
* null. You must implement this method if you implement
|
|
* @{method:getStorageKey}.
|
|
*
|
|
* @param string|null Serialized field representation (from
|
|
* @{method:getValueForStorage}) or null if no value has
|
|
* ever been stored.
|
|
* @return this
|
|
* @task storage
|
|
*/
|
|
public function setValueFromStorage($value) {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/* -( Extending the Revision Edit Interface )------------------------------ */
|
|
|
|
|
|
/**
|
|
* Determine if this field should appear on the "Edit Revision" interface. If
|
|
* you return true from this method, you must implement
|
|
* @{method:setValueFromRequest}, @{method:renderEditControl} and
|
|
* @{method:validateField}.
|
|
*
|
|
* For a concrete example of a field which implements an edit interface, see
|
|
* @{class:DifferentialRevertPlanFieldSpecification}.
|
|
*
|
|
* @return bool True to indicate that this field implements an edit interface.
|
|
* @task edit
|
|
*/
|
|
public function shouldAppearOnEdit() {
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the field's value from an HTTP request. Generally, you should read
|
|
* the value of some field name you emitted in @{method:renderEditControl}
|
|
* and save it into the object, e.g.:
|
|
*
|
|
* $this->value = $request->getStr('my-custom-field');
|
|
*
|
|
* If you have some particularly complicated field, you may need to read
|
|
* more data; this is why you have access to the entire request.
|
|
*
|
|
* You must implement this if you implement @{method:shouldAppearOnEdit}.
|
|
*
|
|
* You should not perform field validation here; instead, you should implement
|
|
* @{method:validateField}.
|
|
*
|
|
* @param AphrontRequest HTTP request representing a user submitting a form
|
|
* with this field in it.
|
|
* @return this
|
|
* @task edit
|
|
*/
|
|
public function setValueFromRequest(AphrontRequest $request) {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Build a renderable object (generally, some @{class:AphrontFormControl})
|
|
* which can be appended to a @{class:AphrontFormView} and represents the
|
|
* interface the user sees on the "Edit Revision" screen when interacting
|
|
* with this field.
|
|
*
|
|
* For example:
|
|
*
|
|
* return id(new AphrontFormTextControl())
|
|
* ->setLabel('Custom Field')
|
|
* ->setName('my-custom-key')
|
|
* ->setValue($this->value);
|
|
*
|
|
* You must implement this if you implement @{method:shouldAppearOnEdit}.
|
|
*
|
|
* @return AphrontView|string Something renderable.
|
|
* @task edit
|
|
*/
|
|
public function renderEditControl() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/**
|
|
* This method will be called after @{method:setValueFromRequest} but before
|
|
* the field is saved. It gives you an opportunity to inspect the field value
|
|
* and throw a @{class:DifferentialFieldValidationException} if there is a
|
|
* problem with the value the user has provided (for example, the value the
|
|
* user entered is not correctly formatted). This method is also called after
|
|
* @{method:setValueFromParsedCommitMessage} before the revision is saved.
|
|
*
|
|
* By default, fields are not validated.
|
|
*
|
|
* @return void
|
|
* @task edit
|
|
*/
|
|
public function validateField() {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Hook for applying revision changes via the editor. Normally, you should
|
|
* not implement this, but a number of builtin fields use the revision object
|
|
* itself as storage. If you need to do something similar for whatever reason,
|
|
* this method gives you an opportunity to interact with the editor or
|
|
* revision before changes are saved (for example, you can write the field's
|
|
* value into some property of the revision).
|
|
*
|
|
* @param DifferentialRevisionEditor Active editor which is applying changes
|
|
* to the revision.
|
|
* @return void
|
|
* @task edit
|
|
*/
|
|
public function willWriteRevision(DifferentialRevisionEditor $editor) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Hook after an edit operation has completed. This allows you to update
|
|
* link tables or do other write operations which should happen after the
|
|
* revision is saved. Normally you don't need to implement this.
|
|
*
|
|
*
|
|
* @param DifferentialRevisionEditor Active editor which has just applied
|
|
* changes to the revision.
|
|
* @return void
|
|
* @task edit
|
|
*/
|
|
public function didWriteRevision(DifferentialRevisionEditor $editor) {
|
|
return;
|
|
}
|
|
|
|
|
|
/* -( Extending the Revision View Interface )------------------------------ */
|
|
|
|
|
|
/**
|
|
* Determine if this field should appear on the revision detail view
|
|
* interface. One use of this interface is to add purely informational
|
|
* fields to the revision view, without any sort of backing storage.
|
|
*
|
|
* If you return true from this method, you must implement the methods
|
|
* @{method:renderLabelForRevisionView} and
|
|
* @{method:renderValueForRevisionView}.
|
|
*
|
|
* @return bool True if this field should appear when viewing a revision.
|
|
* @task view
|
|
*/
|
|
public function shouldAppearOnRevisionView() {
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a string field label which will appear in the revision detail
|
|
* table.
|
|
*
|
|
* You must implement this method if you return true from
|
|
* @{method:shouldAppearOnRevisionView}.
|
|
*
|
|
* @return string Label for field in revision detail view.
|
|
* @task view
|
|
*/
|
|
public function renderLabelForRevisionView() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a markup block representing the field for the revision detail
|
|
* view. Note that you can return null to suppress display (for instance,
|
|
* if the field shows related objects of some type and the revision doesn't
|
|
* have any related objects).
|
|
*
|
|
* You must implement this method if you return true from
|
|
* @{method:shouldAppearOnRevisionView}.
|
|
*
|
|
* @return string|null Display markup for field value, or null to suppress
|
|
* field rendering.
|
|
* @task view
|
|
*/
|
|
public function renderValueForRevisionView() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a markup block representing a warning to display with the comment
|
|
* box when preparing to accept a diff. A return value of null indicates no
|
|
* warning box should be displayed for this field.
|
|
*
|
|
* @return string|null Display markup for warning box, or null for no warning
|
|
*/
|
|
public function renderWarningBoxForRevisionAccept() {
|
|
return null;
|
|
}
|
|
|
|
|
|
/* -( Extending the Revision List Interface )------------------------------ */
|
|
|
|
|
|
/**
|
|
* Determine if this field should appear in the table on the revision list
|
|
* interface.
|
|
*
|
|
* @return bool True if this field should appear in the table.
|
|
*
|
|
* @task list
|
|
*/
|
|
public function shouldAppearOnRevisionList() {
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a column header for revision list tables.
|
|
*
|
|
* @return string Column header.
|
|
*
|
|
* @task list
|
|
*/
|
|
public function renderHeaderForRevisionList() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Optionally, return a column class for revision list tables.
|
|
*
|
|
* @return string CSS class for table cells.
|
|
*
|
|
* @task list
|
|
*/
|
|
public function getColumnClassForRevisionList() {
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a table cell value for revision list tables.
|
|
*
|
|
* @param DifferentialRevision The revision to render a value for.
|
|
* @return string Table cell value.
|
|
*
|
|
* @task list
|
|
*/
|
|
public function renderValueForRevisionList(DifferentialRevision $revision) {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
|
|
/* -( Extending the Conduit Interface )------------------------------------ */
|
|
|
|
|
|
/**
|
|
* @task conduit
|
|
*/
|
|
public function shouldAppearOnConduitView() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @task conduit
|
|
*/
|
|
public function getValueForConduit() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
/**
|
|
* @task conduit
|
|
*/
|
|
public function getKeyForConduit() {
|
|
$key = $this->getStorageKey();
|
|
if ($key === null) {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
return $key;
|
|
}
|
|
|
|
|
|
/* -( Extending Commit Messages )------------------------------------------ */
|
|
|
|
|
|
/**
|
|
* Determine if this field should appear in commit messages. You should return
|
|
* true if this field participates in any part of the commit message workflow,
|
|
* even if it is not rendered by default.
|
|
*
|
|
* If you implement this method, you must implement
|
|
* @{method:getCommitMessageKey} and
|
|
* @{method:setValueFromParsedCommitMessage}.
|
|
*
|
|
* @return bool True if this field appears in commit messages in any capacity.
|
|
* @task commit
|
|
*/
|
|
public function shouldAppearOnCommitMessage() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Key which identifies this field in parsed commit messages. Commit messages
|
|
* exist in two forms: raw textual commit messages and parsed dictionaries of
|
|
* fields. This method must return a unique string which identifies this field
|
|
* in dictionaries. Principally, this dictionary is shipped to and from arc
|
|
* over Conduit. Keys should be appropriate property names, like "testPlan"
|
|
* (not "Test Plan") and must be globally unique.
|
|
*
|
|
* You must implement this method if you return true from
|
|
* @{method:shouldAppearOnCommitMessage}.
|
|
*
|
|
* @return string Key which identifies the field in dictionaries.
|
|
* @task commit
|
|
*/
|
|
public function getCommitMessageKey() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
/**
|
|
* Set this field's value from a value in a parsed commit message dictionary.
|
|
* Afterward, this field will go through the normal write workflows and the
|
|
* change will be permanently stored via either the storage mechanisms (if
|
|
* your field implements them), revision write hooks (if your field implements
|
|
* 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}.
|
|
*
|
|
* @param mixed Field value from a parsed commit message dictionary.
|
|
* @return this
|
|
* @task commit
|
|
*/
|
|
public function setValueFromParsedCommitMessage($value) {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
/**
|
|
* In revision control systems which read revision information from the
|
|
* working copy, the user may edit the commit message outside of invoking
|
|
* "arc diff --edit". When they do this, only some fields (those fields which
|
|
* can not be edited by other users) are safe to overwrite. For instance, it
|
|
* is fine to overwrite "Summary" because no one else can edit it, but not
|
|
* to overwrite "Reviewers" because reviewers may have been added or removed
|
|
* via the web interface.
|
|
*
|
|
* If a field is safe to overwrite when edited in a working copy commit
|
|
* message, return true. If the authoritative value should always be used,
|
|
* return false. By default, fields can not be overwritten.
|
|
*
|
|
* arc will only attempt to overwrite field values if run with "--verbatim".
|
|
*
|
|
* @return bool True to indicate the field is save to overwrite.
|
|
* @task commit
|
|
*/
|
|
public function shouldOverwriteWhenCommitMessageIsEdited() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return true if this field should be suggested to the user during
|
|
* "arc diff --edit". Basicially, return true if the field is something the
|
|
* user might want to fill out (like "Summary"), and false if it's a
|
|
* system/display/readonly field (like "Differential Revision"). If this
|
|
* method returns true, the field will be rendered even if it has no value
|
|
* during edit and update operations.
|
|
*
|
|
* @return bool True to indicate the field should appear in the edit template.
|
|
* @task commit
|
|
*/
|
|
public function shouldAppearOnCommitMessageTemplate() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Render a human-readable label for this field, like "Summary" or
|
|
* "Test Plan". This is distinct from the commit message key, but generally
|
|
* they should be similar.
|
|
*
|
|
* @return string Human-readable field label for commit messages.
|
|
* @task commit
|
|
*/
|
|
public function renderLabelForCommitMessage() {
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
}
|
|
|
|
/**
|
|
* Render a human-readable value for this field when it appears in commit
|
|
* messages (for instance, lists of users should be rendered as user names).
|
|
*
|
|
* The ##$is_edit## parameter allows you to distinguish between commit
|
|
* messages being rendered for editing and those being rendered for amending
|
|
* or commit. Some fields may decline to render a value in one mode (for
|
|
* example, "Reviewed By" appears only when doing commit/amend, not while
|
|
* editing).
|
|
*
|
|
* @param bool True if the message is being edited.
|
|
* @return string Human-readable field value.
|
|
* @task commit
|
|
*/
|
|
public function renderValueForCommitMessage($is_edit) {
|
|
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 )-------------------------------------------- */
|
|
|
|
|
|
/**
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
* field to render correctly.
|
|
*
|
|
* This is a convenience method which makes the handles available on all
|
|
* interfaces where the field appears. If your field needs handles on only
|
|
* some interfaces (or needs different handles on different interfaces) you
|
|
* can overload the more specific methods to customize which interfaces you
|
|
* retrieve handles for. Requesting only the handles you need will improve
|
|
* the performance of your field.
|
|
*
|
|
* You can later retrieve these handles by calling @{method:getHandle}.
|
|
*
|
|
* @return list List of PHIDs to load handles for.
|
|
* @task load
|
|
*/
|
|
protected function getRequiredHandlePHIDs() {
|
|
return array();
|
|
}
|
|
|
|
|
|
/**
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
* field to render correctly on the view interface.
|
|
*
|
|
* This is a more specific version of @{method:getRequiredHandlePHIDs} which
|
|
* can be overridden to improve field performance by loading only data you
|
|
* need.
|
|
*
|
|
* @return list List of PHIDs to load handles for.
|
|
* @task load
|
|
*/
|
|
public function getRequiredHandlePHIDsForRevisionView() {
|
|
return $this->getRequiredHandlePHIDs();
|
|
}
|
|
|
|
|
|
/**
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
* field to render correctly on the list interface.
|
|
*
|
|
* This is a more specific version of @{method:getRequiredHandlePHIDs} which
|
|
* can be overridden to improve field performance by loading only data you
|
|
* need.
|
|
*
|
|
* @param DifferentialRevision The revision to pull PHIDs for.
|
|
* @return list List of PHIDs to load handles for.
|
|
* @task load
|
|
*/
|
|
public function getRequiredHandlePHIDsForRevisionList(
|
|
DifferentialRevision $revision) {
|
|
return array();
|
|
}
|
|
|
|
|
|
/**
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
* field to render correctly on the edit interface.
|
|
*
|
|
* This is a more specific version of @{method:getRequiredHandlePHIDs} which
|
|
* can be overridden to improve field performance by loading only data you
|
|
* need.
|
|
*
|
|
* @return list List of PHIDs to load handles for.
|
|
* @task load
|
|
*/
|
|
public function getRequiredHandlePHIDsForRevisionEdit() {
|
|
return $this->getRequiredHandlePHIDs();
|
|
}
|
|
|
|
/**
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
* field to render correctly on the commit message interface.
|
|
*
|
|
* This is a more specific version of @{method:getRequiredHandlePHIDs} which
|
|
* can be overridden to improve field performance by loading only data you
|
|
* need.
|
|
*
|
|
* @return list List of PHIDs to load handles for.
|
|
* @task load
|
|
*/
|
|
public function getRequiredHandlePHIDsForCommitMessage() {
|
|
return $this->getRequiredHandlePHIDs();
|
|
}
|
|
|
|
/**
|
|
* Specify which diff properties this field needs to load.
|
|
*
|
|
* @return list List of diff property keys this field requires.
|
|
* @task load
|
|
*/
|
|
public function getRequiredDiffProperties() {
|
|
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.
|
|
* @param bool True to include mailing lists.
|
|
* @param bool True to make a best effort. By default, an exception is
|
|
* thrown if any item is invalid.
|
|
* @return list List of corresponding PHIDs.
|
|
* @task load
|
|
*/
|
|
public static function parseCommitMessageObjectList(
|
|
$value,
|
|
$include_mailables,
|
|
$allow_partial = false) {
|
|
|
|
$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);
|
|
|
|
$user_map = mpull($users, 'getPHID', 'getUsername');
|
|
foreach ($user_map as $username => $phid) {
|
|
// Usernames may have uppercase letters in them. Put both names in the
|
|
// map so we can try the original case first, so that username *always*
|
|
// works in weird edge cases where some other mailable object collides.
|
|
$object_map[$username] = $phid;
|
|
$object_map[strtolower($username)] = $phid;
|
|
}
|
|
|
|
$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])) {
|
|
if (empty($object_map[strtolower($name)])) {
|
|
$invalid[] = $name;
|
|
} else {
|
|
$results[] = $object_map[strtolower($name)];
|
|
}
|
|
} else {
|
|
$results[] = $object_map[$name];
|
|
}
|
|
}
|
|
|
|
if ($invalid && !$allow_partial) {
|
|
$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 )---------------------------------------------------- */
|
|
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final public function setRevision(DifferentialRevision $revision) {
|
|
$this->revision = $revision;
|
|
$this->didSetRevision();
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
protected function didSetRevision() {
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final public function setDiff(DifferentialDiff $diff) {
|
|
$this->diff = $diff;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final public function setHandles(array $handles) {
|
|
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
|
$this->handles = $handles;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final public function setDiffProperties(array $diff_properties) {
|
|
$this->diffProperties = $diff_properties;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final public function setUser(PhabricatorUser $user) {
|
|
$this->user = $user;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final protected function getRevision() {
|
|
if (empty($this->revision)) {
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
}
|
|
return $this->revision;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final protected function getDiff() {
|
|
if (empty($this->diff)) {
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
}
|
|
return $this->diff;
|
|
}
|
|
|
|
/**
|
|
* @task context
|
|
*/
|
|
final protected function getUser() {
|
|
if (empty($this->user)) {
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
}
|
|
return $this->user;
|
|
}
|
|
|
|
/**
|
|
* Get the handle for an object PHID. You must overload
|
|
* @{method:getRequiredHandlePHIDs} (or a more specific version thereof)
|
|
* and include the PHID you want in the list for it to be available here.
|
|
*
|
|
* @return PhabricatorObjectHandle Handle to the object.
|
|
* @task context
|
|
*/
|
|
final protected function getHandle($phid) {
|
|
if ($this->handles === null) {
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
}
|
|
if (empty($this->handles[$phid])) {
|
|
$class = get_class($this);
|
|
throw new Exception(
|
|
"A differential field (of class '{$class}') is attempting to retrieve ".
|
|
"a handle ('{$phid}') which it did not request. Return all handle ".
|
|
"PHIDs you need from getRequiredHandlePHIDs().");
|
|
}
|
|
return $this->handles[$phid];
|
|
}
|
|
|
|
/**
|
|
* Get a diff property which this field previously requested by returning
|
|
* the key from @{method:getRequiredDiffProperties}.
|
|
*
|
|
* @param string Diff property key.
|
|
* @return string|null Diff property, or null if the property does not have
|
|
* a value.
|
|
* @task context
|
|
*/
|
|
final public function getDiffProperty($key) {
|
|
if ($this->diffProperties === null) {
|
|
// This will be set to some (possibly empty) array if we've loaded
|
|
// properties, so null means diff properties aren't available in this
|
|
// context.
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
}
|
|
if (!array_key_exists($key, $this->diffProperties)) {
|
|
$class = get_class($this);
|
|
throw new Exception(
|
|
"A differential field (of class '{$class}') is attempting to retrieve ".
|
|
"a diff property ('{$key}') which it did not request. Return all ".
|
|
"diff property keys you need from getRequiredDiffProperties().");
|
|
}
|
|
return $this->diffProperties[$key];
|
|
}
|
|
|
|
}
|