@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

ReleephRequest xactions

Summary:
Migrate to `PhabricatorApplicationTransactions` (`ReleephRequestTransactions` applied by `ReleephRequestTransactionalEditor`, instead of `ReleephRequestEvents` created by `ReleephRequestEditor`) and migrate all the old events into transactions. Email is supported in the standard way (no more `ReleephRequestMail`) as well.

This also collapses the Releeph request create and edit controllers into one class, as well as breaking everyone's subject-based mail rules by standardising them (but which should be more easily filtered by looking at headers.)

Test Plan:
* Make requests, then pick them.
* Pick and revert the same request so that discovery happens way after `arc` has told Releeph about what's been happening.
* Try to pick something that fails to pick in a project with pick instructions (and see the instructions are in the email.)
* Load all of FB's Releeph data into my DB and run the `storage upgrade` script.
* Request a commit via the "action" in a Differential revision.

Reviewers: epriestley

Reviewed By: epriestley

CC: epriestley, aran, Korvin, wez

Maniphest Tasks: T3092, T2720

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

+1325 -1200
+38
resources/sql/patches/20130508.releephtransactions.sql
··· 1 + CREATE TABLE {$NAMESPACE}_releeph.releeph_requesttransaction ( 2 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 7 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 8 + commentPHID VARCHAR(64) COLLATE utf8_bin, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, 11 + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, 12 + newValue LONGTEXT NOT NULL COLLATE utf8_bin, 13 + metadata LONGTEXT NOT NULL COLLATE utf8_bin, 14 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (phid), 18 + KEY `key_object` (objectPHID) 19 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 20 + 21 + CREATE TABLE {$NAMESPACE}_releeph.releeph_requesttransaction_comment ( 22 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 23 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 24 + transactionPHID VARCHAR(64) COLLATE utf8_bin, 25 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 26 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 27 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 28 + commentVersion INT UNSIGNED NOT NULL, 29 + content LONGTEXT NOT NULL COLLATE utf8_bin, 30 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 31 + isDeleted BOOL NOT NULL, 32 + dateCreated INT UNSIGNED NOT NULL, 33 + dateModified INT UNSIGNED NOT NULL, 34 + 35 + UNIQUE KEY `key_phid` (phid), 36 + UNIQUE KEY `key_version` (transactionPHID, commentVersion) 37 + 38 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+131
resources/sql/patches/20130508.releephtransactionsmig.php
··· 1 + <?php 2 + 3 + echo "Migrating Releeph requests events to transactions...\n"; 4 + 5 + $table = new ReleephRequest(); 6 + $table->openTransaction(); 7 + $table->beginReadLocking(); 8 + 9 + foreach (new LiskMigrationIterator($table) as $rq) { 10 + printf("RQ%d:", $rq->getID()); 11 + 12 + $intents_cursor = array(); 13 + $last_pick_status = null; 14 + $last_commit_id = null; 15 + 16 + foreach ($rq->loadEvents() as $event) { 17 + $author = $event->getActorPHID(); 18 + $details = $event->getDetails(); 19 + 20 + $content_source = PhabricatorContentSource::newForSource( 21 + PhabricatorContentSource::SOURCE_UNKNOWN, 22 + array('ReleephRequestEventID' => $event->getID())); 23 + 24 + $xaction = id(new ReleephRequestTransaction()) 25 + ->setObjectPHID($rq->getPHID()) 26 + ->setAuthorPHID($author) 27 + ->setContentSource($content_source) 28 + ->setDateCreated($event->getDateCreated()) 29 + ->setDateModified($event->getDateModified()) 30 + ->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC) 31 + ->setEditPolicy($author); 32 + 33 + printf(" %s(#%d)", $event->getType(), $event->getID()); 34 + 35 + switch ($event->getType()) { 36 + case ReleephRequestEvent::TYPE_COMMENT: 37 + $xaction 38 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 39 + ->save(); // to generate a PHID 40 + $comment = id(new ReleephRequestTransactionComment()) 41 + ->setAuthorPHID($author) 42 + ->setTransactionPHID($xaction->getPHID()) 43 + ->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC) 44 + ->setEditPolicy($author) 45 + ->setCommentVersion(1) 46 + ->setContent($event->getComment()) 47 + ->setContentSource($content_source) 48 + ->save(); 49 + $xaction 50 + ->setCommentPHID($comment->getPHID()) 51 + ->setCommentVersion(1); 52 + break; 53 + 54 + case ReleephRequestEvent::TYPE_STATUS: 55 + // Ignore STATUS events; these are legacy events that we no longer 56 + // support anyway! 57 + continue 2; 58 + 59 + case ReleephRequestEvent::TYPE_USER_INTENT: 60 + $old_intent = idx($intents_cursor, $author); 61 + $new_intent = $event->getDetail('newIntent'); 62 + $xaction 63 + ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT) 64 + ->setMetadataValue('isAuthoritative', $event->getDetail('wasPusher')) 65 + ->setOldValue($old_intent) 66 + ->setNewValue($new_intent); 67 + $intents_cursor[$author] = $new_intent; 68 + break; 69 + 70 + case ReleephRequestEvent::TYPE_PICK_STATUS: 71 + $new_pick_status = $event->getDetail('newPickStatus'); 72 + $xaction 73 + ->setTransactionType(ReleephRequestTransaction::TYPE_PICK_STATUS) 74 + ->setOldValue($last_pick_status) 75 + ->setNewValue($new_pick_status); 76 + $last_pick_status = $new_pick_status; 77 + break; 78 + 79 + case ReleephRequestEvent::TYPE_COMMIT: 80 + $new_commit_id = $event->getDetail('newCommitIdentifier'); 81 + $xaction 82 + ->setTransactionType(ReleephRequestTransaction::TYPE_COMMIT) 83 + ->setMetadataValue('action', $event->getDetail('action')) 84 + ->setOldValue($last_commit_id) 85 + ->setNewValue($new_commit_id); 86 + $last_commit_id = $new_commit_id; 87 + break; 88 + 89 + case ReleephRequestEvent::TYPE_DISCOVERY: 90 + $xaction 91 + ->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY) 92 + ->setNewValue($event->getDetail('newCommitPHID')); 93 + break; 94 + 95 + case ReleephRequestEvent::TYPE_CREATE: 96 + $xaction 97 + ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST) 98 + ->setOldValue(null) 99 + ->setNewValue($rq->getRequestCommitPHID()); 100 + break; 101 + 102 + case ReleephRequestEvent::TYPE_MANUAL_ACTION: 103 + $xaction 104 + ->setTransactionType( 105 + ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH); 106 + switch ($event->getDetail('action')) { 107 + case 'pick': 108 + $xaction->setNewValue(1); 109 + break; 110 + 111 + case 'revert': 112 + $xaction->setNewValue(0); 113 + break; 114 + } 115 + break; 116 + 117 + default: 118 + throw new Exception(sprintf( 119 + "Unhandled ReleephRequestEvent type '%s' in RQ%d", 120 + $event->getType(), 121 + $rq->getID())); 122 + } 123 + 124 + $xaction->save(); 125 + } 126 + echo("\n"); 127 + } 128 + 129 + $table->endReadLocking(); 130 + $table->saveTransaction(); 131 + echo "Done.\n";
+12 -7
src/__phutil_library_map__.php
··· 1733 1733 'ReleephReasonFieldSpecification' => 'applications/releeph/field/specification/ReleephReasonFieldSpecification.php', 1734 1734 'ReleephRequest' => 'applications/releeph/storage/ReleephRequest.php', 1735 1735 'ReleephRequestActionController' => 'applications/releeph/controller/request/ReleephRequestActionController.php', 1736 - 'ReleephRequestCreateController' => 'applications/releeph/controller/request/ReleephRequestCreateController.php', 1736 + 'ReleephRequestCommentController' => 'applications/releeph/controller/request/ReleephRequestCommentController.php', 1737 1737 'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php', 1738 1738 'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php', 1739 - 'ReleephRequestEditor' => 'applications/releeph/editor/ReleephRequestEditor.php', 1740 1739 'ReleephRequestEvent' => 'applications/releeph/storage/request/ReleephRequestEvent.php', 1741 - 'ReleephRequestEventListView' => 'applications/releeph/view/requestevent/ReleephRequestEventListView.php', 1742 1740 'ReleephRequestException' => 'applications/releeph/storage/request/exception/ReleephRequestException.php', 1743 1741 'ReleephRequestHeaderListView' => 'applications/releeph/view/request/header/ReleephRequestHeaderListView.php', 1744 1742 'ReleephRequestHeaderView' => 'applications/releeph/view/request/header/ReleephRequestHeaderView.php', 1745 1743 'ReleephRequestIntentsView' => 'applications/releeph/view/request/ReleephRequestIntentsView.php', 1746 - 'ReleephRequestMail' => 'applications/releeph/editor/mail/ReleephRequestMail.php', 1744 + 'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php', 1747 1745 'ReleephRequestStatusView' => 'applications/releeph/view/request/ReleephRequestStatusView.php', 1746 + 'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php', 1747 + 'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php', 1748 + 'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php', 1749 + 'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php', 1748 1750 'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php', 1749 1751 'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php', 1750 1752 'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php', ··· 3493 3495 'ReleephReasonFieldSpecification' => 'ReleephFieldSpecification', 3494 3496 'ReleephRequest' => 'ReleephDAO', 3495 3497 'ReleephRequestActionController' => 'ReleephController', 3496 - 'ReleephRequestCreateController' => 'ReleephController', 3498 + 'ReleephRequestCommentController' => 'ReleephController', 3497 3499 'ReleephRequestDifferentialCreateController' => 'ReleephController', 3498 3500 'ReleephRequestEditController' => 'ReleephController', 3499 - 'ReleephRequestEditor' => 'PhabricatorEditor', 3500 3501 'ReleephRequestEvent' => 'ReleephDAO', 3501 - 'ReleephRequestEventListView' => 'AphrontView', 3502 3502 'ReleephRequestException' => 'Exception', 3503 3503 'ReleephRequestHeaderListView' => 'AphrontView', 3504 3504 'ReleephRequestHeaderView' => 'AphrontView', 3505 3505 'ReleephRequestIntentsView' => 'AphrontView', 3506 + 'ReleephRequestReplyHandler' => 'PhabricatorMailReplyHandler', 3506 3507 'ReleephRequestStatusView' => 'AphrontView', 3508 + 'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction', 3509 + 'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment', 3510 + 'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3511 + 'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor', 3507 3512 'ReleephRequestTypeaheadControl' => 'AphrontFormControl', 3508 3513 'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController', 3509 3514 'ReleephRequestViewController' => 'ReleephController',
+4 -2
src/applications/releeph/application/PhabricatorApplicationReleeph.php
··· 58 58 ), 59 59 'request/' => array( 60 60 '(?P<requestID>[1-9]\d*)/' => 'ReleephRequestViewController', 61 - 'create/' => 'ReleephRequestCreateController', 61 + 'create/' => 'ReleephRequestEditController', 62 62 'differentialcreate/' => array( 63 63 'D(?P<diffRevID>[1-9]\d*)' => 64 64 'ReleephRequestDifferentialCreateController', ··· 69 69 'ReleephRequestActionController', 70 70 'typeahead/' => 71 71 'ReleephRequestTypeaheadController', 72 + 'comment/(?P<requestID>[1-9]\d*)/' => 73 + 'ReleephRequestCommentController', 72 74 ), 73 75 74 76 // Branch navigation made pretty, as it's the most common: 75 77 '(?P<projectName>[^/]+)/(?P<branchName>[^/]+)/' => array( 76 78 '' => 'ReleephBranchViewController', 77 79 'edit/' => 'ReleephBranchEditController', 78 - 'request/' => 'ReleephRequestCreateController', 80 + 'request/' => 'ReleephRequestEditController', 79 81 '(?P<action>close|re-open)/' => 'ReleephBranchAccessController', 80 82 ), 81 83 )
+32 -6
src/applications/releeph/conduit/ConduitAPI_releeph_request_Method.php
··· 27 27 } 28 28 29 29 protected function execute(ConduitAPIRequest $request) { 30 + $user = $request->getUser(); 30 31 $branch_phid = $request->getValue('branchPHID'); 31 32 $releeph_branch = id(new ReleephBranch()) 32 33 ->loadOneWhere('phid = %s', $branch_phid); ··· 74 75 foreach ($requested_commits as $thing => $commit) { 75 76 $phid = $commit->getPHID(); 76 77 $handles = id(new PhabricatorObjectHandleData(array($phid))) 77 - ->setViewer($request->getUser()) 78 + ->setViewer($user) 78 79 ->loadHandles(); 79 80 $name = id($handles[$phid])->getName(); 80 81 ··· 84 85 if ($existing_releeph_request) { 85 86 $releeph_request = $existing_releeph_request; 86 87 } else { 87 - $releeph_request = new ReleephRequest(); 88 + $releeph_request = id(new ReleephRequest()) 89 + ->setRequestUserPHID($user->getPHID()) 90 + ->setBranchID($releeph_branch->getID()) 91 + ->setInBranch(0); 92 + 93 + $xactions = array(); 94 + 95 + $xactions[] = id(new ReleephRequestTransaction()) 96 + ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST) 97 + ->setNewValue($commit->getPHID()); 98 + 99 + $xactions[] = id(new ReleephRequestTransaction()) 100 + ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT) 101 + ->setMetadataValue('userPHID', $user->getPHID()) 102 + ->setMetadataValue( 103 + 'isAuthoritative', 104 + $releeph_project->isAuthoritative($user)) 105 + ->setNewValue(ReleephRequest::INTENT_WANT); 106 + 88 107 foreach ($fields as $field) { 89 108 if (!$field->isEditable()) { 90 109 continue; ··· 97 116 ->setErrorDescription($ex->getMessage()); 98 117 } 99 118 } 100 - id(new ReleephRequestEditor($releeph_request)) 101 - ->setActor($request->getUser()) 102 - ->create($commit, $releeph_branch); 119 + 120 + $editor = id(new ReleephRequestTransactionalEditor()) 121 + ->setActor($user) 122 + ->setContinueOnNoEffect(true) 123 + ->setContentSource( 124 + PhabricatorContentSource::newForSource( 125 + PhabricatorContentSource::SOURCE_CONDUIT, 126 + array())); 127 + 128 + $editor->applyTransactions($releeph_request, $xactions); 103 129 } 104 130 105 131 $releeph_branch->populateReleephRequestHandles( 106 - $request->getUser(), 132 + $user, 107 133 array($releeph_request)); 108 134 $rq_handles = $releeph_request->getHandles(); 109 135 $requestor_phid = $releeph_request->getRequestUserPHID();
+30 -3
src/applications/releeph/conduit/work/ConduitAPI_releephwork_record_Method.php
··· 7 7 return self::METHOD_STATUS_UNSTABLE; 8 8 } 9 9 10 + /** 11 + * Record that a request was committed locally, and is about to be pushed to 12 + * the remote repository. 13 + * 14 + * This lets us mark a ReleephRequest as being in a branch in real time so 15 + * that no one else tries to pick it. 16 + * 17 + * When the daemons discover this commit in the repository with 18 + * DifferentialReleephRequestFieldSpecification, we'll be able to record the 19 + * commit's PHID as well. That process is slow though, and we don't want to 20 + * wait a whole minute before marking something as cleanly picked or 21 + * reverted. 22 + */ 10 23 public function getMethodDescription() { 11 - return "Wrapper to ReleephRequestEditor->recordSuccessfulCommit()."; 24 + return "Record whether we committed a pick or revert ". 25 + "to the upstream repository."; 12 26 } 13 27 14 28 public function defineParamTypes() { ··· 34 48 $releeph_request = id(new ReleephRequest()) 35 49 ->loadOneWhere('phid = %s', $request->getValue('requestPHID')); 36 50 37 - id(new ReleephRequestEditor($releeph_request)) 51 + $xactions = array(); 52 + 53 + $xactions[] = id(new ReleephRequestTransaction()) 54 + ->setTransactionType(ReleephRequestTransaction::TYPE_COMMIT) 55 + ->setMetadataValue('action', $action) 56 + ->setNewValue($new_commit_id); 57 + 58 + $editor = id(new ReleephRequestTransactionalEditor()) 38 59 ->setActor($request->getUser()) 39 - ->recordSuccessfulCommit($action, $new_commit_id); 60 + ->setContinueOnNoEffect(true) 61 + ->setContentSource( 62 + PhabricatorContentSource::newForSource( 63 + PhabricatorContentSource::SOURCE_CONDUIT, 64 + array())); 65 + 66 + $editor->applyTransactions($releeph_request, $xactions); 40 67 } 41 68 42 69 }
+17 -3
src/applications/releeph/conduit/work/ConduitAPI_releephwork_recordpickstatus_Method.php
··· 8 8 } 9 9 10 10 public function getMethodDescription() { 11 - return "Wrapper to ReleephRequestEditor->changePickStatus()."; 11 + return "Record whether a pick or revert was successful or not."; 12 12 } 13 13 14 14 public function defineParamTypes() { ··· 55 55 $releeph_request = id(new ReleephRequest()) 56 56 ->loadOneWhere('phid = %s', $request->getValue('requestPHID')); 57 57 58 - id(new ReleephRequestEditor($releeph_request)) 58 + $editor = id(new ReleephRequestTransactionalEditor()) 59 59 ->setActor($request->getUser()) 60 - ->changePickStatus($pick_status, $dry_run, $details); 60 + ->setContinueOnNoEffect(true) 61 + ->setContentSource( 62 + PhabricatorContentSource::newForSource( 63 + PhabricatorContentSource::SOURCE_CONDUIT, 64 + array())); 65 + 66 + $xactions = array(); 67 + 68 + $xactions[] = id(new ReleephRequestTransaction()) 69 + ->setTransactionType(ReleephRequestTransaction::TYPE_PICK_STATUS) 70 + ->setMetadataValue('dryRun', $dry_run) 71 + ->setMetadataValue('details', $details) 72 + ->setNewValue($pick_status); 73 + 74 + $editor->applyTransactions($releeph_request, $xactions); 61 75 } 62 76 63 77 }
+45 -7
src/applications/releeph/controller/request/ReleephRequestActionController.php
··· 12 12 public function processRequest() { 13 13 $request = $this->getRequest(); 14 14 15 + $releeph_project = $this->getReleephProject(); 15 16 $releeph_branch = $this->getReleephBranch(); 16 17 $releeph_request = $this->getReleephRequest(); 17 18 ··· 24 25 25 26 $origin_uri = $releeph_request->loadReleephBranch()->getURI(); 26 27 27 - $editor = id(new ReleephRequestEditor($releeph_request)) 28 - ->setActor($user); 28 + $editor = id(new ReleephRequestTransactionalEditor()) 29 + ->setActor($user) 30 + ->setContinueOnNoEffect(true) 31 + ->setContentSource( 32 + PhabricatorContentSource::newForSource( 33 + PhabricatorContentSource::SOURCE_WEB, 34 + array( 35 + 'ip' => $request->getRemoteAddr(), 36 + ))); 37 + 38 + $xactions = array(); 29 39 30 40 switch ($action) { 31 41 case 'want': ··· 34 44 'want' => ReleephRequest::INTENT_WANT, 35 45 'pass' => ReleephRequest::INTENT_PASS); 36 46 $intent = $action_map[$action]; 37 - $editor->changeUserIntent($user, $intent); 47 + $xactions[] = id(new ReleephRequestTransaction()) 48 + ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT) 49 + ->setMetadataValue( 50 + 'isAuthoritative', 51 + $releeph_project->isAuthoritative($user)) 52 + ->setNewValue($intent); 38 53 break; 39 54 40 55 case 'mark-manually-picked': 41 - $editor->markManuallyActioned('pick'); 42 - break; 43 - 44 56 case 'mark-manually-reverted': 45 - $editor->markManuallyActioned('revert'); 57 + if (!$releeph_project->isAuthoritative($user)) { 58 + throw new Exception( 59 + "Bug! Only authoritative users (pushers, or users in pusherless ". 60 + "Releeph projects) can manually change a request's in-branch ". 61 + "status!"); 62 + } 63 + 64 + if ($action === 'mark-manually-picked') { 65 + $in_branch = 1; 66 + $intent = ReleephRequest::INTENT_WANT; 67 + } else { 68 + $in_branch = 0; 69 + $intent = ReleephRequest::INTENT_PASS; 70 + } 71 + 72 + $xactions[] = id(new ReleephRequestTransaction()) 73 + ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT) 74 + ->setMetadataValue('isManual', true) 75 + ->setMetadataValue('isAuthoritative', true) 76 + ->setNewValue($intent); 77 + 78 + $xactions[] = id(new ReleephRequestTransaction()) 79 + ->setTransactionType(ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH) 80 + ->setNewValue($in_branch); 81 + 46 82 break; 47 83 48 84 default: 49 85 throw new Exception("unknown or unimplemented action {$action}"); 50 86 } 87 + 88 + $editor->applyTransactions($releeph_request, $xactions); 51 89 52 90 // If we're adding a new user to userIntents, we'll have to re-populate 53 91 // request handles to load that user's data.
+63
src/applications/releeph/controller/request/ReleephRequestCommentController.php
··· 1 + <?php 2 + 3 + final class ReleephRequestCommentController 4 + extends ReleephController { 5 + 6 + public function processRequest() { 7 + $request = $this->getRequest(); 8 + $user = $request->getUser(); 9 + 10 + $rq = $this->getReleephRequest(); 11 + 12 + if (!$request->isFormPost()) { 13 + return new Aphront400Response(); 14 + } 15 + 16 + $is_preview = $request->isPreviewRequest(); 17 + $draft = PhabricatorDraft::buildFromRequest($request); 18 + 19 + $view_uri = $this->getApplicationURI('/RQ'.$rq->getID()); 20 + 21 + $xactions = array(); 22 + $xactions[] = id(new ReleephRequestTransaction()) 23 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 24 + ->attachComment( 25 + id(new ReleephRequestTransactionComment()) 26 + ->setContent($request->getStr('comment'))); 27 + 28 + $editor = id(new ReleephRequestTransactionalEditor()) 29 + ->setActor($user) 30 + ->setContinueOnNoEffect($request->isContinueRequest()) 31 + ->setContentSource( 32 + PhabricatorContentSource::newForSource( 33 + PhabricatorContentSource::SOURCE_WEB, 34 + array( 35 + 'ip' => $request->getRemoteAddr(), 36 + ))) 37 + ->setIsPreview($is_preview); 38 + 39 + try { 40 + $xactions = $editor->applyTransactions($rq, $xactions); 41 + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { 42 + return id(new PhabricatorApplicationTransactionNoEffectResponse()) 43 + ->setCancelURI($view_uri) 44 + ->setException($ex); 45 + } 46 + 47 + if ($draft) { 48 + $draft->replaceOrDelete(); 49 + } 50 + 51 + if ($request->isAjax()) { 52 + return id(new PhabricatorApplicationTransactionResponse()) 53 + ->setViewer($user) 54 + ->setTransactions($xactions) 55 + ->setIsPreview($is_preview) 56 + ->setAnchorOffset($request->getStr('anchor')); 57 + } else { 58 + return id(new AphrontRedirectResponse()) 59 + ->setURI($view_uri); 60 + } 61 + } 62 + 63 + }
-165
src/applications/releeph/controller/request/ReleephRequestCreateController.php
··· 1 - <?php 2 - 3 - final class ReleephRequestCreateController extends ReleephController { 4 - 5 - const MAX_SUMMARY_LENGTH = 70; 6 - 7 - public function processRequest() { 8 - $request = $this->getRequest(); 9 - 10 - // We arrived via /releeph/request/create/?branchID=$id 11 - $releeph_branch_id = $request->getInt('branchID'); 12 - if ($releeph_branch_id) { 13 - $releeph_branch = id(new ReleephBranch())->load($releeph_branch_id); 14 - } else { 15 - // We arrived via /releeph/$project/$branch/request. 16 - // 17 - // If this throws an Exception, then somethind weird happened. 18 - $releeph_branch = $this->getReleephBranch(); 19 - } 20 - 21 - $releeph_project = $releeph_branch->loadReleephProject(); 22 - $repo = $releeph_project->loadPhabricatorRepository(); 23 - 24 - $request_identifier = $request->getStr('requestIdentifierRaw'); 25 - $e_request_identifier = true; 26 - 27 - $releeph_request = new ReleephRequest(); 28 - 29 - $errors = array(); 30 - 31 - $selector = $releeph_project->getReleephFieldSelector(); 32 - $fields = $selector->getFieldSpecifications(); 33 - foreach ($fields as $field) { 34 - $field 35 - ->setReleephProject($releeph_project) 36 - ->setReleephBranch($releeph_branch) 37 - ->setReleephRequest($releeph_request); 38 - } 39 - 40 - if ($request->isFormPost()) { 41 - foreach ($fields as $field) { 42 - if ($field->isEditable()) { 43 - try { 44 - $field->setValueFromAphrontRequest($request); 45 - } catch (ReleephFieldParseException $ex) { 46 - $errors[] = $ex->getMessage(); 47 - } 48 - } 49 - } 50 - 51 - $pr_commit = null; 52 - $finder = id(new ReleephCommitFinder()) 53 - ->setReleephProject($releeph_project); 54 - try { 55 - $pr_commit = $finder->fromPartial($request_identifier); 56 - } catch (Exception $e) { 57 - $e_request_identifier = pht('Invalid'); 58 - $errors[] = 59 - pht('Request %s is probably not a valid commit', $request_identifier); 60 - $errors[] = $e->getMessage(); 61 - } 62 - 63 - $pr_commit_data = null; 64 - if (!$errors) { 65 - $pr_commit_data = $pr_commit->loadCommitData(); 66 - if (!$pr_commit_data) { 67 - $e_request_identifier = pht('Not parsed yet'); 68 - $errors[] = pht('The requested commit hasn\'t been parsed yet.'); 69 - } 70 - } 71 - 72 - if (!$errors) { 73 - $existing = id(new ReleephRequest()) 74 - ->loadOneWhere('requestCommitPHID = %s AND branchID = %d', 75 - $pr_commit->getPHID(), $releeph_branch->getID()); 76 - 77 - if ($existing) { 78 - return id(new AphrontRedirectResponse()) 79 - ->setURI('/releeph/request/edit/'.$existing->getID(). 80 - '?existing=1'); 81 - } 82 - 83 - id(new ReleephRequestEditor($releeph_request)) 84 - ->setActor($request->getUser()) 85 - ->create($pr_commit, $releeph_branch); 86 - 87 - return id(new AphrontRedirectResponse()) 88 - ->setURI($releeph_branch->getURI()); 89 - } 90 - } 91 - 92 - $error_view = null; 93 - if ($errors) { 94 - $error_view = new AphrontErrorView(); 95 - $error_view->setErrors($errors); 96 - $error_view->setTitle(pht('Form Errors')); 97 - } 98 - 99 - // For the typeahead 100 - $branch_cut_point = id(new PhabricatorRepositoryCommit()) 101 - ->loadOneWhere( 102 - 'phid = %s', 103 - $releeph_branch->getCutPointCommitPHID()); 104 - 105 - // Build the form 106 - $form = id(new AphrontFormView()) 107 - ->setUser($request->getUser()); 108 - 109 - $origin = null; 110 - $diff_rev_id = $request->getStr('D'); 111 - if ($diff_rev_id) { 112 - $diff_rev = id(new DifferentialRevision())->load($diff_rev_id); 113 - $origin = '/D'.$diff_rev->getID(); 114 - $title = sprintf( 115 - 'D%d: %s', 116 - $diff_rev_id, 117 - $diff_rev->getTitle()); 118 - $form 119 - ->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id) 120 - ->appendChild( 121 - id(new AphrontFormStaticControl()) 122 - ->setLabel(pht('Diff')) 123 - ->setValue($title)); 124 - } else { 125 - $origin = $releeph_branch->getURI(); 126 - $form->appendChild( 127 - id(new ReleephRequestTypeaheadControl()) 128 - ->setName('requestIdentifierRaw') 129 - ->setLabel('Commit ID') 130 - ->setRepo($repo) 131 - ->setValue($request_identifier) 132 - ->setError($e_request_identifier) 133 - ->setStartTime($branch_cut_point->getEpoch()) 134 - ->setCaption( 135 - pht('Start typing to autocomplete on commit title, '. 136 - 'or give a Phabricator commit identifier like rFOO1234'))); 137 - } 138 - 139 - // Fields 140 - foreach ($fields as $field) { 141 - if ($field->isEditable()) { 142 - $control = $field->renderEditControl($request); 143 - $form->appendChild($control); 144 - } 145 - } 146 - 147 - $form 148 - ->appendChild( 149 - id(new AphrontFormSubmitControl()) 150 - ->addCancelButton($origin) 151 - ->setValue(pht('Request'))); 152 - 153 - $panel = id(new AphrontPanelView()) 154 - ->setHeader( 155 - pht('Request for %s', 156 - $releeph_branch->getDisplayNameWithDetail())) 157 - ->setWidth(AphrontPanelView::WIDTH_FORM) 158 - ->appendChild($form); 159 - 160 - return $this->buildStandardPageResponse( 161 - array($error_view, $panel), 162 - array('title' => pht('Request Pick'))); 163 - } 164 - 165 - }
+2 -2
src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php
··· 91 91 } 92 92 93 93 private function buildReleephRequestURI(ReleephBranch $branch) { 94 - return id(new PhutilURI('/releeph/request/create/')) 95 - ->setQueryParam('branchID', $branch->getID()) 94 + $uri = $branch->getURI('request/'); 95 + return id(new PhutilURI($uri)) 96 96 ->setQueryParam('D', $this->revision->getID()); 97 97 } 98 98
+202 -60
src/applications/releeph/controller/request/ReleephRequestEditController.php
··· 2 2 3 3 final class ReleephRequestEditController extends ReleephController { 4 4 5 + private $id; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->id = idx($data, 'requestID'); 9 + parent::willProcessRequest($data); 10 + } 11 + 5 12 public function processRequest() { 6 13 $request = $this->getRequest(); 14 + $user = $request->getUser(); 7 15 8 - $releeph_branch = $this->getReleephBranch(); 9 - $releeph_request = $this->getReleephRequest(); 16 + $releeph_project = $this->getReleephProject(); 17 + $releeph_branch = $this->getReleephBranch(); 10 18 11 - $releeph_branch->populateReleephRequestHandles( 12 - $request->getUser(), array($releeph_request)); 19 + $request_identifier = $request->getStr('requestIdentifierRaw'); 20 + $e_request_identifier = true; 13 21 14 - $phids = array(); 15 - $phids[] = $releeph_request->getRequestCommitPHID(); 16 - $phids[] = $releeph_request->getRequestUserPHID(); 17 - 18 - $handles = id(new PhabricatorObjectHandleData($phids)) 19 - ->setViewer($request->getUser()) 20 - ->loadHandles(); 21 - 22 - $age_string = phabricator_format_relative_time( 23 - time() - $releeph_request->getDateCreated()); 22 + // Load the RQ we're editing, or create a new one 23 + if ($this->id) { 24 + $rq = id(new ReleephRequest())->load($this->id); 25 + $is_edit = true; 26 + } else { 27 + $is_edit = false; 28 + $rq = id(new ReleephRequest()) 29 + ->setRequestUserPHID($user->getPHID()) 30 + ->setBranchID($releeph_branch->getID()) 31 + ->setInBranch(0); 32 + } 24 33 25 - // Warn the user if we see this 26 - $notice_view = null; 27 - if ($request->getInt('existing')) { 28 - $notice_messages = array( 29 - 'You are editing an existing pick request!', 30 - hsprintf( 31 - "Requested %s ago by %s", 32 - $age_string, 33 - $handles[$releeph_request->getRequestUserPHID()]->renderLink()) 34 - ); 35 - $notice_view = id(new AphrontErrorView()) 36 - ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 37 - ->setErrors($notice_messages); 34 + // Load all the ReleephFieldSpecifications 35 + $selector = $this->getReleephProject()->getReleephFieldSelector(); 36 + $fields = $selector->getFieldSpecifications(); 37 + foreach ($fields as $field) { 38 + $field 39 + ->setReleephProject($releeph_project) 40 + ->setReleephBranch($releeph_branch) 41 + ->setReleephRequest($rq); 38 42 } 39 43 40 44 // <aidehua> epriestley: Is it common to pass around a referer URL to ··· 43 47 // rather than the full URL. 44 48 switch ($request->getStr('origin')) { 45 49 case 'request': 46 - $origin_uri = '/RQ'.$releeph_request->getID(); 50 + $origin_uri = '/RQ'.$rq->getID(); 47 51 break; 48 52 49 53 case 'branch': 50 54 default: 51 - $origin_uri = $releeph_request->loadReleephBranch()->getURI(); 55 + $origin_uri = $releeph_branch->getURI(); 52 56 break; 53 57 } 54 58 59 + // Make edits 55 60 $errors = array(); 61 + if ($request->isFormPost()) { 62 + $xactions = array(); 56 63 57 - $selector = $this->getReleephProject()->getReleephFieldSelector(); 58 - $fields = $selector->getFieldSpecifications(); 59 - foreach ($fields as $field) { 60 - $field 61 - ->setReleephProject($this->getReleephProject()) 62 - ->setReleephBranch($this->getReleephBranch()) 63 - ->setReleephRequest($this->getReleephRequest()); 64 - } 64 + // The commit-identifier being requested... 65 + if (!$is_edit) { 66 + if ($request_identifier === 67 + ReleephRequestTypeaheadControl::PLACEHOLDER) { 65 68 66 - if ($request->isFormPost()) { 67 - foreach ($fields as $field) { 68 - if ($field->isEditable()) { 69 + $errors[] = "No commit ID was provided."; 70 + $e_request_identifier = 'Required'; 71 + } else { 72 + $pr_commit = null; 73 + $finder = id(new ReleephCommitFinder()) 74 + ->setReleephProject($releeph_project); 69 75 try { 70 - $field->setValueFromAphrontRequest($request); 71 - } catch (ReleephFieldParseException $ex) { 72 - $errors[] = $ex->getMessage(); 76 + $pr_commit = $finder->fromPartial($request_identifier); 77 + } catch (Exception $e) { 78 + $e_request_identifier = 'Invalid'; 79 + $errors[] = 80 + "Request {$request_identifier} is probably not a valid commit"; 81 + $errors[] = $e->getMessage(); 82 + } 83 + 84 + $pr_commit_data = null; 85 + if (!$errors) { 86 + $pr_commit_data = $pr_commit->loadCommitData(); 87 + if (!$pr_commit_data) { 88 + $e_request_identifier = 'Not parsed yet'; 89 + $errors[] = "The requested commit hasn't been parsed yet."; 90 + } 73 91 } 74 92 } 93 + 94 + if (!$errors) { 95 + $existing = id(new ReleephRequest()) 96 + ->loadOneWhere('requestCommitPHID = %s AND branchID = %d', 97 + $pr_commit->getPHID(), $releeph_branch->getID()); 98 + if ($existing) { 99 + return id(new AphrontRedirectResponse()) 100 + ->setURI('/releeph/request/edit/'.$existing->getID(). 101 + '?existing=1'); 102 + } 103 + 104 + $xactions[] = id(new ReleephRequestTransaction()) 105 + ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST) 106 + ->setNewValue($pr_commit->getPHID()); 107 + 108 + $xactions[] = id(new ReleephRequestTransaction()) 109 + ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT) 110 + // To help hide these implicit intents... 111 + ->setMetadataValue('isRQCreate', true) 112 + ->setMetadataValue('userPHID', $user->getPHID()) 113 + ->setMetadataValue( 114 + 'isAuthoritative', 115 + $releeph_project->isAuthoritative($user)) 116 + ->setNewValue(ReleephRequest::INTENT_WANT); 117 + } 75 118 } 76 119 77 120 if (!$errors) { 78 - $releeph_request->save(); 121 + foreach ($fields as $field) { 122 + if ($field->isEditable()) { 123 + try { 124 + $data = $request->getRequestData(); 125 + $value = idx($data, $field->getRequiredStorageKey()); 126 + $field->validate($value); 127 + $xactions[] = id(new ReleephRequestTransaction()) 128 + ->setTransactionType(ReleephRequestTransaction::TYPE_EDIT_FIELD) 129 + ->setMetadataValue('fieldClass', get_class($field)) 130 + ->setNewValue($value); 131 + } catch (ReleephFieldParseException $ex) { 132 + $errors[] = $ex->getMessage(); 133 + } 134 + } 135 + } 136 + } 137 + 138 + if (!$errors) { 139 + $editor = id(new ReleephRequestTransactionalEditor()) 140 + ->setActor($user) 141 + ->setContinueOnNoEffect(true) 142 + ->setContentSource( 143 + PhabricatorContentSource::newForSource( 144 + PhabricatorContentSource::SOURCE_WEB, 145 + array( 146 + 'ip' => $request->getRemoteAddr(), 147 + ))); 148 + $editor->applyTransactions($rq, $xactions); 79 149 return id(new AphrontRedirectResponse())->setURI($origin_uri); 80 150 } 81 151 } 82 152 153 + $releeph_branch->populateReleephRequestHandles($user, array($rq)); 154 + $handles = $rq->getHandles(); 155 + 156 + $age_string = ''; 157 + if ($is_edit) { 158 + $age_string = phabricator_format_relative_time( 159 + time() - $rq->getDateCreated()) . ' ago'; 160 + } 161 + 162 + // Warn the user if we've been redirected here because we tried to 163 + // re-request something. 164 + $notice_view = null; 165 + if ($request->getInt('existing')) { 166 + $notice_messages = array( 167 + 'You are editing an existing pick request!', 168 + hsprintf( 169 + "Requested %s by %s", 170 + $age_string, 171 + $handles[$rq->getRequestUserPHID()]->renderLink()) 172 + ); 173 + $notice_view = id(new AphrontErrorView()) 174 + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 175 + ->setErrors($notice_messages); 176 + } 177 + 83 178 /** 84 179 * Build the rest of the page 85 180 */ ··· 91 186 } 92 187 93 188 $form = id(new AphrontFormView()) 94 - ->setUser($request->getUser()) 95 - ->appendChild( 96 - id(new AphrontFormMarkupControl()) 97 - ->setLabel('Original Commit') 98 - ->setValue( 99 - $handles[$releeph_request->getRequestCommitPHID()]->renderLink())) 100 - ->appendChild( 101 - id(new AphrontFormMarkupControl()) 102 - ->setLabel('Requestor') 103 - ->setValue(hsprintf( 104 - '%s %s ago', 105 - $handles[$releeph_request->getRequestUserPHID()]->renderLink(), 106 - $age_string))); 189 + ->setUser($request->getUser()); 190 + 191 + if ($is_edit) { 192 + $form 193 + ->appendChild( 194 + id(new AphrontFormMarkupControl()) 195 + ->setLabel('Original Commit') 196 + ->setValue( 197 + $handles[$rq->getRequestCommitPHID()]->renderLink())) 198 + ->appendChild( 199 + id(new AphrontFormMarkupControl()) 200 + ->setLabel('Requestor') 201 + ->setValue(hsprintf( 202 + '%s %s', 203 + $handles[$rq->getRequestUserPHID()]->renderLink(), 204 + $age_string))); 205 + } else { 206 + $origin = null; 207 + $diff_rev_id = $request->getStr('D'); 208 + if ($diff_rev_id) { 209 + $diff_rev = id(new DifferentialRevision())->load($diff_rev_id); 210 + $origin = '/D'.$diff_rev->getID(); 211 + $title = sprintf( 212 + 'D%d: %s', 213 + $diff_rev_id, 214 + $diff_rev->getTitle()); 215 + $form 216 + ->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id) 217 + ->appendChild( 218 + id(new AphrontFormStaticControl()) 219 + ->setLabel('Diff') 220 + ->setValue($title)); 221 + } else { 222 + $origin = $releeph_branch->getURI(); 223 + $repo = $releeph_project->loadPhabricatorRepository(); 224 + $branch_cut_point = id(new PhabricatorRepositoryCommit()) 225 + ->loadOneWhere( 226 + 'phid = %s', 227 + $releeph_branch->getCutPointCommitPHID()); 228 + $form->appendChild( 229 + id(new ReleephRequestTypeaheadControl()) 230 + ->setName('requestIdentifierRaw') 231 + ->setLabel('Commit ID') 232 + ->setRepo($repo) 233 + ->setValue($request_identifier) 234 + ->setError($e_request_identifier) 235 + ->setStartTime($branch_cut_point->getEpoch()) 236 + ->setCaption( 237 + 'Start typing to autocomplete on commit title, '. 238 + 'or give a Phabricator commit identifier like rFOO1234')); 239 + } 240 + } 107 241 108 242 // Fields 109 243 foreach ($fields as $field) { ··· 113 247 } 114 248 } 115 249 250 + if ($is_edit) { 251 + $title = pht('Edit Releeph Request'); 252 + $submit_name = pht('Save'); 253 + } else { 254 + $title = pht('Create Releeph Request'); 255 + $submit_name = pht('Create'); 256 + } 257 + 116 258 $form 117 259 ->appendChild( 118 260 id(new AphrontFormSubmitControl()) 119 261 ->addCancelButton($origin_uri, 'Cancel') 120 - ->setValue('Save')); 262 + ->setValue($submit_name)); 121 263 122 264 $panel = id(new AphrontPanelView()) 123 - ->setHeader('Edit Pick Request') 265 + ->setHeader($title) 124 266 ->setWidth(AphrontPanelView::WIDTH_FORM) 125 267 ->appendChild($form); 126 268 127 269 return $this->buildStandardPageResponse( 128 270 array($notice_view, $error_view, $panel), 129 - array('title', 'Edit Pick Request')); 271 + array('title', $title)); 130 272 } 131 273 }
+34 -31
src/applications/releeph/controller/request/ReleephRequestViewController.php
··· 29 29 ->setReloadOnStateChange(true) 30 30 ->setOriginType('request'); 31 31 32 - $events = $releeph_request->loadEvents(); 33 - $phids = array_mergev(mpull($events, 'extractPHIDs')); 34 - $handles = id(new PhabricatorObjectHandleData($phids)) 35 - ->setViewer($request->getUser()) 36 - ->loadHandles(); 32 + $user = $request->getUser(); 33 + 34 + $engine = id(new PhabricatorMarkupEngine()) 35 + ->setViewer($user); 37 36 38 - $rq_event_list_view = 39 - id(new ReleephRequestEventListView()) 40 - ->setUser($request->getUser()) 41 - ->setEvents($events) 42 - ->setHandles($handles); 37 + $xactions = id(new ReleephRequestTransactionQuery()) 38 + ->setViewer($user) 39 + ->withObjectPHIDs(array($releeph_request->getPHID())) 40 + ->execute(); 43 41 44 - // Handle comment submit 45 - $origin_uri = '/RQ'.$releeph_request->getID(); 46 - if ($request->isFormPost()) { 47 - id(new ReleephRequestEditor($releeph_request)) 48 - ->setActor($request->getUser()) 49 - ->addComment($request->getStr('comment')); 50 - return id(new AphrontRedirectResponse())->setURI($origin_uri); 42 + foreach ($xactions as $xaction) { 43 + if ($xaction->getComment()) { 44 + $engine->addObject( 45 + $xaction->getComment(), 46 + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 47 + } 51 48 } 49 + $engine->process(); 52 50 53 - $form = id(new AphrontFormView()) 51 + $timeline = id(new PhabricatorApplicationTransactionView()) 54 52 ->setUser($request->getUser()) 55 - ->appendChild( 56 - id(new AphrontFormTextAreaControl()) 57 - ->setName('comment')) 58 - ->appendChild( 59 - id(new AphrontFormSubmitControl()) 60 - ->addCancelButton($origin_uri, 'Cancel') 61 - ->setValue("Submit")); 53 + ->setTransactions($xactions) 54 + ->setMarkupEngine($engine); 55 + 56 + $add_comment_header = id(new PhabricatorHeaderView()) 57 + ->setHeader('Plea or yield'); 58 + 59 + $draft = PhabricatorDraft::newFromUserAndKey( 60 + $user, 61 + $releeph_request->getPHID()); 62 62 63 - $rq_comment_form = id(new AphrontPanelView()) 64 - ->setHeader('Add a comment') 65 - ->setWidth(AphrontPanelView::WIDTH_FULL) 66 - ->appendChild($form); 63 + $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) 64 + ->setUser($user) 65 + ->setDraft($draft) 66 + ->setAction($this->getApplicationURI( 67 + '/request/comment/'.$releeph_request->getID().'/')) 68 + ->setSubmitButtonName('Comment'); 67 69 68 70 $title = hsprintf("RQ%d: %s", 69 71 $releeph_request->getID(), ··· 88 90 $crumbs, 89 91 array( 90 92 $rq_view, 91 - $rq_event_list_view, 92 - $rq_comment_form 93 + $timeline, 94 + $add_comment_header, 95 + $add_comment_form, 93 96 ) 94 97 ), 95 98 array(
+19 -2
src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php
··· 268 268 $actor = id(new PhabricatorUser()) 269 269 ->loadOneWhere('phid = %s', $actor_phid); 270 270 271 - id(new ReleephRequestEditor($releeph_request)) 271 + $xactions = array(); 272 + 273 + $xactions[] = id(new ReleephRequestTransaction()) 274 + ->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY) 275 + ->setMetadataValue('action', $action) 276 + ->setMetadataValue('authorPHID', 277 + $data->getCommitDetail('authorPHID')) 278 + ->setMetadataValue('committerPHID', 279 + $data->getCommitDetail('committerPHID')) 280 + ->setNewValue($commit->getPHID()); 281 + 282 + $editor = id(new ReleephRequestTransactionalEditor()) 272 283 ->setActor($actor) 273 - ->discoverCommit($action, $commit, $data); 284 + ->setContinueOnNoEffect(true) 285 + ->setContentSource( 286 + PhabricatorContentSource::newForSource( 287 + PhabricatorContentSource::SOURCE_UNKNOWN, 288 + array())); 289 + 290 + $editor->applyTransactions($releeph_request, $xactions); 274 291 } 275 292 } 276 293
-402
src/applications/releeph/editor/ReleephRequestEditor.php
··· 1 - <?php 2 - 3 - /** 4 - * Provide methods for the common ways of creating and mutating a 5 - * ReleephRequest, sending email when something interesting happens. 6 - * 7 - * This class generates ReleephRequestEvents, and each type of event 8 - * (ReleephRequestEvent::TYPE_*) corresponds to one of the editor methods. 9 - * 10 - * The editor methods (except for create() use newEvent() and commit() to save 11 - * some code duplication. 12 - */ 13 - final class ReleephRequestEditor extends PhabricatorEditor { 14 - 15 - private $releephRequest; 16 - private $event; 17 - private $silentUpdate; 18 - 19 - public function __construct(ReleephRequest $rq) { 20 - $this->releephRequest = $rq; 21 - } 22 - 23 - public function setSilentUpdate($silent) { 24 - $this->silentUpdate = $silent; 25 - return $this; 26 - } 27 - 28 - 29 - /* -( ReleephRequest edit methods )---------------------------------------- */ 30 - 31 - /** 32 - * Request a PhabricatorRepositoryCommit to be committed to the given 33 - * ReleephBranch. 34 - */ 35 - public function create(PhabricatorRepositoryCommit $commit, 36 - ReleephBranch $branch) { 37 - 38 - // We can't use newEvent() / commit() abstractions, so do what those 39 - // helpers do manually. 40 - $requestor = $this->requireActor(); 41 - 42 - $rq = $this->releephRequest; 43 - $rq->openTransaction(); 44 - 45 - $rq 46 - ->setBranchID($branch->getID()) 47 - ->setRequestCommitPHID($commit->getPHID()) 48 - ->setInBranch(0) 49 - ->setRequestUserPHID($requestor->getPHID()) 50 - ->setUserIntent($requestor, ReleephRequest::INTENT_WANT) 51 - ->save(); 52 - 53 - $event = id(new ReleephRequestEvent()) 54 - ->setType(ReleephRequestEvent::TYPE_CREATE) 55 - ->setActorPHID($requestor->getPHID()) 56 - ->setStatusBefore(null) 57 - ->setStatusAfter($rq->getStatus()) 58 - ->setReleephRequestID($rq->getID()) 59 - ->setDetail('commitPHID', $commit->getPHID()) 60 - ->save(); 61 - 62 - $rq->saveTransaction(); 63 - 64 - // Mail 65 - if (!$this->silentUpdate) { 66 - $project = $this->releephRequest->loadReleephProject(); 67 - $mail = id(new ReleephRequestMail()) 68 - ->setReleephRequest($this->releephRequest) 69 - ->setReleephProject($project) 70 - ->setEvents(array($event)) 71 - ->setSenderAndRecipientPHID($requestor->getPHID()) 72 - ->addTos(ReleephRequestMail::ENT_ALL_PUSHERS) 73 - ->addCCs(ReleephRequestMail::ENT_REQUESTOR) 74 - ->send(); 75 - } 76 - } 77 - 78 - /** 79 - * Record whether the PhabricatorUser wants or passes on this request. 80 - */ 81 - public function changeUserIntent(PhabricatorUser $user, $intent) { 82 - $project = $this->releephRequest->loadReleephProject(); 83 - $is_pusher = $project->isPusher($user); 84 - 85 - $event = $this->newEvent() 86 - ->setType(ReleephRequestEvent::TYPE_USER_INTENT) 87 - ->setDetail('userPHID', $user->getPHID()) 88 - ->setDetail('wasPusher', $is_pusher) 89 - ->setDetail('newIntent', $intent); 90 - 91 - $this->releephRequest 92 - ->setUserIntent($user, $intent); 93 - 94 - $this->commit(); 95 - 96 - // Mail if this is 'interesting' 97 - if (!$this->silentUpdate && 98 - $event->getStatusBefore() != $event->getStatusAfter()) { 99 - 100 - $project = $this->releephRequest->loadReleephProject(); 101 - $mail = id(new ReleephRequestMail()) 102 - ->setReleephRequest($this->releephRequest) 103 - ->setReleephProject($project) 104 - ->setEvents(array($event)) 105 - ->setSenderAndRecipientPHID($this->requireActor()->getPHID()) 106 - ->addTos(ReleephRequestMail::ENT_REQUESTOR) 107 - ->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS) 108 - ->send(); 109 - } 110 - } 111 - 112 - /** 113 - * Record the results of someone trying to pick or revert a request in their 114 - * local repository, to give advance warning that something doesn't pick or 115 - * revert cleanly. 116 - */ 117 - public function changePickStatus($pick_status, $dry_run, $details) { 118 - $event = $this->newEvent() 119 - ->setType(ReleephRequestEvent::TYPE_PICK_STATUS) 120 - ->setDetail('newPickStatus', $pick_status) 121 - ->setDetail('commitDetails', $details); 122 - $this->releephRequest->setPickStatus($pick_status); 123 - $this->commit(); 124 - 125 - // Failures should generate an email 126 - if (!$this->silentUpdate && 127 - !$dry_run && 128 - ($pick_status == ReleephRequest::PICK_FAILED || 129 - $pick_status == ReleephRequest::REVERT_FAILED)) { 130 - 131 - $project = $this->releephRequest->loadReleephProject(); 132 - $mail = id(new ReleephRequestMail()) 133 - ->setReleephRequest($this->releephRequest) 134 - ->setReleephProject($project) 135 - ->setEvents(array($event)) 136 - ->setSenderAndRecipientPHID($this->requireActor()->getPHID()) 137 - ->addTos(ReleephRequestMail::ENT_REQUESTOR) 138 - ->addCCs(ReleephRequestMail::ENT_ACTORS) 139 - ->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS) 140 - ->send(); 141 - } 142 - } 143 - 144 - /** 145 - * Record that a request was committed locally, and is about to be pushed to 146 - * the remote repository. 147 - * 148 - * This lets us mark a ReleephRequest as being in a branch in real time so 149 - * that no one else tries to pick it. 150 - * 151 - * When the daemons discover this commit in the repository with 152 - * DifferentialReleephRequestFieldSpecification, we'll be able to recrod the 153 - * commit's PHID as well. That process is slow though, and 154 - * we don't want to wait a whole minute before marking something as cleanly 155 - * picked or reverted. 156 - */ 157 - public function recordSuccessfulCommit($action, $new_commit_id) { 158 - $table = $this->releephRequest; 159 - $table->openTransaction(); 160 - 161 - $actor = $this->requireActor(); 162 - 163 - $event = id(new ReleephRequestEvent()) 164 - ->setReleephRequestID($this->releephRequest->getID()) 165 - ->setActorPHID($actor->getPHID()) 166 - ->setType(ReleephRequestEvent::TYPE_COMMIT) 167 - ->setDetail('action', $action) 168 - ->setDetail('newCommitIdentifier', $new_commit_id) 169 - ->save(); 170 - 171 - switch ($action) { 172 - case 'pick': 173 - $this->releephRequest 174 - ->setInBranch(1) 175 - ->setPickStatus(ReleephRequest::PICK_OK) 176 - ->setCommitIdentifier($new_commit_id) 177 - ->setCommitPHID(null) 178 - ->save(); 179 - break; 180 - 181 - case 'revert': 182 - $this->releephRequest 183 - ->setInBranch(0) 184 - ->setPickStatus(ReleephRequest::REVERT_OK) 185 - ->setCommitIdentifier(null) 186 - ->setCommitPHID(null) 187 - ->save(); 188 - break; 189 - 190 - default: 191 - $table->killTransaction(); 192 - throw new Exception("Unknown action {$action}!"); 193 - break; 194 - } 195 - 196 - $table->saveTransaction(); 197 - 198 - // Don't spam people about local commits -- we'll do that with 199 - // discoverCommit() instead! 200 - } 201 - 202 - /** 203 - * Mark this request as picked or reverted based on discovering it in the 204 - * branch. We have a PhabricatorRepositoryCommit, so we're able to 205 - * setCommitPHID on the ReleephRequest (unlike recordSuccessfulCommit()). 206 - */ 207 - public function discoverCommit( 208 - $action, 209 - PhabricatorRepositoryCommit $commit, 210 - PhabricatorRepositoryCommitData $data) { 211 - 212 - $table = $this->releephRequest; 213 - $table->openTransaction(); 214 - $table->beginWriteLocking(); 215 - 216 - $past_events = id(new ReleephRequestEvent())->loadAllWhere( 217 - 'releephRequestID = %d AND type = %s', 218 - $this->releephRequest->getID(), 219 - ReleephRequestEvent::TYPE_DISCOVERY); 220 - 221 - foreach ($past_events as $past_event) { 222 - if ($past_event->getDetail('newCommitIdentifier') 223 - == $commit->getCommitIdentifier()) { 224 - 225 - // Avoid re-discovery if reparsing! 226 - $table->endWriteLocking(); 227 - $table->killTransaction(); 228 - return; 229 - } 230 - } 231 - 232 - $actor = $this->requireActor(); 233 - 234 - $event = id(new ReleephRequestEvent()) 235 - ->setReleephRequestID($this->releephRequest->getID()) 236 - ->setActorPHID($actor->getPHID()) 237 - ->setType(ReleephRequestEvent::TYPE_DISCOVERY) 238 - ->setDateCreated($commit->getEpoch()) 239 - ->setDetail('action', $action) 240 - ->setDetail('newCommitIdentifier', $commit->getCommitIdentifier()) 241 - ->setDetail('newCommitPHID', $commit->getPHID()) 242 - ->setDetail('authorPHID', $data->getCommitDetail('authorPHID')) 243 - ->setDetail('committerPHID', $data->getCommitDetail('committerPHID')) 244 - ->save(); 245 - 246 - switch ($action) { 247 - case 'pick': 248 - $this->releephRequest 249 - ->setInBranch(1) 250 - ->setPickStatus(ReleephRequest::PICK_OK) 251 - ->setCommitIdentifier($commit->getCommitIdentifier()) 252 - ->setCommitPHID($commit->getPHID()) 253 - ->save(); 254 - break; 255 - 256 - case 'revert': 257 - $this->releephRequest 258 - ->setInBranch(0) 259 - ->setPickStatus(ReleephRequest::REVERT_OK) 260 - ->setCommitIdentifier(null) 261 - ->setCommitPHID(null) 262 - ->save(); 263 - break; 264 - 265 - default: 266 - $table->killTransaction(); 267 - throw new Exception("Unknown action {$action}!"); 268 - break; 269 - } 270 - 271 - $table->endWriteLocking(); 272 - $table->saveTransaction(); 273 - 274 - // Mail 275 - if (!$this->silentUpdate) { 276 - $project = $this->releephRequest->loadReleephProject(); 277 - $mail = id(new ReleephRequestMail()) 278 - ->setReleephRequest($this->releephRequest) 279 - ->setReleephProject($project) 280 - ->setEvents(array($event)) 281 - ->setSenderAndRecipientPHID($this->requireActor()->getPHID()) 282 - ->addTos(ReleephRequestMail::ENT_REQUESTOR) 283 - ->addCCs(ReleephRequestMail::ENT_ACTORS) 284 - ->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS) 285 - ->send(); 286 - } 287 - } 288 - 289 - public function addComment($comment) { 290 - $event = $this->newEvent() 291 - ->setType(ReleephRequestEvent::TYPE_COMMENT) 292 - ->setDetail('comment', $comment); 293 - $this->commit(); 294 - 295 - // Mail 296 - if (!$this->silentUpdate) { 297 - $project = $this->releephRequest->loadReleephProject(); 298 - $mail = id(new ReleephRequestMail()) 299 - ->setReleephRequest($this->releephRequest) 300 - ->setReleephProject($project) 301 - ->setEvents(array($event)) 302 - ->setSenderAndRecipientPHID($this->requireActor()->getPHID()) 303 - ->addTos(ReleephRequestMail::ENT_REQUESTOR) 304 - ->addCCs(ReleephRequestMail::ENT_ACTORS) 305 - ->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS) 306 - ->send(); 307 - } 308 - } 309 - 310 - public function markManuallyActioned($action) { 311 - $event = $this->newEvent() 312 - ->setType(ReleephRequestEvent::TYPE_MANUAL_ACTION) 313 - ->setDetail('action', $action); 314 - 315 - $actor = $this->requireActor(); 316 - $project = $this->releephRequest->loadReleephProject(); 317 - $requestor_phid = $this->releephRequest->getRequestUserPHID(); 318 - if (!$project->isPusher($actor) && 319 - $actor->getPHID() !== $requestor_phid) { 320 - 321 - throw new Exception( 322 - "Only pushers or requestors can mark requests as ". 323 - "manually picked or reverted!"); 324 - } 325 - 326 - switch ($action) { 327 - case 'pick': 328 - $in_branch = true; 329 - $intent = ReleephRequest::INTENT_WANT; 330 - break; 331 - 332 - case 'revert': 333 - $in_branch = false; 334 - $intent = ReleephRequest::INTENT_PASS; 335 - break; 336 - 337 - default: 338 - throw new Exception("Unknown action {$action}!"); 339 - break; 340 - } 341 - 342 - $this->releephRequest 343 - ->setInBranch((int)$in_branch) 344 - ->setUserIntent($this->getActor(), $intent); 345 - 346 - $this->commit(); 347 - 348 - // Mail 349 - if (!$this->silentUpdate) { 350 - $project = $this->releephRequest->loadReleephProject(); 351 - $mail = id(new ReleephRequestMail()) 352 - ->setReleephRequest($this->releephRequest) 353 - ->setReleephProject($project) 354 - ->setEvents(array($event)) 355 - ->setSenderAndRecipientPHID($this->requireActor()->getPHID()) 356 - ->addTos(ReleephRequestMail::ENT_REQUESTOR) 357 - ->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS) 358 - ->send(); 359 - } 360 - } 361 - 362 - /* -( Implementation )----------------------------------------------------- */ 363 - 364 - /** 365 - * Create and return a new ReleephRequestEvent bound to the editor's 366 - * ReleephRequest, inside a transaction. 367 - * 368 - * When you call commit(), the event and this editor's ReleephRequest (along 369 - * with any changes you made to the ReleephRequest) are saved and the 370 - * transaction committed. 371 - */ 372 - private function newEvent() { 373 - $actor = $this->requireActor(); 374 - 375 - if ($this->event) { 376 - throw new Exception("You have already called newEvent()!"); 377 - } 378 - $rq = $this->releephRequest; 379 - $rq->openTransaction(); 380 - 381 - $this->event = id(new ReleephRequestEvent()) 382 - ->setReleephRequestID($rq->getID()) 383 - ->setActorPHID($actor->getPHID()) 384 - ->setStatusBefore($rq->getStatus()); 385 - 386 - return $this->event; 387 - } 388 - 389 - private function commit() { 390 - if (!$this->event) { 391 - throw new Exception("You must call newEvent first!"); 392 - } 393 - $rq = $this->releephRequest; 394 - $this->event 395 - ->setStatusAfter($rq->getStatus()) 396 - ->save(); 397 - $rq->save(); 398 - $rq->saveTransaction(); 399 - $this->event = null; 400 - } 401 - 402 - }
+300
src/applications/releeph/editor/ReleephRequestTransactionalEditor.php
··· 1 + <?php 2 + 3 + final class ReleephRequestTransactionalEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getTransactionTypes() { 7 + $types = parent::getTransactionTypes(); 8 + 9 + $types[] = PhabricatorTransactions::TYPE_COMMENT; 10 + $types[] = ReleephRequestTransaction::TYPE_COMMIT; 11 + $types[] = ReleephRequestTransaction::TYPE_DISCOVERY; 12 + $types[] = ReleephRequestTransaction::TYPE_EDIT_FIELD; 13 + $types[] = ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH; 14 + $types[] = ReleephRequestTransaction::TYPE_PICK_STATUS; 15 + $types[] = ReleephRequestTransaction::TYPE_REQUEST; 16 + $types[] = ReleephRequestTransaction::TYPE_USER_INTENT; 17 + 18 + return $types; 19 + } 20 + 21 + public function getCustomTransactionOldValue( 22 + PhabricatorLiskDAO $object, 23 + PhabricatorApplicationTransaction $xaction) { 24 + 25 + switch ($xaction->getTransactionType()) { 26 + case ReleephRequestTransaction::TYPE_REQUEST: 27 + return $object->getRequestCommitPHID(); 28 + 29 + case ReleephRequestTransaction::TYPE_EDIT_FIELD: 30 + $field = newv($xaction->getMetadataValue('fieldClass'), array()); 31 + $value = $field->setReleephRequest($object)->getValue(); 32 + return $value; 33 + 34 + case ReleephRequestTransaction::TYPE_USER_INTENT: 35 + $user_phid = $xaction->getAuthorPHID(); 36 + return idx($object->getUserIntents(), $user_phid); 37 + 38 + case ReleephRequestTransaction::TYPE_PICK_STATUS: 39 + return (int)$object->getPickStatus(); 40 + break; 41 + 42 + case ReleephRequestTransaction::TYPE_COMMIT: 43 + return $object->getCommitIdentifier(); 44 + 45 + case ReleephRequestTransaction::TYPE_DISCOVERY: 46 + return $object->getCommitPHID(); 47 + 48 + case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: 49 + return $object->getInBranch(); 50 + } 51 + } 52 + 53 + public function getCustomTransactionNewValue( 54 + PhabricatorLiskDAO $object, 55 + PhabricatorApplicationTransaction $xaction) { 56 + 57 + switch ($xaction->getTransactionType()) { 58 + case ReleephRequestTransaction::TYPE_REQUEST: 59 + case ReleephRequestTransaction::TYPE_USER_INTENT: 60 + case ReleephRequestTransaction::TYPE_EDIT_FIELD: 61 + case ReleephRequestTransaction::TYPE_PICK_STATUS: 62 + case ReleephRequestTransaction::TYPE_COMMIT: 63 + case ReleephRequestTransaction::TYPE_DISCOVERY: 64 + case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: 65 + return $xaction->getNewValue(); 66 + } 67 + } 68 + 69 + public function applyCustomInternalTransaction( 70 + PhabricatorLiskDAO $object, 71 + PhabricatorApplicationTransaction $xaction) { 72 + 73 + $new = $xaction->getNewValue(); 74 + 75 + switch ($xaction->getTransactionType()) { 76 + case ReleephRequestTransaction::TYPE_REQUEST: 77 + $object->setRequestCommitPHID($new); 78 + break; 79 + 80 + case ReleephRequestTransaction::TYPE_USER_INTENT: 81 + $user_phid = $xaction->getAuthorPHID(); 82 + $intents = $object->getUserIntents(); 83 + $intents[$user_phid] = $new; 84 + $object->setUserIntents($intents); 85 + break; 86 + 87 + case ReleephRequestTransaction::TYPE_EDIT_FIELD: 88 + $field = newv($xaction->getMetadataValue('fieldClass'), array()); 89 + $field 90 + ->setReleephRequest($object) 91 + ->setValue($new); 92 + break; 93 + 94 + case ReleephRequestTransaction::TYPE_PICK_STATUS: 95 + $object->setPickStatus($new); 96 + break; 97 + 98 + case ReleephRequestTransaction::TYPE_COMMIT: 99 + $this->setInBranchFromAction($object, $xaction); 100 + $object->setCommitIdentifier($new); 101 + break; 102 + 103 + case ReleephRequestTransaction::TYPE_DISCOVERY: 104 + $this->setInBranchFromAction($object, $xaction); 105 + $object->setCommitPHID($new); 106 + break; 107 + 108 + case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: 109 + $object->setInBranch((int) $new); 110 + break; 111 + } 112 + } 113 + 114 + protected function applyCustomExternalTransaction( 115 + PhabricatorLiskDAO $object, 116 + PhabricatorApplicationTransaction $xaction) { 117 + 118 + return; 119 + } 120 + 121 + protected function filterTransactions( 122 + PhabricatorLiskDAO $object, 123 + array $xactions) { 124 + 125 + // Remove TYPE_DISCOVERY xactions that are the result of a reparse. 126 + $previously_discovered_commits = array(); 127 + $discovery_xactions = idx( 128 + mgroup($xactions, 'getTransactionType'), 129 + ReleephRequestTransaction::TYPE_DISCOVERY); 130 + if ($discovery_xactions) { 131 + $previous_xactions = id(new ReleephRequestTransactionQuery()) 132 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 133 + ->withObjectPHIDs(array($object->getPHID())) 134 + ->execute(); 135 + 136 + foreach ($previous_xactions as $xaction) { 137 + if ($xaction->getTransactionType() === 138 + ReleephRequestTransaction::TYPE_DISCOVERY) { 139 + 140 + $commit_phid = $xaction->getNewValue(); 141 + $previously_discovered_commits[$commit_phid] = true; 142 + } 143 + } 144 + } 145 + 146 + foreach ($xactions as $key => $xaction) { 147 + if ($xaction->getTransactionType() === 148 + ReleephRequestTransaction::TYPE_DISCOVERY && 149 + idx($previously_discovered_commits, $xaction->getNewValue())) { 150 + 151 + unset($xactions[$key]); 152 + } 153 + } 154 + 155 + return parent::filterTransactions($object, $xactions); 156 + } 157 + 158 + protected function supportsMail() { 159 + return true; 160 + } 161 + 162 + protected function sendMail( 163 + PhabricatorLiskDAO $object, 164 + array $xactions) { 165 + 166 + // Avoid sending emails that only talk about commit discovery. 167 + $types = array_unique(mpull($xactions, 'getTransactionType')); 168 + if ($types === array(ReleephRequestTransaction::TYPE_DISCOVERY)) { 169 + return null; 170 + } 171 + 172 + // Don't email people when we discover that something picks or reverts OK. 173 + if ($types === array(ReleephRequestTransaction::TYPE_PICK_STATUS)) { 174 + if (!mfilter($xactions, 'isBoringPickStatus', true /* negate */)) { 175 + // If we effectively call "isInterestingPickStatus" and get nothing... 176 + return null; 177 + } 178 + } 179 + 180 + return parent::sendMail($object, $xactions); 181 + } 182 + 183 + protected function buildReplyHandler(PhabricatorLiskDAO $object) { 184 + return id(new ReleephRequestReplyHandler()) 185 + ->setActor($this->getActor()) 186 + ->setMailReceiver($object); 187 + } 188 + 189 + protected function getMailSubjectPrefix() { 190 + return '[Releeph]'; 191 + } 192 + 193 + protected function buildMailTemplate(PhabricatorLiskDAO $object) { 194 + $id = $object->getID(); 195 + $phid = $object->getPHID(); 196 + $title = $object->getSummaryForDisplay(); 197 + return id(new PhabricatorMetaMTAMail()) 198 + ->setSubject("RQ{$id}: {$title}") 199 + ->addHeader('Thread-Topic', "RQ{$id}: {$phid}"); 200 + } 201 + 202 + protected function getMailTo(PhabricatorLiskDAO $object) { 203 + $to_phids = array(); 204 + 205 + $releeph_project = $object->loadReleephProject(); 206 + foreach ($releeph_project->getPushers() as $phid) { 207 + $to_phids[] = $phid; 208 + } 209 + 210 + foreach ($object->getUserIntents() as $phid => $intent) { 211 + $to_phids[] = $phid; 212 + } 213 + 214 + return $to_phids; 215 + } 216 + 217 + protected function getMailCC(PhabricatorLiskDAO $object) { 218 + return array(); 219 + } 220 + 221 + protected function buildMailBody( 222 + PhabricatorLiskDAO $object, 223 + array $xactions) { 224 + 225 + $body = parent::buildMailBody($object, $xactions); 226 + 227 + $rq = $object; 228 + $releeph_branch = $rq->loadReleephBranch(); 229 + $releeph_project = $releeph_branch->loadReleephProject(); 230 + 231 + /** 232 + * If any of the events we are emailing about were about a pick failure 233 + * (and/or a revert failure?), include pick failure instructions. 234 + */ 235 + $has_pick_failure = false; 236 + foreach ($xactions as $xaction) { 237 + if ($xaction->getTransactionType() === 238 + ReleephRequestTransaction::TYPE_PICK_STATUS && 239 + $xaction->getNewValue() === ReleephRequest::PICK_FAILED) { 240 + 241 + $has_pick_failure = true; 242 + break; 243 + } 244 + } 245 + if ($has_pick_failure) { 246 + $instructions = $releeph_project->getDetail('pick_failure_instructions'); 247 + if ($instructions) { 248 + $body->addTextSection( 249 + pht('PICK FAILURE INSTRUCTIONS'), 250 + $instructions); 251 + } 252 + } 253 + 254 + $name = sprintf("RQ%s: %s", $rq->getID(), $rq->getSummaryForDisplay()); 255 + $body->addTextSection( 256 + pht('RELEEPH REQUEST'), 257 + $name."\n". 258 + PhabricatorEnv::getProductionURI('/RQ'.$rq->getID())); 259 + 260 + $project_and_branch = sprintf( 261 + '%s - %s', 262 + $releeph_project->getName(), 263 + $releeph_branch->getDisplayNameWithDetail()); 264 + 265 + $body->addTextSection( 266 + pht('RELEEPH BRANCH'), 267 + $project_and_branch."\n". 268 + $releeph_branch->getURI()); 269 + 270 + return $body; 271 + } 272 + 273 + private function setInBranchFromAction( 274 + ReleephRequest $rq, 275 + ReleephRequestTransaction $xaction) { 276 + 277 + $action = $xaction->getMetadataValue('action'); 278 + switch ($action) { 279 + case 'pick': 280 + $rq->setInBranch(1); 281 + break; 282 + 283 + case 'revert': 284 + $rq->setInBranch(0); 285 + break; 286 + 287 + default: 288 + $id = $rq->getID(); 289 + $type = $xaction->getTransactionType(); 290 + $new = $xaction->getNewValue(); 291 + phlog( 292 + "Unknown discovery action '{$action}' ". 293 + "for xaction of type {$type} ". 294 + "with new value {$new} ". 295 + "mentioning RQ{$id}!"); 296 + break; 297 + } 298 + } 299 + 300 + }
-213
src/applications/releeph/editor/mail/ReleephRequestMail.php
··· 1 - <?php 2 - 3 - /** 4 - * Build an email that renders a group of events with and appends some standard 5 - * Releeph things (a URI for this request, and this branch). 6 - * 7 - * Also includes some helper stuff for adding groups of people to the To: and 8 - * Cc: headers. 9 - */ 10 - final class ReleephRequestMail { 11 - 12 - const ENT_REQUESTOR = 'requestor'; 13 - const ENT_DIFF = 'diff'; 14 - const ENT_ALL_PUSHERS = 'pushers'; 15 - const ENT_ACTORS = 'actors'; 16 - const ENT_INTERESTED_PUSHERS = 'interested-pushers'; 17 - 18 - private $sender; 19 - private $tos = array(); 20 - private $ccs = array(); 21 - private $events; 22 - private $releephRequest; 23 - private $releephProject; 24 - 25 - public function setReleephRequest(ReleephRequest $rq) { 26 - $this->releephRequest = $rq; 27 - return $this; 28 - } 29 - 30 - public function setReleephProject(ReleephProject $rp) { 31 - $this->releephProject = $rp; 32 - return $this; 33 - } 34 - 35 - public function setEvents(array $events) { 36 - assert_instances_of($events, 'ReleephRequestEvent'); 37 - $this->events = $events; 38 - return $this; 39 - } 40 - 41 - public function setSenderAndRecipientPHID($sender_phid) { 42 - $this->sender = $sender_phid; 43 - $this->tos[] = $sender_phid; 44 - return $this; 45 - } 46 - 47 - public function addTos($entity) { 48 - $this->tos = array_merge( 49 - $this->tos, 50 - $this->getEntityPHIDs($entity)); 51 - return $this; 52 - } 53 - 54 - public function addCcs($entity) { 55 - $this->ccs = array_merge( 56 - $this->tos, 57 - $this->getEntityPHIDs($entity)); 58 - return $this; 59 - } 60 - 61 - public function send() { 62 - $this->buildMail()->save(); 63 - } 64 - 65 - public function buildMail() { 66 - return id(new PhabricatorMetaMTAMail()) 67 - ->setSubject($this->renderSubject()) 68 - ->setBody($this->buildBody()->render()) 69 - ->setFrom($this->sender) 70 - ->addTos($this->tos) 71 - ->addCCs($this->ccs); 72 - } 73 - 74 - private function getEntityPHIDs($entity) { 75 - $phids = array(); 76 - switch ($entity) { 77 - // The requestor 78 - case self::ENT_REQUESTOR: 79 - $phids[] = $this->releephRequest->getRequestUserPHID(); 80 - break; 81 - 82 - // People on the original diff 83 - case self::ENT_DIFF: 84 - $commit = $this->releephRequest->loadPhabricatorRepositoryCommit(); 85 - $commit_data = $commit->loadCommitData(); 86 - if ($commit_data) { 87 - $phids[] = $commit_data->getCommitDetail('reviewerPHID'); 88 - $phids[] = $commit_data->getCommitDetail('authorPHID'); 89 - } 90 - break; 91 - 92 - // All pushers for this project 93 - case self::ENT_ALL_PUSHERS: 94 - $phids = array_merge( 95 - $phids, 96 - $this->releephProject->getPushers()); 97 - break; 98 - 99 - // Pushers who have explicitly wanted or passed on this request 100 - case self::ENT_INTERESTED_PUSHERS: 101 - $all_pushers = $this->releephProject->getPushers(); 102 - $intents = $this->releephRequest->getUserIntents(); 103 - foreach ($all_pushers as $pusher) { 104 - if (idx($intents, $pusher)) { 105 - $phids[] = $pusher; 106 - } 107 - } 108 - break; 109 - 110 - // Anyone who created our list of events 111 - case self::ENT_ACTORS: 112 - $phids = array_merge( 113 - $phids, 114 - mpull($this->events, 'getActorPHID')); 115 - break; 116 - 117 - default: 118 - throw new Exception( 119 - "Unknown entity type {$entity}!"); 120 - break; 121 - } 122 - 123 - return array_filter($phids); 124 - } 125 - 126 - private function buildBody() { 127 - $body = new PhabricatorMetaMTAMailBody(); 128 - $rq = $this->releephRequest; 129 - 130 - // Events and comments 131 - $phids = array( 132 - $rq->getPHID(), 133 - ); 134 - foreach ($this->events as $event) { 135 - $phids = array_merge($phids, $event->extractPHIDs()); 136 - } 137 - $handles = id(new PhabricatorObjectHandleData($phids)) 138 - // By the time we're generating email, we can assume that whichever 139 - // entitties are receving the email are authorized to see the loaded 140 - // handles! 141 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 142 - ->loadHandles(); 143 - 144 - $raw_events = id(new ReleephRequestEventListView()) 145 - ->setUser(PhabricatorUser::getOmnipotentUser()) 146 - ->setHandles($handles) 147 - ->setEvents($this->events) 148 - ->renderForEmail(); 149 - 150 - $body->addRawSection($raw_events); 151 - 152 - $project = $rq->loadReleephProject(); 153 - $branch = $rq->loadReleephBranch(); 154 - 155 - /** 156 - * If any of the events we are emailing about were TYPE_PICK_STATUS where 157 - * the newPickStatus was a pick failure (and/or a revert failure?), include 158 - * pick failure instructions. 159 - */ 160 - $pick_failure_events = array(); 161 - foreach ($this->events as $event) { 162 - if ($event->getType() == ReleephRequestEvent::TYPE_PICK_STATUS && 163 - $event->getDetail('newPickStatus') == ReleephRequest::PICK_FAILED) { 164 - 165 - $pick_failure_events[] = $event; 166 - } 167 - } 168 - 169 - if ($pick_failure_events) { 170 - $instructions = $project->getDetail('pick_failure_instructions'); 171 - if ($instructions) { 172 - $body->addTextSection('PICK FAILURE INSTRUCTIONS', $instructions); 173 - } 174 - } 175 - 176 - // Common stuff at the end 177 - $body->addTextSection( 178 - 'RELEEPH REQUEST', 179 - $handles[$rq->getPHID()]->getFullName()."\n". 180 - PhabricatorEnv::getProductionURI('/RQ'.$rq->getID())); 181 - 182 - $project_and_branch = sprintf( 183 - '%s - %s', 184 - $project->getName(), 185 - $branch->getDisplayNameWithDetail()); 186 - 187 - $body->addTextSection( 188 - 'RELEEPH BRANCH', 189 - $project_and_branch."\n". 190 - $branch->getURI()); 191 - 192 - // But verbose stuff at the *very* end! 193 - foreach ($pick_failure_events as $event) { 194 - $failure_details = $event->getDetail('commitDetails'); 195 - if ($failure_details) { 196 - $body->addRawSection('PICK FAILURE DETAILS'); 197 - foreach ($failure_details as $heading => $data) { 198 - $body->addTextSection($heading, $data); 199 - } 200 - } 201 - } 202 - 203 - return $body; 204 - } 205 - 206 - private function renderSubject() { 207 - $rq = $this->releephRequest; 208 - $id = $rq->getID(); 209 - $summary = $rq->getSummaryForDisplay(); 210 - return "RQ{$id}: {$summary}"; 211 - } 212 - 213 - }
+27 -23
src/applications/releeph/field/specification/ReleephFieldSpecification.php
··· 11 11 return null; 12 12 } 13 13 14 + public function getRequiredStorageKey() { 15 + $key = $this->getStorageKey(); 16 + if ($key === null) { 17 + throw new ReleephFieldSpecificationIncompleteException($this); 18 + } 19 + if (strpos($key, '.') !== false) { 20 + /** 21 + * Storage keys are reused for form controls, and periods in form control 22 + * names break HTML forms. 23 + */ 24 + throw new Exception( 25 + "You can't use '.' in storage keys!"); 26 + } 27 + return $key; 28 + } 29 + 14 30 final public function isEditable() { 15 31 return $this->getStorageKey() !== null; 16 32 } ··· 37 53 return; 38 54 } 39 55 56 + /** 57 + * Turn values as they are stored in a ReleephRequest into a text that can be 58 + * rendered as a transactions old/new values. 59 + */ 60 + public function normalizeForTransactionView( 61 + PhabricatorApplicationTransaction $xaction, 62 + $value) { 63 + 64 + return $value; 65 + } 66 + 40 67 41 68 /* -( Header View )-------------------------------------------------------- */ 42 69 ··· 58 85 59 86 public function renderEditControl(AphrontRequest $request) { 60 87 throw new ReleephFieldSpecificationIncompleteException($this); 61 - } 62 - 63 - public function setValueFromAphrontRequest(AphrontRequest $request) { 64 - $data = $request->getRequestData(); 65 - $value = idx($data, $this->getRequiredStorageKey()); 66 - $this->validate($value); 67 - $this->setValue($value); 68 88 } 69 89 70 90 ··· 299 319 300 320 301 321 /* -( Implementation )----------------------------------------------------- */ 302 - 303 - protected function getRequiredStorageKey() { 304 - $key = $this->getStorageKey(); 305 - if ($key === null) { 306 - throw new ReleephFieldSpecificationIncompleteException($this); 307 - } 308 - if (strpos($key, '.') !== false) { 309 - /** 310 - * Storage keys are reused for form controls, and periods in form control 311 - * names break HTML forms. 312 - */ 313 - throw new Exception( 314 - "You can't use '.' in storage keys!"); 315 - } 316 - return $key; 317 - } 318 322 319 323 /** 320 324 * The "hook" functions ##appendSelectControlsHook()## and
+55
src/applications/releeph/mail/ReleephRequestReplyHandler.php
··· 1 + <?php 2 + 3 + final class ReleephRequestReplyHandler extends PhabricatorMailReplyHandler { 4 + 5 + public function validateMailReceiver($mail_receiver) { 6 + if (!($mail_receiver instanceof ReleephRequest)) { 7 + throw new Exception("Mail receiver is not a ReleephRequest!"); 8 + } 9 + } 10 + 11 + public function getPrivateReplyHandlerEmailAddress( 12 + PhabricatorObjectHandle $handle) { 13 + return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'RERQ'); 14 + } 15 + 16 + public function getPublicReplyHandlerEmailAddress() { 17 + return $this->getDefaultPublicReplyHandlerEmailAddress('RERQ'); 18 + } 19 + 20 + public function getReplyHandlerInstructions() { 21 + if ($this->supportsReplies()) { 22 + return pht('Reply to comment.'); 23 + } else { 24 + return null; 25 + } 26 + } 27 + 28 + protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { 29 + $rq = $this->getMailReceiver(); 30 + $user = $this->getActor(); 31 + 32 + $content_source = PhabricatorContentSource::newForSource( 33 + PhabricatorContentSource::SOURCE_EMAIL, 34 + array( 35 + 'id' => $mail->getID(), 36 + )); 37 + 38 + $editor = id(new ReleephRequestTransactionalEditor()) 39 + ->setActor($user) 40 + ->setContentSource($content_source) 41 + ->setParentMessageID($mail->getMessageID()); 42 + 43 + $body = $mail->getCleanTextBody(); 44 + 45 + $xactions = array(); 46 + $xactions[] = id(new ReleephRequestTransaction()) 47 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 48 + ->attachComment($body); 49 + 50 + $editor->applyTransactions($rq, $xactions); 51 + 52 + return $rq; 53 + } 54 + 55 + }
+10
src/applications/releeph/query/ReleephRequestTransactionQuery.php
··· 1 + <?php 2 + 3 + final class ReleephRequestTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + protected function getTemplateApplicationTransaction() { 7 + return new ReleephRequestTransaction(); 8 + } 9 + 10 + }
-7
src/applications/releeph/storage/ReleephRequest.php
··· 312 312 throw new Exception('`status` is now deprecated!'); 313 313 } 314 314 315 - 316 - /* -( Make magic Lisk methods private )------------------------------------ */ 317 - 318 - private function setUserIntents(array $ar) { 319 - return parent::setUserIntents($ar); 320 - } 321 - 322 315 }
+283
src/applications/releeph/storage/ReleephRequestTransaction.php
··· 1 + <?php 2 + 3 + final class ReleephRequestTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_REQUEST = 'releeph:request'; 7 + const TYPE_USER_INTENT = 'releeph:user_intent'; 8 + const TYPE_EDIT_FIELD = 'releeph:edit_field'; 9 + const TYPE_PICK_STATUS = 'releeph:pick_status'; 10 + const TYPE_COMMIT = 'releeph:commit'; 11 + const TYPE_DISCOVERY = 'releeph:discovery'; 12 + const TYPE_MANUAL_IN_BRANCH = 'releeph:manual'; 13 + 14 + public function getApplicationName() { 15 + return 'releeph'; 16 + } 17 + 18 + public function getApplicationTransactionType() { 19 + return ReleephPHIDConstants::PHID_TYPE_RERQ; 20 + } 21 + 22 + public function getApplicationTransactionCommentObject() { 23 + return new ReleephRequestTransactionComment(); 24 + } 25 + 26 + public function getApplicationObjectTypeName() { 27 + return pht('releeph request'); 28 + } 29 + 30 + public function hasChangeDetails() { 31 + switch ($this->getTransactionType()) { 32 + default; 33 + break; 34 + } 35 + return parent::hasChangeDetails(); 36 + } 37 + 38 + public function getRequiredHandlePHIDs() { 39 + $phids = parent::getRequiredHandlePHIDs(); 40 + $phids[] = $this->getObjectPHID(); 41 + 42 + $new = $this->getNewValue(); 43 + 44 + switch ($this->getTransactionType()) { 45 + case ReleephRequestTransaction::TYPE_REQUEST: 46 + case ReleephRequestTransaction::TYPE_DISCOVERY: 47 + $phids[] = $new; 48 + break; 49 + 50 + case ReleephRequestTransaction::TYPE_EDIT_FIELD: 51 + self::searchForPHIDs($this->getOldValue(), $phids); 52 + self::searchForPHIDs($this->getNewValue(), $phids); 53 + break; 54 + } 55 + 56 + return $phids; 57 + } 58 + 59 + public function getTitle() { 60 + $author_phid = $this->getAuthorPHID(); 61 + $object_phid = $this->getObjectPHID(); 62 + 63 + $old = $this->getOldValue(); 64 + $new = $this->getNewValue(); 65 + 66 + switch ($this->getTransactionType()) { 67 + case ReleephRequestTransaction::TYPE_REQUEST: 68 + return pht( 69 + '%s requested %s', 70 + $this->renderHandleLink($author_phid), 71 + $this->renderHandleLink($new)); 72 + break; 73 + 74 + case ReleephRequestTransaction::TYPE_USER_INTENT: 75 + return $this->getIntentTitle(); 76 + break; 77 + 78 + case ReleephRequestTransaction::TYPE_EDIT_FIELD: 79 + $field = newv($this->getMetadataValue('fieldClass'), array()); 80 + $name = $field->getName(); 81 + 82 + $markup = $name; 83 + if ($this->getRenderingTarget() === 84 + PhabricatorApplicationTransaction::TARGET_HTML) { 85 + 86 + $markup = hsprintf('<em>%s</em>', $name); 87 + } 88 + 89 + return pht( 90 + '%s changed the %s to "%s"', 91 + $this->renderHandleLink($author_phid), 92 + $markup, 93 + $field->normalizeForTransactionView($this, $new)); 94 + break; 95 + 96 + case ReleephRequestTransaction::TYPE_PICK_STATUS: 97 + switch ($new) { 98 + case ReleephRequest::PICK_OK: 99 + return pht('%s found this request picks without error', 100 + $this->renderHandleLink($author_phid)); 101 + 102 + case ReleephRequest::REVERT_OK: 103 + return pht('%s found this request reverts without error', 104 + $this->renderHandleLink($author_phid)); 105 + 106 + case ReleephRequest::PICK_FAILED: 107 + return pht("%s couldn't pick this request", 108 + $this->renderHandleLink($author_phid)); 109 + 110 + case ReleephRequest::REVERT_FAILED: 111 + return pht("%s couldn't revert this request", 112 + $this->renderHandleLink($author_phid)); 113 + } 114 + break; 115 + 116 + case ReleephRequestTransaction::TYPE_COMMIT: 117 + $action_type = $this->getMetadataValue('action'); 118 + switch ($action_type) { 119 + case 'pick': 120 + return pht( 121 + '%s picked this request and committed the result upstream', 122 + $this->renderHandleLink($author_phid)); 123 + break; 124 + 125 + case 'revert': 126 + return pht( 127 + '%s reverted this request and committed the result upstream', 128 + $this->renderHandleLink($author_phid)); 129 + break; 130 + } 131 + break; 132 + 133 + case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: 134 + $action = $new ? pht('picked') : pht('reverted'); 135 + return pht( 136 + '%s marked this request as manually %s', 137 + $this->renderHandleLink($author_phid), 138 + $action); 139 + break; 140 + 141 + case ReleephRequestTransaction::TYPE_DISCOVERY: 142 + return pht('%s discovered this commit as %s', 143 + $this->renderHandleLink($author_phid), 144 + $this->renderHandleLink($new)); 145 + break; 146 + 147 + default: 148 + return parent::getTitle(); 149 + break; 150 + } 151 + } 152 + 153 + public function getActionStrength() { 154 + return parent::getActionStrength(); 155 + } 156 + 157 + public function getActionName() { 158 + switch ($this->getTransactionType()) { 159 + case self::TYPE_REQUEST: 160 + return pht('Requested'); 161 + 162 + case self::TYPE_COMMIT: 163 + $action_type = $this->getMetadataValue('action'); 164 + switch ($action_type) { 165 + case 'pick': 166 + return pht('Picked'); 167 + 168 + case 'revert': 169 + return pht('Reverted'); 170 + } 171 + } 172 + 173 + return parent::getActionName(); 174 + } 175 + 176 + public function getColor() { 177 + $new = $this->getNewValue(); 178 + 179 + switch ($this->getTransactionType()) { 180 + case ReleephRequestTransaction::TYPE_USER_INTENT: 181 + switch ($new) { 182 + case ReleephRequest::INTENT_WANT: 183 + return PhabricatorTransactions::COLOR_GREEN; 184 + case ReleephRequest::INTENT_PASS: 185 + return PhabricatorTransactions::COLOR_RED; 186 + } 187 + } 188 + return parent::getColor(); 189 + } 190 + 191 + private static function searchForPHIDs($thing, array &$phids) { 192 + /** 193 + * To implement something like getRequiredHandlePHIDs() in a 194 + * ReleephFieldSpecification, we'd have to provide the field with its 195 + * ReleephRequest (so that it could load the PHIDs from the 196 + * ReleephRequest's storage, and return them.) 197 + * 198 + * We don't have fields initialized with their ReleephRequests, but we can 199 + * make a good guess at what handles will be needed for rendering the field 200 + * in this transaction by inspecting the old and new values. 201 + */ 202 + if (!is_array($thing)) { 203 + $thing = array($thing); 204 + } 205 + 206 + foreach ($thing as $value) { 207 + if (phid_get_type($value) !== 208 + PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { 209 + 210 + $phids[] = $value; 211 + } 212 + } 213 + } 214 + 215 + private function getIntentTitle() { 216 + $author_phid = $this->getAuthorPHID(); 217 + $object_phid = $this->getObjectPHID(); 218 + 219 + $new = $this->getNewValue(); 220 + $is_pusher = $this->getMetadataValue('isPusher'); 221 + 222 + switch ($new) { 223 + case ReleephRequest::INTENT_WANT: 224 + if ($is_pusher) { 225 + return pht( 226 + '%s approved this request', 227 + $this->renderHandleLink($author_phid)); 228 + } else { 229 + return pht( 230 + '%s wanted this request', 231 + $this->renderHandleLink($author_phid)); 232 + } 233 + 234 + case ReleephRequest::INTENT_PASS: 235 + if ($is_pusher) { 236 + return pht( 237 + '%s rejected this request', 238 + $this->renderHandleLink($author_phid)); 239 + } else { 240 + return pht( 241 + '%s passed on this request', 242 + $this->renderHandleLink($author_phid)); 243 + } 244 + } 245 + } 246 + 247 + public function shouldHide() { 248 + $type = $this->getTransactionType(); 249 + 250 + if ($type === ReleephRequestTransaction::TYPE_USER_INTENT && 251 + $this->getMetadataValue('isRQCreate')) { 252 + 253 + return true; 254 + } 255 + 256 + if ($this->isBoringPickStatus()) { 257 + return true; 258 + } 259 + 260 + // ReleephSummaryFieldSpecification is usually blank when an RQ is created, 261 + // creating a transaction change from null to "". Hide these! 262 + if ($type === ReleephRequestTransaction::TYPE_EDIT_FIELD) { 263 + if ($this->getOldValue() === null && $this->getNewValue() === "") { 264 + return true; 265 + } 266 + } 267 + return parent::shouldHide(); 268 + } 269 + 270 + public function isBoringPickStatus() { 271 + $type = $this->getTransactionType(); 272 + if ($type === ReleephRequestTransaction::TYPE_PICK_STATUS) { 273 + $new = $this->getNewValue(); 274 + if ($new === ReleephRequest::PICK_OK || 275 + $new === ReleephRequest::REVERT_OK) { 276 + 277 + return true; 278 + } 279 + } 280 + return false; 281 + } 282 + 283 + }
+10
src/applications/releeph/storage/ReleephRequestTransactionComment.php
··· 1 + <?php 2 + 3 + final class ReleephRequestTransactionComment 4 + extends PhabricatorApplicationTransactionComment { 5 + 6 + public function getApplicationTransactionObject() { 7 + return new ReleephRequestTransaction(); 8 + } 9 + 10 + }
+3 -1
src/applications/releeph/view/request/ReleephRequestTypeaheadControl.php
··· 2 2 3 3 final class ReleephRequestTypeaheadControl extends AphrontFormControl { 4 4 5 + const PLACEHOLDER = 'Type a commit id or first line of commit message...'; 6 + 5 7 private $repo; 6 8 private $startTime; 7 9 ··· 42 44 Javelin::initBehavior('releeph-request-typeahead', array( 43 45 'id' => $id, 44 46 'src' => '/releeph/request/typeahead/', 45 - 'placeholder' => 'Type a commit id or first line of commit message...', 47 + 'placeholder' => self::PLACEHOLDER, 46 48 'value' => $this->getValue(), 47 49 'aux' => array( 48 50 'repo' => $this->repo->getID(),
-266
src/applications/releeph/view/requestevent/ReleephRequestEventListView.php
··· 1 - <?php 2 - 3 - final class ReleephRequestEventListView extends AphrontView { 4 - 5 - private $events; 6 - private $handles; 7 - 8 - public function setEvents(array $events) { 9 - assert_instances_of($events, 'ReleephRequestEvent'); 10 - $this->events = $events; 11 - return $this; 12 - } 13 - 14 - public function setHandles(array $handles) { 15 - assert_instances_of($handles, 'PhabricatorObjectHandle'); 16 - $this->handles = $handles; 17 - return $this; 18 - } 19 - 20 - public function render() { 21 - $views = array(); 22 - 23 - $discovered_commits = array(); 24 - foreach ($this->events as $event) { 25 - $commit_id = $event->getDetail('newCommitIdentifier'); 26 - switch ($event->getType()) { 27 - case ReleephRequestEvent::TYPE_DISCOVERY: 28 - $discovered_commits[$commit_id] = true; 29 - break; 30 - } 31 - } 32 - 33 - $markup_engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); 34 - $markup_engine->setConfig('viewer', $this->getUser()); 35 - 36 - foreach ($this->events as $event) { 37 - $description = $this->describeEvent($event); 38 - if (!$description) { 39 - continue; 40 - } 41 - 42 - if ($event->getType() === ReleephRequestEvent::TYPE_COMMIT) { 43 - $commit_id = $event->getDetail('newCommitIdentifier'); 44 - if (idx($discovered_commits, $commit_id)) { 45 - continue; 46 - } 47 - } 48 - 49 - $actor_handle = $this->handles[$event->getActorPHID()]; 50 - $description = $this->describeEvent($event); 51 - $action = phutil_tag( 52 - 'div', 53 - array(), 54 - array( 55 - $actor_handle->renderLink(), 56 - ' ', 57 - $description)); 58 - 59 - $view = id(new PhabricatorTransactionView()) 60 - ->setUser($this->user) 61 - ->setImageURI($actor_handle->getImageURI()) 62 - ->setEpoch($event->getDateCreated()) 63 - ->setActions(array($action)) 64 - ->addClass($this->getTransactionClass($event)); 65 - 66 - $comment = $this->getEventComment($event); 67 - if ($comment) { 68 - $markup = phutil_tag( 69 - 'div', 70 - array( 71 - 'class' => 'phabricator-remarkup', 72 - ), 73 - phutil_safe_html( 74 - $markup_engine->markupText($comment))); 75 - $view->appendChild($markup); 76 - } 77 - 78 - $views[] = $view; 79 - } 80 - 81 - return phutil_tag( 82 - 'div', 83 - array( 84 - 'class' => 'releeph-request-event-list', 85 - ), 86 - $views); 87 - } 88 - 89 - public function renderForEmail() { 90 - $items = array(); 91 - foreach ($this->events as $event) { 92 - $description = $this->describeEvent($event); 93 - if (!$description) { 94 - continue; 95 - } 96 - $actor = $this->handles[$event->getActorPHID()]->getName(); 97 - $items[] = $actor.' '.$description; 98 - 99 - $comment = $this->getEventComment($event); 100 - if ($comment) { 101 - $items[] = preg_replace('/^/m', ' ', $comment); 102 - } 103 - } 104 - 105 - return implode("\n\n", $items); 106 - } 107 - 108 - private function describeEvent(ReleephRequestEvent $event) { 109 - $type = $event->getType(); 110 - 111 - switch ($type) { 112 - case ReleephRequestEvent::TYPE_CREATE: 113 - return "created this request."; 114 - break; 115 - 116 - case ReleephRequestEvent::TYPE_STATUS: 117 - $status = $event->getStatusAfter(); 118 - return sprintf( 119 - "updated status to %s.", 120 - ReleephRequest::getStatusDescriptionFor($status)); 121 - break; 122 - 123 - case ReleephRequestEvent::TYPE_USER_INTENT: 124 - $intent = $event->getDetail('newIntent'); 125 - $was_pusher = $event->getDetail('wasPusher'); 126 - if ($intent == ReleephRequest::INTENT_WANT) { 127 - if ($was_pusher) { 128 - $verb = "approved"; 129 - } else { 130 - $verb = "wanted"; 131 - } 132 - } else { 133 - if ($was_pusher) { 134 - $verb = "rejected"; 135 - } else { 136 - $verb = "passed on"; 137 - } 138 - } 139 - return "{$verb} this request."; 140 - break; 141 - 142 - case ReleephRequestEvent::TYPE_PICK_STATUS: 143 - $pick_status = $event->getDetail('newPickStatus'); 144 - switch ($pick_status) { 145 - case ReleephRequest::PICK_FAILED: 146 - return "found a conflict when picking."; 147 - break; 148 - 149 - case ReleephRequest::REVERT_FAILED: 150 - return "found a conflict when reverting."; 151 - break; 152 - 153 - case ReleephRequest::PICK_OK: 154 - case ReleephRequest::REVERT_OK: 155 - // (nothing) 156 - break; 157 - 158 - default: 159 - return "changed pick-status to {$pick_status}."; 160 - break; 161 - } 162 - break; 163 - 164 - case ReleephRequestEvent::TYPE_MANUAL_ACTION: 165 - $action = $event->getDetail('action'); 166 - return "claimed to have manually {$action}ed this request."; 167 - break; 168 - 169 - case ReleephRequestEvent::TYPE_COMMIT: 170 - $action = $event->getDetail('action'); 171 - if ($action) { 172 - return "{$action}ed this request."; 173 - } else { 174 - return "did something with this request."; 175 - } 176 - break; 177 - 178 - case ReleephRequestEvent::TYPE_DISCOVERY: 179 - $action = $event->getDetail('action'); 180 - if ($action) { 181 - return "{$action}ed this request."; 182 - } else { 183 - // It's unlikely we'll have action-less TYPE_DISCOVERY events, but I 184 - // used this during testing and I guess it's a useful safety net. 185 - return "discovered this request in the branch."; 186 - } 187 - break; 188 - 189 - case ReleephRequestEvent::TYPE_COMMENT: 190 - return "commented on this request."; 191 - break; 192 - 193 - default: 194 - return "did event of type {$type}."; 195 - break; 196 - } 197 - } 198 - 199 - private function getEventComment(ReleephRequestEvent $event) { 200 - switch ($event->getType()) { 201 - case ReleephRequestEvent::TYPE_CREATE: 202 - $commit_phid = $event->getDetail('commitPHID'); 203 - return sprintf( 204 - "Commit %s was requested.", 205 - $this->handles[$commit_phid]->getName()); 206 - break; 207 - 208 - case ReleephRequestEvent::TYPE_STATUS: 209 - case ReleephRequestEvent::TYPE_USER_INTENT: 210 - case ReleephRequestEvent::TYPE_PICK_STATUS: 211 - case ReleephRequestEvent::TYPE_MANUAL_ACTION: 212 - // no comment! 213 - break; 214 - 215 - case ReleephRequestEvent::TYPE_COMMIT: 216 - return sprintf( 217 - "Closed by commit %s.", 218 - $event->getDetail('newCommitIdentifier')); 219 - break; 220 - 221 - case ReleephRequestEvent::TYPE_DISCOVERY: 222 - $author_phid = $event->getDetail('authorPHID'); 223 - $commit_phid = $event->getDetail('newCommitPHID'); 224 - if ($author_phid && $author_phid != $event->getActorPHID()) { 225 - return sprintf( 226 - "Closed by commit %s (with author set to @%s).", 227 - $this->handles[$commit_phid]->getName(), 228 - $this->handles[$author_phid]->getName()); 229 - } else { 230 - return sprintf( 231 - 'Closed by commit %s.', 232 - $this->handles[$commit_phid]->getName()); 233 - } 234 - break; 235 - 236 - case ReleephRequestEvent::TYPE_COMMENT: 237 - return $event->getComment(); 238 - break; 239 - } 240 - } 241 - 242 - private function getTransactionClass($event) { 243 - switch ($event->getType()) { 244 - case ReleephRequestEvent::TYPE_COMMIT: 245 - case ReleephRequestEvent::TYPE_DISCOVERY: 246 - $action = $event->getDetail('action'); 247 - if ($action == 'pick') { 248 - return 'releeph-border-color-picked'; 249 - } else { 250 - return 'releeph-border-color-abandoned'; 251 - } 252 - break; 253 - 254 - case ReleephRequestEvent::TYPE_COMMENT: 255 - return 'releeph-border-color-comment'; 256 - break; 257 - 258 - default: 259 - $status_after = $event->getStatusAfter(); 260 - $class_suffix = ReleephRequest::getStatusClassSuffixFor($status_after); 261 - return ' releeph-border-color-'.$class_suffix; 262 - break; 263 - } 264 - } 265 - 266 - }
+8
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1286 1286 'type' => 'sql', 1287 1287 'name' => $this->getPatchPath('20130508.search_namedquery.sql'), 1288 1288 ), 1289 + '20130508.releephtransactions.sql' => array( 1290 + 'type' => 'sql', 1291 + 'name' => $this->getPatchPath('20130508.releephtransactions.sql'), 1292 + ), 1293 + '20130508.releephtransactionsmig.php' => array( 1294 + 'type' => 'php', 1295 + 'name' => $this->getPatchPath('20130508.releephtransactionsmig.php'), 1296 + ), 1289 1297 ); 1290 1298 } 1291 1299 }