diff --git a/src/applications/differential/customfield/DifferentialBranchField.php b/src/applications/differential/customfield/DifferentialBranchField.php index a0c9ccb779..bbda1f8551 100644 --- a/src/applications/differential/customfield/DifferentialBranchField.php +++ b/src/applications/differential/customfield/DifferentialBranchField.php @@ -1,45 +1,54 @@ getFieldName(); } public function renderPropertyViewValue(array $handles) { return $this->getBranchDescription($this->getObject()->getActiveDiff()); } private function getBranchDescription(DifferentialDiff $diff) { $branch = $diff->getBranch(); $bookmark = $diff->getBookmark(); if (strlen($branch) && strlen($bookmark)) { return pht('%s (bookmark) on %s (branch)', $bookmark, $branch); } else if (strlen($bookmark)) { return pht('%s (bookmark)', $bookmark); } else if (strlen($branch)) { return $branch; } else { return null; } } + public function getProTips() { + return array( + pht( + 'In Git and Mercurial, use a branch like "T123" to automatically '. + 'associate changes with the corresponding task.'), + ); + } + + } diff --git a/src/applications/differential/field/specification/DifferentialBranchFieldSpecification.php b/src/applications/differential/field/specification/DifferentialBranchFieldSpecification.php index af427fe34c..099fa2861f 100644 --- a/src/applications/differential/field/specification/DifferentialBranchFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialBranchFieldSpecification.php @@ -1,52 +1,46 @@ getBranch(); $bookmark = $diff->getBookmark(); $has_branch = ($branch != ''); $has_bookmark = ($bookmark != ''); if ($has_branch && $has_bookmark) { return "{$bookmark} bookmark on {$branch} branch"; } else if ($has_bookmark) { return "{$bookmark} bookmark"; } else if ($has_branch) { return $branch; } return null; } public function renderValueForRevisionView() { $diff = $this->getManualDiff(); return $this->getBranchOrBookmarkDescription($diff); } public function renderValueForMail($phase) { $diff = $this->getRevision()->loadActiveDiff(); if ($diff) { $description = $this->getBranchOrBookmarkDescription($diff); if ($description) { return "BRANCH\n {$description}"; } } return null; } - public function getCommitMessageTips() { - return array( - 'Name branch "T123" to attach the diff to a task.', - ); - } - } diff --git a/src/applications/differential/field/specification/DifferentialDependsOnFieldSpecification.php b/src/applications/differential/field/specification/DifferentialDependsOnFieldSpecification.php index 19a0a483c1..befce76bdf 100644 --- a/src/applications/differential/field/specification/DifferentialDependsOnFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialDependsOnFieldSpecification.php @@ -1,57 +1,50 @@ getDependentRevisionPHIDs(); } public function renderLabelForRevisionView() { return 'Depends On:'; } public function renderValueForRevisionView() { $revision_phids = $this->getDependentRevisionPHIDs(); if (!$revision_phids) { return null; } $links = array(); foreach ($revision_phids as $revision_phid) { $links[] = $this->getHandle($revision_phid)->renderLink(); } return phutil_implode_html(phutil_tag('br'), $links); } private function getDependentRevisionPHIDs() { return PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getRevision()->getPHID(), PhabricatorEdgeConfig::TYPE_DREV_DEPENDS_ON_DREV); } public function shouldAppearOnConduitView() { return true; } public function getValueForConduit() { return $this->getDependentRevisionPHIDs(); } public function getKeyForConduit() { return 'phabricator:depends-on'; } - public function getCommitMessageTips() { - return array( - 'Use "Depends on D123" in your summary to mark '. - 'a dependency between revisions.' - ); - } - } diff --git a/src/applications/differential/field/specification/DifferentialFieldSpecification.php b/src/applications/differential/field/specification/DifferentialFieldSpecification.php index 7ef49e7aed..a1b70beeba 100644 --- a/src/applications/differential/field/specification/DifferentialFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialFieldSpecification.php @@ -1,972 +1,969 @@ 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); } /** * Optionally, build a preview panel for the field which will appear on the * edit interface. This is used for the "Summary" field, but custom fields * generally need not implement it. * * @return AphrontView|string Something renderable. * @task edit */ public function renderEditPreview() { return null; } /** * 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; } /** * Determine if user mentions should be extracted from the value and added to * CC when creating revision. Mentions are then extracted from the string * returned by @{method:renderValueForCommitMessage}. * * By default, mentions are not extracted. * * @return bool * @task edit */ public function shouldExtractMentions() { return false; } /* -( 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); } /** * Load users, their current statuses and return a markup with links to the * user profiles and information about their current status. * * @return string Display markup. * @task view */ public function renderUserList(array $user_phids) { if (!$user_phids) { return phutil_tag('em', array(), pht('None')); } return implode_selected_handle_links(', ', $this->getLoadedHandles(), $user_phids); } /** * 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 Diff View Interface )------------------------------ */ /** * Determine if this field should appear on the diff detail view * interface. One use of this interface is to add purely informational * fields to the diff view, without any sort of backing storage. * * NOTE: These diffs are not necessarily attached yet to a revision. * As such, a field on the diff view can not rely on the existence of a * revision or use storage attached to the revision. * * If you return true from this method, you must implement the methods * @{method:renderLabelForDiffView} and * @{method:renderValueForDiffView}. * * @return bool True if this field should appear when viewing a diff. * @task view */ public function shouldAppearOnDiffView() { return false; } /** * Return a string field label which will appear in the diff detail * table. * * You must implement this method if you return true from * @{method:shouldAppearOnDiffView}. * * @return string Label for field in revision detail view. * @task view */ public function renderLabelForDiffView() { throw new DifferentialFieldSpecificationIncompleteException($this); } /** * Return a markup block representing the field for the diff 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:shouldAppearOnDiffView}. * * @return string|null Display markup for field value, or null to suppress * field rendering. * @task view */ public function renderValueForDiffView() { throw new DifferentialFieldSpecificationIncompleteException($this); } /* -( Extending the E-mail Interface )------------------------------------- */ /** * Return plain text to render in e-mail messages. The text may span * multiple lines. * * @return int One of DifferentialMailPhase constants. * @return string|null Plain text, or null for no message. * * @task mail */ public function renderValueForMail($phase) { return null; } /* -( 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 the Search Interface )------------------------------------ */ /** * @task search */ public function shouldAddToSearchIndex() { return false; } /** * @task search */ public function getValueForSearchIndex() { throw new DifferentialFieldSpecificationIncompleteException($this); } /** * NOTE: Keys *must be* 4 characters for * @{class:PhabricatorSearchEngineMySQL}. * * @task search */ public function getKeyForSearchIndex() { throw new DifferentialFieldSpecificationIncompleteException($this); } /* -( 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); } /** * This method allows you to take action when a commit appears in a tracked * branch (for example, by closing tasks associated with the commit). * * @param PhabricatorRepository The repository the commit appeared in. * @param PhabricatorRepositoryCommit The commit itself. * @param PhabricatorRepostioryCommitData Commit data. * @return void * * @task commit */ public function didParseCommit( PhabricatorRepository $repo, PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { return; } - public function getCommitMessageTips() { - return array(); - } /* -( Loading Additional Data )-------------------------------------------- */ /** * Specify which @{class:PhabricatorObjectHandle}s 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:PhabricatorObjectHandle}s 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:PhabricatorObjectHandle}s 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:PhabricatorObjectHandle}s 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:PhabricatorObjectHandle}s 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(); } /** * 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); } protected function parseCommitMessageUserOrProjectList($value) { return $this->parseCommitMessageObjectList( $value, $mailables = false, $allow_partial = 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) { $types = array( PhabricatorPeoplePHIDTypeUser::TYPECONST, PhabricatorProjectPHIDTypeProject::TYPECONST, ); if ($include_mailables) { $types[] = PhabricatorMailingListPHIDTypeList::TYPECONST; } return id(new PhabricatorObjectListQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->setAllowPartialResults($allow_partial) ->setAllowedTypes($types) ->setObjectList($value) ->execute(); } /* -( 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 setManualDiff(DifferentialDiff $diff) { $this->manualDiff = $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; } /** * Determine if revision context is currently available. * * @task context */ final protected function hasRevision() { return (bool)$this->revision; } /** * @task context */ final protected function getDiff() { if (empty($this->diff)) { throw new DifferentialFieldDataNotAvailableException($this); } return $this->diff; } /** * @task context */ final protected function getManualDiff() { if (!$this->manualDiff) { return $this->getDiff(); } return $this->manualDiff; } /** * @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]; } final protected function getLoadedHandles() { if ($this->handles === null) { throw new DifferentialFieldDataNotAvailableException($this); } return $this->handles; } /** * Get the list of properties for a diff set by @{method:setManualDiff}. * * @return array Array of all Diff properties. * @task context */ final public function getDiffProperties() { 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); } return $this->diffProperties; } /** * Get a property of a diff set by @{method:setManualDiff}. * * @param string Diff property key. * @return mixed|null Diff property, or null if the property does not have * a value. * @task context */ final public function getDiffProperty($key) { return idx($this->getDiffProperties(), $key); } }