1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

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
This commit is contained in:
Edward Speyer 2013-04-08 16:34:21 +01:00
parent 00cc6b1ba5
commit fcb7286533
26 changed files with 1326 additions and 1201 deletions

View file

@ -0,0 +1,38 @@
CREATE TABLE {$NAMESPACE}_releeph.releeph_requesttransaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_releeph.releeph_requesttransaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion)
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -0,0 +1,131 @@
<?php
echo "Migrating Releeph requests events to transactions...\n";
$table = new ReleephRequest();
$table->openTransaction();
$table->beginReadLocking();
foreach (new LiskMigrationIterator($table) as $rq) {
printf("RQ%d:", $rq->getID());
$intents_cursor = array();
$last_pick_status = null;
$last_commit_id = null;
foreach ($rq->loadEvents() as $event) {
$author = $event->getActorPHID();
$details = $event->getDetails();
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_UNKNOWN,
array('ReleephRequestEventID' => $event->getID()));
$xaction = id(new ReleephRequestTransaction())
->setObjectPHID($rq->getPHID())
->setAuthorPHID($author)
->setContentSource($content_source)
->setDateCreated($event->getDateCreated())
->setDateModified($event->getDateModified())
->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC)
->setEditPolicy($author);
printf(" %s(#%d)", $event->getType(), $event->getID());
switch ($event->getType()) {
case ReleephRequestEvent::TYPE_COMMENT:
$xaction
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->save(); // to generate a PHID
$comment = id(new ReleephRequestTransactionComment())
->setAuthorPHID($author)
->setTransactionPHID($xaction->getPHID())
->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC)
->setEditPolicy($author)
->setCommentVersion(1)
->setContent($event->getComment())
->setContentSource($content_source)
->save();
$xaction
->setCommentPHID($comment->getPHID())
->setCommentVersion(1);
break;
case ReleephRequestEvent::TYPE_STATUS:
// Ignore STATUS events; these are legacy events that we no longer
// support anyway!
continue 2;
case ReleephRequestEvent::TYPE_USER_INTENT:
$old_intent = idx($intents_cursor, $author);
$new_intent = $event->getDetail('newIntent');
$xaction
->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
->setMetadataValue('isAuthoritative', $event->getDetail('wasPusher'))
->setOldValue($old_intent)
->setNewValue($new_intent);
$intents_cursor[$author] = $new_intent;
break;
case ReleephRequestEvent::TYPE_PICK_STATUS:
$new_pick_status = $event->getDetail('newPickStatus');
$xaction
->setTransactionType(ReleephRequestTransaction::TYPE_PICK_STATUS)
->setOldValue($last_pick_status)
->setNewValue($new_pick_status);
$last_pick_status = $new_pick_status;
break;
case ReleephRequestEvent::TYPE_COMMIT:
$new_commit_id = $event->getDetail('newCommitIdentifier');
$xaction
->setTransactionType(ReleephRequestTransaction::TYPE_COMMIT)
->setMetadataValue('action', $event->getDetail('action'))
->setOldValue($last_commit_id)
->setNewValue($new_commit_id);
$last_commit_id = $new_commit_id;
break;
case ReleephRequestEvent::TYPE_DISCOVERY:
$xaction
->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY)
->setNewValue($event->getDetail('newCommitPHID'));
break;
case ReleephRequestEvent::TYPE_CREATE:
$xaction
->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
->setOldValue(null)
->setNewValue($rq->getRequestCommitPHID());
break;
case ReleephRequestEvent::TYPE_MANUAL_ACTION:
$xaction
->setTransactionType(
ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH);
switch ($event->getDetail('action')) {
case 'pick':
$xaction->setNewValue(1);
break;
case 'revert':
$xaction->setNewValue(0);
break;
}
break;
default:
throw new Exception(sprintf(
"Unhandled ReleephRequestEvent type '%s' in RQ%d",
$event->getType(),
$rq->getID()));
}
$xaction->save();
}
echo("\n");
}
$table->endReadLocking();
$table->saveTransaction();
echo "Done.\n";

View file

@ -1733,18 +1733,20 @@ phutil_register_library_map(array(
'ReleephReasonFieldSpecification' => 'applications/releeph/field/specification/ReleephReasonFieldSpecification.php',
'ReleephRequest' => 'applications/releeph/storage/ReleephRequest.php',
'ReleephRequestActionController' => 'applications/releeph/controller/request/ReleephRequestActionController.php',
'ReleephRequestCreateController' => 'applications/releeph/controller/request/ReleephRequestCreateController.php',
'ReleephRequestCommentController' => 'applications/releeph/controller/request/ReleephRequestCommentController.php',
'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php',
'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php',
'ReleephRequestEditor' => 'applications/releeph/editor/ReleephRequestEditor.php',
'ReleephRequestEvent' => 'applications/releeph/storage/request/ReleephRequestEvent.php',
'ReleephRequestEventListView' => 'applications/releeph/view/requestevent/ReleephRequestEventListView.php',
'ReleephRequestException' => 'applications/releeph/storage/request/exception/ReleephRequestException.php',
'ReleephRequestHeaderListView' => 'applications/releeph/view/request/header/ReleephRequestHeaderListView.php',
'ReleephRequestHeaderView' => 'applications/releeph/view/request/header/ReleephRequestHeaderView.php',
'ReleephRequestIntentsView' => 'applications/releeph/view/request/ReleephRequestIntentsView.php',
'ReleephRequestMail' => 'applications/releeph/editor/mail/ReleephRequestMail.php',
'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php',
'ReleephRequestStatusView' => 'applications/releeph/view/request/ReleephRequestStatusView.php',
'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php',
'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php',
'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php',
'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php',
'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php',
'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php',
'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php',
@ -3493,17 +3495,20 @@ phutil_register_library_map(array(
'ReleephReasonFieldSpecification' => 'ReleephFieldSpecification',
'ReleephRequest' => 'ReleephDAO',
'ReleephRequestActionController' => 'ReleephController',
'ReleephRequestCreateController' => 'ReleephController',
'ReleephRequestCommentController' => 'ReleephController',
'ReleephRequestDifferentialCreateController' => 'ReleephController',
'ReleephRequestEditController' => 'ReleephController',
'ReleephRequestEditor' => 'PhabricatorEditor',
'ReleephRequestEvent' => 'ReleephDAO',
'ReleephRequestEventListView' => 'AphrontView',
'ReleephRequestException' => 'Exception',
'ReleephRequestHeaderListView' => 'AphrontView',
'ReleephRequestHeaderView' => 'AphrontView',
'ReleephRequestIntentsView' => 'AphrontView',
'ReleephRequestReplyHandler' => 'PhabricatorMailReplyHandler',
'ReleephRequestStatusView' => 'AphrontView',
'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction',
'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment',
'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor',
'ReleephRequestTypeaheadControl' => 'AphrontFormControl',
'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController',
'ReleephRequestViewController' => 'ReleephController',

View file

@ -58,7 +58,7 @@ final class PhabricatorApplicationReleeph extends PhabricatorApplication {
),
'request/' => array(
'(?P<requestID>[1-9]\d*)/' => 'ReleephRequestViewController',
'create/' => 'ReleephRequestCreateController',
'create/' => 'ReleephRequestEditController',
'differentialcreate/' => array(
'D(?P<diffRevID>[1-9]\d*)' =>
'ReleephRequestDifferentialCreateController',
@ -69,13 +69,15 @@ final class PhabricatorApplicationReleeph extends PhabricatorApplication {
'ReleephRequestActionController',
'typeahead/' =>
'ReleephRequestTypeaheadController',
'comment/(?P<requestID>[1-9]\d*)/' =>
'ReleephRequestCommentController',
),
// Branch navigation made pretty, as it's the most common:
'(?P<projectName>[^/]+)/(?P<branchName>[^/]+)/' => array(
'' => 'ReleephBranchViewController',
'edit/' => 'ReleephBranchEditController',
'request/' => 'ReleephRequestCreateController',
'request/' => 'ReleephRequestEditController',
'(?P<action>close|re-open)/' => 'ReleephBranchAccessController',
),
)

View file

@ -27,6 +27,7 @@ final class ConduitAPI_releeph_request_Method
}
protected function execute(ConduitAPIRequest $request) {
$user = $request->getUser();
$branch_phid = $request->getValue('branchPHID');
$releeph_branch = id(new ReleephBranch())
->loadOneWhere('phid = %s', $branch_phid);
@ -74,7 +75,7 @@ final class ConduitAPI_releeph_request_Method
foreach ($requested_commits as $thing => $commit) {
$phid = $commit->getPHID();
$handles = id(new PhabricatorObjectHandleData(array($phid)))
->setViewer($request->getUser())
->setViewer($user)
->loadHandles();
$name = id($handles[$phid])->getName();
@ -84,7 +85,25 @@ final class ConduitAPI_releeph_request_Method
if ($existing_releeph_request) {
$releeph_request = $existing_releeph_request;
} else {
$releeph_request = new ReleephRequest();
$releeph_request = id(new ReleephRequest())
->setRequestUserPHID($user->getPHID())
->setBranchID($releeph_branch->getID())
->setInBranch(0);
$xactions = array();
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
->setNewValue($commit->getPHID());
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
->setMetadataValue('userPHID', $user->getPHID())
->setMetadataValue(
'isAuthoritative',
$releeph_project->isAuthoritative($user))
->setNewValue(ReleephRequest::INTENT_WANT);
foreach ($fields as $field) {
if (!$field->isEditable()) {
continue;
@ -97,13 +116,20 @@ final class ConduitAPI_releeph_request_Method
->setErrorDescription($ex->getMessage());
}
}
id(new ReleephRequestEditor($releeph_request))
->setActor($request->getUser())
->create($commit, $releeph_branch);
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_CONDUIT,
array()));
$editor->applyTransactions($releeph_request, $xactions);
}
$releeph_branch->populateReleephRequestHandles(
$request->getUser(),
$user,
array($releeph_request));
$rq_handles = $releeph_request->getHandles();
$requestor_phid = $releeph_request->getRequestUserPHID();

View file

@ -7,8 +7,22 @@ final class ConduitAPI_releephwork_record_Method
return self::METHOD_STATUS_UNSTABLE;
}
/**
* Record that a request was committed locally, and is about to be pushed to
* the remote repository.
*
* This lets us mark a ReleephRequest as being in a branch in real time so
* that no one else tries to pick it.
*
* When the daemons discover this commit in the repository with
* DifferentialReleephRequestFieldSpecification, we'll be able to record the
* commit's PHID as well. That process is slow though, and we don't want to
* wait a whole minute before marking something as cleanly picked or
* reverted.
*/
public function getMethodDescription() {
return "Wrapper to ReleephRequestEditor->recordSuccessfulCommit().";
return "Record whether we committed a pick or revert ".
"to the upstream repository.";
}
public function defineParamTypes() {
@ -34,9 +48,22 @@ final class ConduitAPI_releephwork_record_Method
$releeph_request = id(new ReleephRequest())
->loadOneWhere('phid = %s', $request->getValue('requestPHID'));
id(new ReleephRequestEditor($releeph_request))
$xactions = array();
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_COMMIT)
->setMetadataValue('action', $action)
->setNewValue($new_commit_id);
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($request->getUser())
->recordSuccessfulCommit($action, $new_commit_id);
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_CONDUIT,
array()));
$editor->applyTransactions($releeph_request, $xactions);
}
}

View file

@ -8,7 +8,7 @@ final class ConduitAPI_releephwork_recordpickstatus_Method
}
public function getMethodDescription() {
return "Wrapper to ReleephRequestEditor->changePickStatus().";
return "Record whether a pick or revert was successful or not.";
}
public function defineParamTypes() {
@ -55,9 +55,23 @@ final class ConduitAPI_releephwork_recordpickstatus_Method
$releeph_request = id(new ReleephRequest())
->loadOneWhere('phid = %s', $request->getValue('requestPHID'));
id(new ReleephRequestEditor($releeph_request))
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($request->getUser())
->changePickStatus($pick_status, $dry_run, $details);
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_CONDUIT,
array()));
$xactions = array();
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_PICK_STATUS)
->setMetadataValue('dryRun', $dry_run)
->setMetadataValue('details', $details)
->setNewValue($pick_status);
$editor->applyTransactions($releeph_request, $xactions);
}
}

View file

@ -12,6 +12,7 @@ final class ReleephRequestActionController extends ReleephController {
public function processRequest() {
$request = $this->getRequest();
$releeph_project = $this->getReleephProject();
$releeph_branch = $this->getReleephBranch();
$releeph_request = $this->getReleephRequest();
@ -24,8 +25,17 @@ final class ReleephRequestActionController extends ReleephController {
$origin_uri = $releeph_request->loadReleephBranch()->getURI();
$editor = id(new ReleephRequestEditor($releeph_request))
->setActor($user);
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
)));
$xactions = array();
switch ($action) {
case 'want':
@ -34,21 +44,49 @@ final class ReleephRequestActionController extends ReleephController {
'want' => ReleephRequest::INTENT_WANT,
'pass' => ReleephRequest::INTENT_PASS);
$intent = $action_map[$action];
$editor->changeUserIntent($user, $intent);
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
->setMetadataValue(
'isAuthoritative',
$releeph_project->isAuthoritative($user))
->setNewValue($intent);
break;
case 'mark-manually-picked':
$editor->markManuallyActioned('pick');
break;
case 'mark-manually-reverted':
$editor->markManuallyActioned('revert');
if (!$releeph_project->isAuthoritative($user)) {
throw new Exception(
"Bug! Only authoritative users (pushers, or users in pusherless ".
"Releeph projects) can manually change a request's in-branch ".
"status!");
}
if ($action === 'mark-manually-picked') {
$in_branch = 1;
$intent = ReleephRequest::INTENT_WANT;
} else {
$in_branch = 0;
$intent = ReleephRequest::INTENT_PASS;
}
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
->setMetadataValue('isManual', true)
->setMetadataValue('isAuthoritative', true)
->setNewValue($intent);
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH)
->setNewValue($in_branch);
break;
default:
throw new Exception("unknown or unimplemented action {$action}");
}
$editor->applyTransactions($releeph_request, $xactions);
// If we're adding a new user to userIntents, we'll have to re-populate
// request handles to load that user's data.
//

View file

@ -0,0 +1,63 @@
<?php
final class ReleephRequestCommentController
extends ReleephController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$rq = $this->getReleephRequest();
if (!$request->isFormPost()) {
return new Aphront400Response();
}
$is_preview = $request->isPreviewRequest();
$draft = PhabricatorDraft::buildFromRequest($request);
$view_uri = $this->getApplicationURI('/RQ'.$rq->getID());
$xactions = array();
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ReleephRequestTransactionComment())
->setContent($request->getStr('comment')));
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($user)
->setContinueOnNoEffect($request->isContinueRequest())
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
)))
->setIsPreview($is_preview);
try {
$xactions = $editor->applyTransactions($rq, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
->setException($ex);
}
if ($draft) {
$draft->replaceOrDelete();
}
if ($request->isAjax()) {
return id(new PhabricatorApplicationTransactionResponse())
->setViewer($user)
->setTransactions($xactions)
->setIsPreview($is_preview)
->setAnchorOffset($request->getStr('anchor'));
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
}

View file

@ -1,165 +0,0 @@
<?php
final class ReleephRequestCreateController extends ReleephController {
const MAX_SUMMARY_LENGTH = 70;
public function processRequest() {
$request = $this->getRequest();
// We arrived via /releeph/request/create/?branchID=$id
$releeph_branch_id = $request->getInt('branchID');
if ($releeph_branch_id) {
$releeph_branch = id(new ReleephBranch())->load($releeph_branch_id);
} else {
// We arrived via /releeph/$project/$branch/request.
//
// If this throws an Exception, then somethind weird happened.
$releeph_branch = $this->getReleephBranch();
}
$releeph_project = $releeph_branch->loadReleephProject();
$repo = $releeph_project->loadPhabricatorRepository();
$request_identifier = $request->getStr('requestIdentifierRaw');
$e_request_identifier = true;
$releeph_request = new ReleephRequest();
$errors = array();
$selector = $releeph_project->getReleephFieldSelector();
$fields = $selector->getFieldSpecifications();
foreach ($fields as $field) {
$field
->setReleephProject($releeph_project)
->setReleephBranch($releeph_branch)
->setReleephRequest($releeph_request);
}
if ($request->isFormPost()) {
foreach ($fields as $field) {
if ($field->isEditable()) {
try {
$field->setValueFromAphrontRequest($request);
} catch (ReleephFieldParseException $ex) {
$errors[] = $ex->getMessage();
}
}
}
$pr_commit = null;
$finder = id(new ReleephCommitFinder())
->setReleephProject($releeph_project);
try {
$pr_commit = $finder->fromPartial($request_identifier);
} catch (Exception $e) {
$e_request_identifier = pht('Invalid');
$errors[] =
pht('Request %s is probably not a valid commit', $request_identifier);
$errors[] = $e->getMessage();
}
$pr_commit_data = null;
if (!$errors) {
$pr_commit_data = $pr_commit->loadCommitData();
if (!$pr_commit_data) {
$e_request_identifier = pht('Not parsed yet');
$errors[] = pht('The requested commit hasn\'t been parsed yet.');
}
}
if (!$errors) {
$existing = id(new ReleephRequest())
->loadOneWhere('requestCommitPHID = %s AND branchID = %d',
$pr_commit->getPHID(), $releeph_branch->getID());
if ($existing) {
return id(new AphrontRedirectResponse())
->setURI('/releeph/request/edit/'.$existing->getID().
'?existing=1');
}
id(new ReleephRequestEditor($releeph_request))
->setActor($request->getUser())
->create($pr_commit, $releeph_branch);
return id(new AphrontRedirectResponse())
->setURI($releeph_branch->getURI());
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle(pht('Form Errors'));
}
// For the typeahead
$branch_cut_point = id(new PhabricatorRepositoryCommit())
->loadOneWhere(
'phid = %s',
$releeph_branch->getCutPointCommitPHID());
// Build the form
$form = id(new AphrontFormView())
->setUser($request->getUser());
$origin = null;
$diff_rev_id = $request->getStr('D');
if ($diff_rev_id) {
$diff_rev = id(new DifferentialRevision())->load($diff_rev_id);
$origin = '/D'.$diff_rev->getID();
$title = sprintf(
'D%d: %s',
$diff_rev_id,
$diff_rev->getTitle());
$form
->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id)
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Diff'))
->setValue($title));
} else {
$origin = $releeph_branch->getURI();
$form->appendChild(
id(new ReleephRequestTypeaheadControl())
->setName('requestIdentifierRaw')
->setLabel('Commit ID')
->setRepo($repo)
->setValue($request_identifier)
->setError($e_request_identifier)
->setStartTime($branch_cut_point->getEpoch())
->setCaption(
pht('Start typing to autocomplete on commit title, '.
'or give a Phabricator commit identifier like rFOO1234')));
}
// Fields
foreach ($fields as $field) {
if ($field->isEditable()) {
$control = $field->renderEditControl($request);
$form->appendChild($control);
}
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($origin)
->setValue(pht('Request')));
$panel = id(new AphrontPanelView())
->setHeader(
pht('Request for %s',
$releeph_branch->getDisplayNameWithDetail()))
->setWidth(AphrontPanelView::WIDTH_FORM)
->appendChild($form);
return $this->buildStandardPageResponse(
array($error_view, $panel),
array('title' => pht('Request Pick')));
}
}

View file

@ -91,8 +91,8 @@ final class ReleephRequestDifferentialCreateController
}
private function buildReleephRequestURI(ReleephBranch $branch) {
return id(new PhutilURI('/releeph/request/create/'))
->setQueryParam('branchID', $branch->getID())
$uri = $branch->getURI('request/');
return id(new PhutilURI($uri))
->setQueryParam('D', $this->revision->getID());
}

View file

@ -2,39 +2,43 @@
final class ReleephRequestEditController extends ReleephController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'requestID');
parent::willProcessRequest($data);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$releeph_branch = $this->getReleephBranch();
$releeph_request = $this->getReleephRequest();
$releeph_project = $this->getReleephProject();
$releeph_branch = $this->getReleephBranch();
$releeph_branch->populateReleephRequestHandles(
$request->getUser(), array($releeph_request));
$request_identifier = $request->getStr('requestIdentifierRaw');
$e_request_identifier = true;
$phids = array();
$phids[] = $releeph_request->getRequestCommitPHID();
$phids[] = $releeph_request->getRequestUserPHID();
// Load the RQ we're editing, or create a new one
if ($this->id) {
$rq = id(new ReleephRequest())->load($this->id);
$is_edit = true;
} else {
$is_edit = false;
$rq = id(new ReleephRequest())
->setRequestUserPHID($user->getPHID())
->setBranchID($releeph_branch->getID())
->setInBranch(0);
}
$handles = id(new PhabricatorObjectHandleData($phids))
->setViewer($request->getUser())
->loadHandles();
$age_string = phabricator_format_relative_time(
time() - $releeph_request->getDateCreated());
// Warn the user if we see this
$notice_view = null;
if ($request->getInt('existing')) {
$notice_messages = array(
'You are editing an existing pick request!',
hsprintf(
"Requested %s ago by %s",
$age_string,
$handles[$releeph_request->getRequestUserPHID()]->renderLink())
);
$notice_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors($notice_messages);
// Load all the ReleephFieldSpecifications
$selector = $this->getReleephProject()->getReleephFieldSelector();
$fields = $selector->getFieldSpecifications();
foreach ($fields as $field) {
$field
->setReleephProject($releeph_project)
->setReleephBranch($releeph_branch)
->setReleephRequest($rq);
}
// <aidehua> epriestley: Is it common to pass around a referer URL to
@ -43,43 +47,134 @@ final class ReleephRequestEditController extends ReleephController {
// rather than the full URL.
switch ($request->getStr('origin')) {
case 'request':
$origin_uri = '/RQ'.$releeph_request->getID();
$origin_uri = '/RQ'.$rq->getID();
break;
case 'branch':
default:
$origin_uri = $releeph_request->loadReleephBranch()->getURI();
$origin_uri = $releeph_branch->getURI();
break;
}
// Make edits
$errors = array();
$selector = $this->getReleephProject()->getReleephFieldSelector();
$fields = $selector->getFieldSpecifications();
foreach ($fields as $field) {
$field
->setReleephProject($this->getReleephProject())
->setReleephBranch($this->getReleephBranch())
->setReleephRequest($this->getReleephRequest());
}
if ($request->isFormPost()) {
foreach ($fields as $field) {
if ($field->isEditable()) {
$xactions = array();
// The commit-identifier being requested...
if (!$is_edit) {
if ($request_identifier ===
ReleephRequestTypeaheadControl::PLACEHOLDER) {
$errors[] = "No commit ID was provided.";
$e_request_identifier = 'Required';
} else {
$pr_commit = null;
$finder = id(new ReleephCommitFinder())
->setReleephProject($releeph_project);
try {
$field->setValueFromAphrontRequest($request);
} catch (ReleephFieldParseException $ex) {
$errors[] = $ex->getMessage();
$pr_commit = $finder->fromPartial($request_identifier);
} catch (Exception $e) {
$e_request_identifier = 'Invalid';
$errors[] =
"Request {$request_identifier} is probably not a valid commit";
$errors[] = $e->getMessage();
}
$pr_commit_data = null;
if (!$errors) {
$pr_commit_data = $pr_commit->loadCommitData();
if (!$pr_commit_data) {
$e_request_identifier = 'Not parsed yet';
$errors[] = "The requested commit hasn't been parsed yet.";
}
}
}
if (!$errors) {
$existing = id(new ReleephRequest())
->loadOneWhere('requestCommitPHID = %s AND branchID = %d',
$pr_commit->getPHID(), $releeph_branch->getID());
if ($existing) {
return id(new AphrontRedirectResponse())
->setURI('/releeph/request/edit/'.$existing->getID().
'?existing=1');
}
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
->setNewValue($pr_commit->getPHID());
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
// To help hide these implicit intents...
->setMetadataValue('isRQCreate', true)
->setMetadataValue('userPHID', $user->getPHID())
->setMetadataValue(
'isAuthoritative',
$releeph_project->isAuthoritative($user))
->setNewValue(ReleephRequest::INTENT_WANT);
}
}
if (!$errors) {
foreach ($fields as $field) {
if ($field->isEditable()) {
try {
$data = $request->getRequestData();
$value = idx($data, $field->getRequiredStorageKey());
$field->validate($value);
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_EDIT_FIELD)
->setMetadataValue('fieldClass', get_class($field))
->setNewValue($value);
} catch (ReleephFieldParseException $ex) {
$errors[] = $ex->getMessage();
}
}
}
}
if (!$errors) {
$releeph_request->save();
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
)));
$editor->applyTransactions($rq, $xactions);
return id(new AphrontRedirectResponse())->setURI($origin_uri);
}
}
$releeph_branch->populateReleephRequestHandles($user, array($rq));
$handles = $rq->getHandles();
$age_string = '';
if ($is_edit) {
$age_string = phabricator_format_relative_time(
time() - $rq->getDateCreated()) . ' ago';
}
// Warn the user if we've been redirected here because we tried to
// re-request something.
$notice_view = null;
if ($request->getInt('existing')) {
$notice_messages = array(
'You are editing an existing pick request!',
hsprintf(
"Requested %s by %s",
$age_string,
$handles[$rq->getRequestUserPHID()]->renderLink())
);
$notice_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors($notice_messages);
}
/**
* Build the rest of the page
*/
@ -91,19 +186,58 @@ final class ReleephRequestEditController extends ReleephController {
}
$form = id(new AphrontFormView())
->setUser($request->getUser())
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Original Commit')
->setValue(
$handles[$releeph_request->getRequestCommitPHID()]->renderLink()))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Requestor')
->setValue(hsprintf(
'%s %s ago',
$handles[$releeph_request->getRequestUserPHID()]->renderLink(),
$age_string)));
->setUser($request->getUser());
if ($is_edit) {
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Original Commit')
->setValue(
$handles[$rq->getRequestCommitPHID()]->renderLink()))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Requestor')
->setValue(hsprintf(
'%s %s',
$handles[$rq->getRequestUserPHID()]->renderLink(),
$age_string)));
} else {
$origin = null;
$diff_rev_id = $request->getStr('D');
if ($diff_rev_id) {
$diff_rev = id(new DifferentialRevision())->load($diff_rev_id);
$origin = '/D'.$diff_rev->getID();
$title = sprintf(
'D%d: %s',
$diff_rev_id,
$diff_rev->getTitle());
$form
->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id)
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Diff')
->setValue($title));
} else {
$origin = $releeph_branch->getURI();
$repo = $releeph_project->loadPhabricatorRepository();
$branch_cut_point = id(new PhabricatorRepositoryCommit())
->loadOneWhere(
'phid = %s',
$releeph_branch->getCutPointCommitPHID());
$form->appendChild(
id(new ReleephRequestTypeaheadControl())
->setName('requestIdentifierRaw')
->setLabel('Commit ID')
->setRepo($repo)
->setValue($request_identifier)
->setError($e_request_identifier)
->setStartTime($branch_cut_point->getEpoch())
->setCaption(
'Start typing to autocomplete on commit title, '.
'or give a Phabricator commit identifier like rFOO1234'));
}
}
// Fields
foreach ($fields as $field) {
@ -113,19 +247,27 @@ final class ReleephRequestEditController extends ReleephController {
}
}
if ($is_edit) {
$title = pht('Edit Releeph Request');
$submit_name = pht('Save');
} else {
$title = pht('Create Releeph Request');
$submit_name = pht('Create');
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($origin_uri, 'Cancel')
->setValue('Save'));
->setValue($submit_name));
$panel = id(new AphrontPanelView())
->setHeader('Edit Pick Request')
->setHeader($title)
->setWidth(AphrontPanelView::WIDTH_FORM)
->appendChild($form);
return $this->buildStandardPageResponse(
array($notice_view, $error_view, $panel),
array('title', 'Edit Pick Request'));
array('title', $title));
}
}

View file

@ -29,41 +29,43 @@ final class ReleephRequestViewController extends ReleephController {
->setReloadOnStateChange(true)
->setOriginType('request');
$events = $releeph_request->loadEvents();
$phids = array_mergev(mpull($events, 'extractPHIDs'));
$handles = id(new PhabricatorObjectHandleData($phids))
->setViewer($request->getUser())
->loadHandles();
$user = $request->getUser();
$rq_event_list_view =
id(new ReleephRequestEventListView())
->setUser($request->getUser())
->setEvents($events)
->setHandles($handles);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
// Handle comment submit
$origin_uri = '/RQ'.$releeph_request->getID();
if ($request->isFormPost()) {
id(new ReleephRequestEditor($releeph_request))
->setActor($request->getUser())
->addComment($request->getStr('comment'));
return id(new AphrontRedirectResponse())->setURI($origin_uri);
$xactions = id(new ReleephRequestTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($releeph_request->getPHID()))
->execute();
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$form = id(new AphrontFormView())
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($request->getUser())
->appendChild(
id(new AphrontFormTextAreaControl())
->setName('comment'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($origin_uri, 'Cancel')
->setValue("Submit"));
->setTransactions($xactions)
->setMarkupEngine($engine);
$rq_comment_form = id(new AphrontPanelView())
->setHeader('Add a comment')
->setWidth(AphrontPanelView::WIDTH_FULL)
->appendChild($form);
$add_comment_header = id(new PhabricatorHeaderView())
->setHeader('Plea or yield');
$draft = PhabricatorDraft::newFromUserAndKey(
$user,
$releeph_request->getPHID());
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setDraft($draft)
->setAction($this->getApplicationURI(
'/request/comment/'.$releeph_request->getID().'/'))
->setSubmitButtonName('Comment');
$title = hsprintf("RQ%d: %s",
$releeph_request->getID(),
@ -88,8 +90,9 @@ final class ReleephRequestViewController extends ReleephController {
$crumbs,
array(
$rq_view,
$rq_event_list_view,
$rq_comment_form
$timeline,
$add_comment_header,
$add_comment_form,
)
),
array(

View file

@ -268,9 +268,26 @@ final class DifferentialReleephRequestFieldSpecification
$actor = id(new PhabricatorUser())
->loadOneWhere('phid = %s', $actor_phid);
id(new ReleephRequestEditor($releeph_request))
$xactions = array();
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY)
->setMetadataValue('action', $action)
->setMetadataValue('authorPHID',
$data->getCommitDetail('authorPHID'))
->setMetadataValue('committerPHID',
$data->getCommitDetail('committerPHID'))
->setNewValue($commit->getPHID());
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($actor)
->discoverCommit($action, $commit, $data);
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_UNKNOWN,
array()));
$editor->applyTransactions($releeph_request, $xactions);
}
}

View file

@ -1,402 +0,0 @@
<?php
/**
* Provide methods for the common ways of creating and mutating a
* ReleephRequest, sending email when something interesting happens.
*
* This class generates ReleephRequestEvents, and each type of event
* (ReleephRequestEvent::TYPE_*) corresponds to one of the editor methods.
*
* The editor methods (except for create() use newEvent() and commit() to save
* some code duplication.
*/
final class ReleephRequestEditor extends PhabricatorEditor {
private $releephRequest;
private $event;
private $silentUpdate;
public function __construct(ReleephRequest $rq) {
$this->releephRequest = $rq;
}
public function setSilentUpdate($silent) {
$this->silentUpdate = $silent;
return $this;
}
/* -( ReleephRequest edit methods )---------------------------------------- */
/**
* Request a PhabricatorRepositoryCommit to be committed to the given
* ReleephBranch.
*/
public function create(PhabricatorRepositoryCommit $commit,
ReleephBranch $branch) {
// We can't use newEvent() / commit() abstractions, so do what those
// helpers do manually.
$requestor = $this->requireActor();
$rq = $this->releephRequest;
$rq->openTransaction();
$rq
->setBranchID($branch->getID())
->setRequestCommitPHID($commit->getPHID())
->setInBranch(0)
->setRequestUserPHID($requestor->getPHID())
->setUserIntent($requestor, ReleephRequest::INTENT_WANT)
->save();
$event = id(new ReleephRequestEvent())
->setType(ReleephRequestEvent::TYPE_CREATE)
->setActorPHID($requestor->getPHID())
->setStatusBefore(null)
->setStatusAfter($rq->getStatus())
->setReleephRequestID($rq->getID())
->setDetail('commitPHID', $commit->getPHID())
->save();
$rq->saveTransaction();
// Mail
if (!$this->silentUpdate) {
$project = $this->releephRequest->loadReleephProject();
$mail = id(new ReleephRequestMail())
->setReleephRequest($this->releephRequest)
->setReleephProject($project)
->setEvents(array($event))
->setSenderAndRecipientPHID($requestor->getPHID())
->addTos(ReleephRequestMail::ENT_ALL_PUSHERS)
->addCCs(ReleephRequestMail::ENT_REQUESTOR)
->send();
}
}
/**
* Record whether the PhabricatorUser wants or passes on this request.
*/
public function changeUserIntent(PhabricatorUser $user, $intent) {
$project = $this->releephRequest->loadReleephProject();
$is_pusher = $project->isPusher($user);
$event = $this->newEvent()
->setType(ReleephRequestEvent::TYPE_USER_INTENT)
->setDetail('userPHID', $user->getPHID())
->setDetail('wasPusher', $is_pusher)
->setDetail('newIntent', $intent);
$this->releephRequest
->setUserIntent($user, $intent);
$this->commit();
// Mail if this is 'interesting'
if (!$this->silentUpdate &&
$event->getStatusBefore() != $event->getStatusAfter()) {
$project = $this->releephRequest->loadReleephProject();
$mail = id(new ReleephRequestMail())
->setReleephRequest($this->releephRequest)
->setReleephProject($project)
->setEvents(array($event))
->setSenderAndRecipientPHID($this->requireActor()->getPHID())
->addTos(ReleephRequestMail::ENT_REQUESTOR)
->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS)
->send();
}
}
/**
* Record the results of someone trying to pick or revert a request in their
* local repository, to give advance warning that something doesn't pick or
* revert cleanly.
*/
public function changePickStatus($pick_status, $dry_run, $details) {
$event = $this->newEvent()
->setType(ReleephRequestEvent::TYPE_PICK_STATUS)
->setDetail('newPickStatus', $pick_status)
->setDetail('commitDetails', $details);
$this->releephRequest->setPickStatus($pick_status);
$this->commit();
// Failures should generate an email
if (!$this->silentUpdate &&
!$dry_run &&
($pick_status == ReleephRequest::PICK_FAILED ||
$pick_status == ReleephRequest::REVERT_FAILED)) {
$project = $this->releephRequest->loadReleephProject();
$mail = id(new ReleephRequestMail())
->setReleephRequest($this->releephRequest)
->setReleephProject($project)
->setEvents(array($event))
->setSenderAndRecipientPHID($this->requireActor()->getPHID())
->addTos(ReleephRequestMail::ENT_REQUESTOR)
->addCCs(ReleephRequestMail::ENT_ACTORS)
->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS)
->send();
}
}
/**
* Record that a request was committed locally, and is about to be pushed to
* the remote repository.
*
* This lets us mark a ReleephRequest as being in a branch in real time so
* that no one else tries to pick it.
*
* When the daemons discover this commit in the repository with
* DifferentialReleephRequestFieldSpecification, we'll be able to recrod the
* commit's PHID as well. That process is slow though, and
* we don't want to wait a whole minute before marking something as cleanly
* picked or reverted.
*/
public function recordSuccessfulCommit($action, $new_commit_id) {
$table = $this->releephRequest;
$table->openTransaction();
$actor = $this->requireActor();
$event = id(new ReleephRequestEvent())
->setReleephRequestID($this->releephRequest->getID())
->setActorPHID($actor->getPHID())
->setType(ReleephRequestEvent::TYPE_COMMIT)
->setDetail('action', $action)
->setDetail('newCommitIdentifier', $new_commit_id)
->save();
switch ($action) {
case 'pick':
$this->releephRequest
->setInBranch(1)
->setPickStatus(ReleephRequest::PICK_OK)
->setCommitIdentifier($new_commit_id)
->setCommitPHID(null)
->save();
break;
case 'revert':
$this->releephRequest
->setInBranch(0)
->setPickStatus(ReleephRequest::REVERT_OK)
->setCommitIdentifier(null)
->setCommitPHID(null)
->save();
break;
default:
$table->killTransaction();
throw new Exception("Unknown action {$action}!");
break;
}
$table->saveTransaction();
// Don't spam people about local commits -- we'll do that with
// discoverCommit() instead!
}
/**
* Mark this request as picked or reverted based on discovering it in the
* branch. We have a PhabricatorRepositoryCommit, so we're able to
* setCommitPHID on the ReleephRequest (unlike recordSuccessfulCommit()).
*/
public function discoverCommit(
$action,
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data) {
$table = $this->releephRequest;
$table->openTransaction();
$table->beginWriteLocking();
$past_events = id(new ReleephRequestEvent())->loadAllWhere(
'releephRequestID = %d AND type = %s',
$this->releephRequest->getID(),
ReleephRequestEvent::TYPE_DISCOVERY);
foreach ($past_events as $past_event) {
if ($past_event->getDetail('newCommitIdentifier')
== $commit->getCommitIdentifier()) {
// Avoid re-discovery if reparsing!
$table->endWriteLocking();
$table->killTransaction();
return;
}
}
$actor = $this->requireActor();
$event = id(new ReleephRequestEvent())
->setReleephRequestID($this->releephRequest->getID())
->setActorPHID($actor->getPHID())
->setType(ReleephRequestEvent::TYPE_DISCOVERY)
->setDateCreated($commit->getEpoch())
->setDetail('action', $action)
->setDetail('newCommitIdentifier', $commit->getCommitIdentifier())
->setDetail('newCommitPHID', $commit->getPHID())
->setDetail('authorPHID', $data->getCommitDetail('authorPHID'))
->setDetail('committerPHID', $data->getCommitDetail('committerPHID'))
->save();
switch ($action) {
case 'pick':
$this->releephRequest
->setInBranch(1)
->setPickStatus(ReleephRequest::PICK_OK)
->setCommitIdentifier($commit->getCommitIdentifier())
->setCommitPHID($commit->getPHID())
->save();
break;
case 'revert':
$this->releephRequest
->setInBranch(0)
->setPickStatus(ReleephRequest::REVERT_OK)
->setCommitIdentifier(null)
->setCommitPHID(null)
->save();
break;
default:
$table->killTransaction();
throw new Exception("Unknown action {$action}!");
break;
}
$table->endWriteLocking();
$table->saveTransaction();
// Mail
if (!$this->silentUpdate) {
$project = $this->releephRequest->loadReleephProject();
$mail = id(new ReleephRequestMail())
->setReleephRequest($this->releephRequest)
->setReleephProject($project)
->setEvents(array($event))
->setSenderAndRecipientPHID($this->requireActor()->getPHID())
->addTos(ReleephRequestMail::ENT_REQUESTOR)
->addCCs(ReleephRequestMail::ENT_ACTORS)
->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS)
->send();
}
}
public function addComment($comment) {
$event = $this->newEvent()
->setType(ReleephRequestEvent::TYPE_COMMENT)
->setDetail('comment', $comment);
$this->commit();
// Mail
if (!$this->silentUpdate) {
$project = $this->releephRequest->loadReleephProject();
$mail = id(new ReleephRequestMail())
->setReleephRequest($this->releephRequest)
->setReleephProject($project)
->setEvents(array($event))
->setSenderAndRecipientPHID($this->requireActor()->getPHID())
->addTos(ReleephRequestMail::ENT_REQUESTOR)
->addCCs(ReleephRequestMail::ENT_ACTORS)
->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS)
->send();
}
}
public function markManuallyActioned($action) {
$event = $this->newEvent()
->setType(ReleephRequestEvent::TYPE_MANUAL_ACTION)
->setDetail('action', $action);
$actor = $this->requireActor();
$project = $this->releephRequest->loadReleephProject();
$requestor_phid = $this->releephRequest->getRequestUserPHID();
if (!$project->isPusher($actor) &&
$actor->getPHID() !== $requestor_phid) {
throw new Exception(
"Only pushers or requestors can mark requests as ".
"manually picked or reverted!");
}
switch ($action) {
case 'pick':
$in_branch = true;
$intent = ReleephRequest::INTENT_WANT;
break;
case 'revert':
$in_branch = false;
$intent = ReleephRequest::INTENT_PASS;
break;
default:
throw new Exception("Unknown action {$action}!");
break;
}
$this->releephRequest
->setInBranch((int)$in_branch)
->setUserIntent($this->getActor(), $intent);
$this->commit();
// Mail
if (!$this->silentUpdate) {
$project = $this->releephRequest->loadReleephProject();
$mail = id(new ReleephRequestMail())
->setReleephRequest($this->releephRequest)
->setReleephProject($project)
->setEvents(array($event))
->setSenderAndRecipientPHID($this->requireActor()->getPHID())
->addTos(ReleephRequestMail::ENT_REQUESTOR)
->addCCs(ReleephRequestMail::ENT_INTERESTED_PUSHERS)
->send();
}
}
/* -( Implementation )----------------------------------------------------- */
/**
* Create and return a new ReleephRequestEvent bound to the editor's
* ReleephRequest, inside a transaction.
*
* When you call commit(), the event and this editor's ReleephRequest (along
* with any changes you made to the ReleephRequest) are saved and the
* transaction committed.
*/
private function newEvent() {
$actor = $this->requireActor();
if ($this->event) {
throw new Exception("You have already called newEvent()!");
}
$rq = $this->releephRequest;
$rq->openTransaction();
$this->event = id(new ReleephRequestEvent())
->setReleephRequestID($rq->getID())
->setActorPHID($actor->getPHID())
->setStatusBefore($rq->getStatus());
return $this->event;
}
private function commit() {
if (!$this->event) {
throw new Exception("You must call newEvent first!");
}
$rq = $this->releephRequest;
$this->event
->setStatusAfter($rq->getStatus())
->save();
$rq->save();
$rq->saveTransaction();
$this->event = null;
}
}

View file

@ -0,0 +1,300 @@
<?php
final class ReleephRequestTransactionalEditor
extends PhabricatorApplicationTransactionEditor {
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = ReleephRequestTransaction::TYPE_COMMIT;
$types[] = ReleephRequestTransaction::TYPE_DISCOVERY;
$types[] = ReleephRequestTransaction::TYPE_EDIT_FIELD;
$types[] = ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH;
$types[] = ReleephRequestTransaction::TYPE_PICK_STATUS;
$types[] = ReleephRequestTransaction::TYPE_REQUEST;
$types[] = ReleephRequestTransaction::TYPE_USER_INTENT;
return $types;
}
public function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ReleephRequestTransaction::TYPE_REQUEST:
return $object->getRequestCommitPHID();
case ReleephRequestTransaction::TYPE_EDIT_FIELD:
$field = newv($xaction->getMetadataValue('fieldClass'), array());
$value = $field->setReleephRequest($object)->getValue();
return $value;
case ReleephRequestTransaction::TYPE_USER_INTENT:
$user_phid = $xaction->getAuthorPHID();
return idx($object->getUserIntents(), $user_phid);
case ReleephRequestTransaction::TYPE_PICK_STATUS:
return (int)$object->getPickStatus();
break;
case ReleephRequestTransaction::TYPE_COMMIT:
return $object->getCommitIdentifier();
case ReleephRequestTransaction::TYPE_DISCOVERY:
return $object->getCommitPHID();
case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
return $object->getInBranch();
}
}
public function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ReleephRequestTransaction::TYPE_REQUEST:
case ReleephRequestTransaction::TYPE_USER_INTENT:
case ReleephRequestTransaction::TYPE_EDIT_FIELD:
case ReleephRequestTransaction::TYPE_PICK_STATUS:
case ReleephRequestTransaction::TYPE_COMMIT:
case ReleephRequestTransaction::TYPE_DISCOVERY:
case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
return $xaction->getNewValue();
}
}
public function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$new = $xaction->getNewValue();
switch ($xaction->getTransactionType()) {
case ReleephRequestTransaction::TYPE_REQUEST:
$object->setRequestCommitPHID($new);
break;
case ReleephRequestTransaction::TYPE_USER_INTENT:
$user_phid = $xaction->getAuthorPHID();
$intents = $object->getUserIntents();
$intents[$user_phid] = $new;
$object->setUserIntents($intents);
break;
case ReleephRequestTransaction::TYPE_EDIT_FIELD:
$field = newv($xaction->getMetadataValue('fieldClass'), array());
$field
->setReleephRequest($object)
->setValue($new);
break;
case ReleephRequestTransaction::TYPE_PICK_STATUS:
$object->setPickStatus($new);
break;
case ReleephRequestTransaction::TYPE_COMMIT:
$this->setInBranchFromAction($object, $xaction);
$object->setCommitIdentifier($new);
break;
case ReleephRequestTransaction::TYPE_DISCOVERY:
$this->setInBranchFromAction($object, $xaction);
$object->setCommitPHID($new);
break;
case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
$object->setInBranch((int) $new);
break;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return;
}
protected function filterTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
// Remove TYPE_DISCOVERY xactions that are the result of a reparse.
$previously_discovered_commits = array();
$discovery_xactions = idx(
mgroup($xactions, 'getTransactionType'),
ReleephRequestTransaction::TYPE_DISCOVERY);
if ($discovery_xactions) {
$previous_xactions = id(new ReleephRequestTransactionQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withObjectPHIDs(array($object->getPHID()))
->execute();
foreach ($previous_xactions as $xaction) {
if ($xaction->getTransactionType() ===
ReleephRequestTransaction::TYPE_DISCOVERY) {
$commit_phid = $xaction->getNewValue();
$previously_discovered_commits[$commit_phid] = true;
}
}
}
foreach ($xactions as $key => $xaction) {
if ($xaction->getTransactionType() ===
ReleephRequestTransaction::TYPE_DISCOVERY &&
idx($previously_discovered_commits, $xaction->getNewValue())) {
unset($xactions[$key]);
}
}
return parent::filterTransactions($object, $xactions);
}
protected function supportsMail() {
return true;
}
protected function sendMail(
PhabricatorLiskDAO $object,
array $xactions) {
// Avoid sending emails that only talk about commit discovery.
$types = array_unique(mpull($xactions, 'getTransactionType'));
if ($types === array(ReleephRequestTransaction::TYPE_DISCOVERY)) {
return null;
}
// Don't email people when we discover that something picks or reverts OK.
if ($types === array(ReleephRequestTransaction::TYPE_PICK_STATUS)) {
if (!mfilter($xactions, 'isBoringPickStatus', true /* negate */)) {
// If we effectively call "isInterestingPickStatus" and get nothing...
return null;
}
}
return parent::sendMail($object, $xactions);
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new ReleephRequestReplyHandler())
->setActor($this->getActor())
->setMailReceiver($object);
}
protected function getMailSubjectPrefix() {
return '[Releeph]';
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$phid = $object->getPHID();
$title = $object->getSummaryForDisplay();
return id(new PhabricatorMetaMTAMail())
->setSubject("RQ{$id}: {$title}")
->addHeader('Thread-Topic', "RQ{$id}: {$phid}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {
$to_phids = array();
$releeph_project = $object->loadReleephProject();
foreach ($releeph_project->getPushers() as $phid) {
$to_phids[] = $phid;
}
foreach ($object->getUserIntents() as $phid => $intent) {
$to_phids[] = $phid;
}
return $to_phids;
}
protected function getMailCC(PhabricatorLiskDAO $object) {
return array();
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$rq = $object;
$releeph_branch = $rq->loadReleephBranch();
$releeph_project = $releeph_branch->loadReleephProject();
/**
* If any of the events we are emailing about were about a pick failure
* (and/or a revert failure?), include pick failure instructions.
*/
$has_pick_failure = false;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() ===
ReleephRequestTransaction::TYPE_PICK_STATUS &&
$xaction->getNewValue() === ReleephRequest::PICK_FAILED) {
$has_pick_failure = true;
break;
}
}
if ($has_pick_failure) {
$instructions = $releeph_project->getDetail('pick_failure_instructions');
if ($instructions) {
$body->addTextSection(
pht('PICK FAILURE INSTRUCTIONS'),
$instructions);
}
}
$name = sprintf("RQ%s: %s", $rq->getID(), $rq->getSummaryForDisplay());
$body->addTextSection(
pht('RELEEPH REQUEST'),
$name."\n".
PhabricatorEnv::getProductionURI('/RQ'.$rq->getID()));
$project_and_branch = sprintf(
'%s - %s',
$releeph_project->getName(),
$releeph_branch->getDisplayNameWithDetail());
$body->addTextSection(
pht('RELEEPH BRANCH'),
$project_and_branch."\n".
$releeph_branch->getURI());
return $body;
}
private function setInBranchFromAction(
ReleephRequest $rq,
ReleephRequestTransaction $xaction) {
$action = $xaction->getMetadataValue('action');
switch ($action) {
case 'pick':
$rq->setInBranch(1);
break;
case 'revert':
$rq->setInBranch(0);
break;
default:
$id = $rq->getID();
$type = $xaction->getTransactionType();
$new = $xaction->getNewValue();
phlog(
"Unknown discovery action '{$action}' ".
"for xaction of type {$type} ".
"with new value {$new} ".
"mentioning RQ{$id}!");
break;
}
}
}

View file

@ -1,213 +0,0 @@
<?php
/**
* Build an email that renders a group of events with and appends some standard
* Releeph things (a URI for this request, and this branch).
*
* Also includes some helper stuff for adding groups of people to the To: and
* Cc: headers.
*/
final class ReleephRequestMail {
const ENT_REQUESTOR = 'requestor';
const ENT_DIFF = 'diff';
const ENT_ALL_PUSHERS = 'pushers';
const ENT_ACTORS = 'actors';
const ENT_INTERESTED_PUSHERS = 'interested-pushers';
private $sender;
private $tos = array();
private $ccs = array();
private $events;
private $releephRequest;
private $releephProject;
public function setReleephRequest(ReleephRequest $rq) {
$this->releephRequest = $rq;
return $this;
}
public function setReleephProject(ReleephProject $rp) {
$this->releephProject = $rp;
return $this;
}
public function setEvents(array $events) {
assert_instances_of($events, 'ReleephRequestEvent');
$this->events = $events;
return $this;
}
public function setSenderAndRecipientPHID($sender_phid) {
$this->sender = $sender_phid;
$this->tos[] = $sender_phid;
return $this;
}
public function addTos($entity) {
$this->tos = array_merge(
$this->tos,
$this->getEntityPHIDs($entity));
return $this;
}
public function addCcs($entity) {
$this->ccs = array_merge(
$this->tos,
$this->getEntityPHIDs($entity));
return $this;
}
public function send() {
$this->buildMail()->save();
}
public function buildMail() {
return id(new PhabricatorMetaMTAMail())
->setSubject($this->renderSubject())
->setBody($this->buildBody()->render())
->setFrom($this->sender)
->addTos($this->tos)
->addCCs($this->ccs);
}
private function getEntityPHIDs($entity) {
$phids = array();
switch ($entity) {
// The requestor
case self::ENT_REQUESTOR:
$phids[] = $this->releephRequest->getRequestUserPHID();
break;
// People on the original diff
case self::ENT_DIFF:
$commit = $this->releephRequest->loadPhabricatorRepositoryCommit();
$commit_data = $commit->loadCommitData();
if ($commit_data) {
$phids[] = $commit_data->getCommitDetail('reviewerPHID');
$phids[] = $commit_data->getCommitDetail('authorPHID');
}
break;
// All pushers for this project
case self::ENT_ALL_PUSHERS:
$phids = array_merge(
$phids,
$this->releephProject->getPushers());
break;
// Pushers who have explicitly wanted or passed on this request
case self::ENT_INTERESTED_PUSHERS:
$all_pushers = $this->releephProject->getPushers();
$intents = $this->releephRequest->getUserIntents();
foreach ($all_pushers as $pusher) {
if (idx($intents, $pusher)) {
$phids[] = $pusher;
}
}
break;
// Anyone who created our list of events
case self::ENT_ACTORS:
$phids = array_merge(
$phids,
mpull($this->events, 'getActorPHID'));
break;
default:
throw new Exception(
"Unknown entity type {$entity}!");
break;
}
return array_filter($phids);
}
private function buildBody() {
$body = new PhabricatorMetaMTAMailBody();
$rq = $this->releephRequest;
// Events and comments
$phids = array(
$rq->getPHID(),
);
foreach ($this->events as $event) {
$phids = array_merge($phids, $event->extractPHIDs());
}
$handles = id(new PhabricatorObjectHandleData($phids))
// By the time we're generating email, we can assume that whichever
// entitties are receving the email are authorized to see the loaded
// handles!
->setViewer(PhabricatorUser::getOmnipotentUser())
->loadHandles();
$raw_events = id(new ReleephRequestEventListView())
->setUser(PhabricatorUser::getOmnipotentUser())
->setHandles($handles)
->setEvents($this->events)
->renderForEmail();
$body->addRawSection($raw_events);
$project = $rq->loadReleephProject();
$branch = $rq->loadReleephBranch();
/**
* If any of the events we are emailing about were TYPE_PICK_STATUS where
* the newPickStatus was a pick failure (and/or a revert failure?), include
* pick failure instructions.
*/
$pick_failure_events = array();
foreach ($this->events as $event) {
if ($event->getType() == ReleephRequestEvent::TYPE_PICK_STATUS &&
$event->getDetail('newPickStatus') == ReleephRequest::PICK_FAILED) {
$pick_failure_events[] = $event;
}
}
if ($pick_failure_events) {
$instructions = $project->getDetail('pick_failure_instructions');
if ($instructions) {
$body->addTextSection('PICK FAILURE INSTRUCTIONS', $instructions);
}
}
// Common stuff at the end
$body->addTextSection(
'RELEEPH REQUEST',
$handles[$rq->getPHID()]->getFullName()."\n".
PhabricatorEnv::getProductionURI('/RQ'.$rq->getID()));
$project_and_branch = sprintf(
'%s - %s',
$project->getName(),
$branch->getDisplayNameWithDetail());
$body->addTextSection(
'RELEEPH BRANCH',
$project_and_branch."\n".
$branch->getURI());
// But verbose stuff at the *very* end!
foreach ($pick_failure_events as $event) {
$failure_details = $event->getDetail('commitDetails');
if ($failure_details) {
$body->addRawSection('PICK FAILURE DETAILS');
foreach ($failure_details as $heading => $data) {
$body->addTextSection($heading, $data);
}
}
}
return $body;
}
private function renderSubject() {
$rq = $this->releephRequest;
$id = $rq->getID();
$summary = $rq->getSummaryForDisplay();
return "RQ{$id}: {$summary}";
}
}

View file

@ -11,6 +11,22 @@ abstract class ReleephFieldSpecification
return null;
}
public function getRequiredStorageKey() {
$key = $this->getStorageKey();
if ($key === null) {
throw new ReleephFieldSpecificationIncompleteException($this);
}
if (strpos($key, '.') !== false) {
/**
* Storage keys are reused for form controls, and periods in form control
* names break HTML forms.
*/
throw new Exception(
"You can't use '.' in storage keys!");
}
return $key;
}
final public function isEditable() {
return $this->getStorageKey() !== null;
}
@ -37,6 +53,17 @@ abstract class ReleephFieldSpecification
return;
}
/**
* Turn values as they are stored in a ReleephRequest into a text that can be
* rendered as a transactions old/new values.
*/
public function normalizeForTransactionView(
PhabricatorApplicationTransaction $xaction,
$value) {
return $value;
}
/* -( Header View )-------------------------------------------------------- */
@ -60,13 +87,6 @@ abstract class ReleephFieldSpecification
throw new ReleephFieldSpecificationIncompleteException($this);
}
public function setValueFromAphrontRequest(AphrontRequest $request) {
$data = $request->getRequestData();
$value = idx($data, $this->getRequiredStorageKey());
$this->validate($value);
$this->setValue($value);
}
/* -( Conduit )------------------------------------------------------------ */
@ -300,22 +320,6 @@ abstract class ReleephFieldSpecification
/* -( Implementation )----------------------------------------------------- */
protected function getRequiredStorageKey() {
$key = $this->getStorageKey();
if ($key === null) {
throw new ReleephFieldSpecificationIncompleteException($this);
}
if (strpos($key, '.') !== false) {
/**
* Storage keys are reused for form controls, and periods in form control
* names break HTML forms.
*/
throw new Exception(
"You can't use '.' in storage keys!");
}
return $key;
}
/**
* The "hook" functions ##appendSelectControlsHook()## and
* ##selectReleephRequestsHook()## are used with ##hasSelectablePHIDs()##, to

View file

@ -0,0 +1,55 @@
<?php
final class ReleephRequestReplyHandler extends PhabricatorMailReplyHandler {
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof ReleephRequest)) {
throw new Exception("Mail receiver is not a ReleephRequest!");
}
}
public function getPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle) {
return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'RERQ');
}
public function getPublicReplyHandlerEmailAddress() {
return $this->getDefaultPublicReplyHandlerEmailAddress('RERQ');
}
public function getReplyHandlerInstructions() {
if ($this->supportsReplies()) {
return pht('Reply to comment.');
} else {
return null;
}
}
protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
$rq = $this->getMailReceiver();
$user = $this->getActor();
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_EMAIL,
array(
'id' => $mail->getID(),
));
$editor = id(new ReleephRequestTransactionalEditor())
->setActor($user)
->setContentSource($content_source)
->setParentMessageID($mail->getMessageID());
$body = $mail->getCleanTextBody();
$xactions = array();
$xactions[] = id(new ReleephRequestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment($body);
$editor->applyTransactions($rq, $xactions);
return $rq;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ReleephRequestTransactionQuery
extends PhabricatorApplicationTransactionQuery {
protected function getTemplateApplicationTransaction() {
return new ReleephRequestTransaction();
}
}

View file

@ -312,11 +312,4 @@ final class ReleephRequest extends ReleephDAO {
throw new Exception('`status` is now deprecated!');
}
/* -( Make magic Lisk methods private )------------------------------------ */
private function setUserIntents(array $ar) {
return parent::setUserIntents($ar);
}
}

View file

@ -0,0 +1,283 @@
<?php
final class ReleephRequestTransaction
extends PhabricatorApplicationTransaction {
const TYPE_REQUEST = 'releeph:request';
const TYPE_USER_INTENT = 'releeph:user_intent';
const TYPE_EDIT_FIELD = 'releeph:edit_field';
const TYPE_PICK_STATUS = 'releeph:pick_status';
const TYPE_COMMIT = 'releeph:commit';
const TYPE_DISCOVERY = 'releeph:discovery';
const TYPE_MANUAL_IN_BRANCH = 'releeph:manual';
public function getApplicationName() {
return 'releeph';
}
public function getApplicationTransactionType() {
return ReleephPHIDConstants::PHID_TYPE_RERQ;
}
public function getApplicationTransactionCommentObject() {
return new ReleephRequestTransactionComment();
}
public function getApplicationObjectTypeName() {
return pht('releeph request');
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
default;
break;
}
return parent::hasChangeDetails();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$phids[] = $this->getObjectPHID();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case ReleephRequestTransaction::TYPE_REQUEST:
case ReleephRequestTransaction::TYPE_DISCOVERY:
$phids[] = $new;
break;
case ReleephRequestTransaction::TYPE_EDIT_FIELD:
self::searchForPHIDs($this->getOldValue(), $phids);
self::searchForPHIDs($this->getNewValue(), $phids);
break;
}
return $phids;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case ReleephRequestTransaction::TYPE_REQUEST:
return pht(
'%s requested %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
break;
case ReleephRequestTransaction::TYPE_USER_INTENT:
return $this->getIntentTitle();
break;
case ReleephRequestTransaction::TYPE_EDIT_FIELD:
$field = newv($this->getMetadataValue('fieldClass'), array());
$name = $field->getName();
$markup = $name;
if ($this->getRenderingTarget() ===
PhabricatorApplicationTransaction::TARGET_HTML) {
$markup = hsprintf('<em>%s</em>', $name);
}
return pht(
'%s changed the %s to "%s"',
$this->renderHandleLink($author_phid),
$markup,
$field->normalizeForTransactionView($this, $new));
break;
case ReleephRequestTransaction::TYPE_PICK_STATUS:
switch ($new) {
case ReleephRequest::PICK_OK:
return pht('%s found this request picks without error',
$this->renderHandleLink($author_phid));
case ReleephRequest::REVERT_OK:
return pht('%s found this request reverts without error',
$this->renderHandleLink($author_phid));
case ReleephRequest::PICK_FAILED:
return pht("%s couldn't pick this request",
$this->renderHandleLink($author_phid));
case ReleephRequest::REVERT_FAILED:
return pht("%s couldn't revert this request",
$this->renderHandleLink($author_phid));
}
break;
case ReleephRequestTransaction::TYPE_COMMIT:
$action_type = $this->getMetadataValue('action');
switch ($action_type) {
case 'pick':
return pht(
'%s picked this request and committed the result upstream',
$this->renderHandleLink($author_phid));
break;
case 'revert':
return pht(
'%s reverted this request and committed the result upstream',
$this->renderHandleLink($author_phid));
break;
}
break;
case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
$action = $new ? pht('picked') : pht('reverted');
return pht(
'%s marked this request as manually %s',
$this->renderHandleLink($author_phid),
$action);
break;
case ReleephRequestTransaction::TYPE_DISCOVERY:
return pht('%s discovered this commit as %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
break;
default:
return parent::getTitle();
break;
}
}
public function getActionStrength() {
return parent::getActionStrength();
}
public function getActionName() {
switch ($this->getTransactionType()) {
case self::TYPE_REQUEST:
return pht('Requested');
case self::TYPE_COMMIT:
$action_type = $this->getMetadataValue('action');
switch ($action_type) {
case 'pick':
return pht('Picked');
case 'revert':
return pht('Reverted');
}
}
return parent::getActionName();
}
public function getColor() {
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case ReleephRequestTransaction::TYPE_USER_INTENT:
switch ($new) {
case ReleephRequest::INTENT_WANT:
return PhabricatorTransactions::COLOR_GREEN;
case ReleephRequest::INTENT_PASS:
return PhabricatorTransactions::COLOR_RED;
}
}
return parent::getColor();
}
private static function searchForPHIDs($thing, array &$phids) {
/**
* To implement something like getRequiredHandlePHIDs() in a
* ReleephFieldSpecification, we'd have to provide the field with its
* ReleephRequest (so that it could load the PHIDs from the
* ReleephRequest's storage, and return them.)
*
* We don't have fields initialized with their ReleephRequests, but we can
* make a good guess at what handles will be needed for rendering the field
* in this transaction by inspecting the old and new values.
*/
if (!is_array($thing)) {
$thing = array($thing);
}
foreach ($thing as $value) {
if (phid_get_type($value) !==
PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
$phids[] = $value;
}
}
}
private function getIntentTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$new = $this->getNewValue();
$is_pusher = $this->getMetadataValue('isPusher');
switch ($new) {
case ReleephRequest::INTENT_WANT:
if ($is_pusher) {
return pht(
'%s approved this request',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s wanted this request',
$this->renderHandleLink($author_phid));
}
case ReleephRequest::INTENT_PASS:
if ($is_pusher) {
return pht(
'%s rejected this request',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s passed on this request',
$this->renderHandleLink($author_phid));
}
}
}
public function shouldHide() {
$type = $this->getTransactionType();
if ($type === ReleephRequestTransaction::TYPE_USER_INTENT &&
$this->getMetadataValue('isRQCreate')) {
return true;
}
if ($this->isBoringPickStatus()) {
return true;
}
// ReleephSummaryFieldSpecification is usually blank when an RQ is created,
// creating a transaction change from null to "". Hide these!
if ($type === ReleephRequestTransaction::TYPE_EDIT_FIELD) {
if ($this->getOldValue() === null && $this->getNewValue() === "") {
return true;
}
}
return parent::shouldHide();
}
public function isBoringPickStatus() {
$type = $this->getTransactionType();
if ($type === ReleephRequestTransaction::TYPE_PICK_STATUS) {
$new = $this->getNewValue();
if ($new === ReleephRequest::PICK_OK ||
$new === ReleephRequest::REVERT_OK) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ReleephRequestTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new ReleephRequestTransaction();
}
}

View file

@ -2,6 +2,8 @@
final class ReleephRequestTypeaheadControl extends AphrontFormControl {
const PLACEHOLDER = 'Type a commit id or first line of commit message...';
private $repo;
private $startTime;
@ -42,7 +44,7 @@ final class ReleephRequestTypeaheadControl extends AphrontFormControl {
Javelin::initBehavior('releeph-request-typeahead', array(
'id' => $id,
'src' => '/releeph/request/typeahead/',
'placeholder' => 'Type a commit id or first line of commit message...',
'placeholder' => self::PLACEHOLDER,
'value' => $this->getValue(),
'aux' => array(
'repo' => $this->repo->getID(),

View file

@ -1,266 +0,0 @@
<?php
final class ReleephRequestEventListView extends AphrontView {
private $events;
private $handles;
public function setEvents(array $events) {
assert_instances_of($events, 'ReleephRequestEvent');
$this->events = $events;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function render() {
$views = array();
$discovered_commits = array();
foreach ($this->events as $event) {
$commit_id = $event->getDetail('newCommitIdentifier');
switch ($event->getType()) {
case ReleephRequestEvent::TYPE_DISCOVERY:
$discovered_commits[$commit_id] = true;
break;
}
}
$markup_engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$markup_engine->setConfig('viewer', $this->getUser());
foreach ($this->events as $event) {
$description = $this->describeEvent($event);
if (!$description) {
continue;
}
if ($event->getType() === ReleephRequestEvent::TYPE_COMMIT) {
$commit_id = $event->getDetail('newCommitIdentifier');
if (idx($discovered_commits, $commit_id)) {
continue;
}
}
$actor_handle = $this->handles[$event->getActorPHID()];
$description = $this->describeEvent($event);
$action = phutil_tag(
'div',
array(),
array(
$actor_handle->renderLink(),
' ',
$description));
$view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($actor_handle->getImageURI())
->setEpoch($event->getDateCreated())
->setActions(array($action))
->addClass($this->getTransactionClass($event));
$comment = $this->getEventComment($event);
if ($comment) {
$markup = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
phutil_safe_html(
$markup_engine->markupText($comment)));
$view->appendChild($markup);
}
$views[] = $view;
}
return phutil_tag(
'div',
array(
'class' => 'releeph-request-event-list',
),
$views);
}
public function renderForEmail() {
$items = array();
foreach ($this->events as $event) {
$description = $this->describeEvent($event);
if (!$description) {
continue;
}
$actor = $this->handles[$event->getActorPHID()]->getName();
$items[] = $actor.' '.$description;
$comment = $this->getEventComment($event);
if ($comment) {
$items[] = preg_replace('/^/m', ' ', $comment);
}
}
return implode("\n\n", $items);
}
private function describeEvent(ReleephRequestEvent $event) {
$type = $event->getType();
switch ($type) {
case ReleephRequestEvent::TYPE_CREATE:
return "created this request.";
break;
case ReleephRequestEvent::TYPE_STATUS:
$status = $event->getStatusAfter();
return sprintf(
"updated status to %s.",
ReleephRequest::getStatusDescriptionFor($status));
break;
case ReleephRequestEvent::TYPE_USER_INTENT:
$intent = $event->getDetail('newIntent');
$was_pusher = $event->getDetail('wasPusher');
if ($intent == ReleephRequest::INTENT_WANT) {
if ($was_pusher) {
$verb = "approved";
} else {
$verb = "wanted";
}
} else {
if ($was_pusher) {
$verb = "rejected";
} else {
$verb = "passed on";
}
}
return "{$verb} this request.";
break;
case ReleephRequestEvent::TYPE_PICK_STATUS:
$pick_status = $event->getDetail('newPickStatus');
switch ($pick_status) {
case ReleephRequest::PICK_FAILED:
return "found a conflict when picking.";
break;
case ReleephRequest::REVERT_FAILED:
return "found a conflict when reverting.";
break;
case ReleephRequest::PICK_OK:
case ReleephRequest::REVERT_OK:
// (nothing)
break;
default:
return "changed pick-status to {$pick_status}.";
break;
}
break;
case ReleephRequestEvent::TYPE_MANUAL_ACTION:
$action = $event->getDetail('action');
return "claimed to have manually {$action}ed this request.";
break;
case ReleephRequestEvent::TYPE_COMMIT:
$action = $event->getDetail('action');
if ($action) {
return "{$action}ed this request.";
} else {
return "did something with this request.";
}
break;
case ReleephRequestEvent::TYPE_DISCOVERY:
$action = $event->getDetail('action');
if ($action) {
return "{$action}ed this request.";
} else {
// It's unlikely we'll have action-less TYPE_DISCOVERY events, but I
// used this during testing and I guess it's a useful safety net.
return "discovered this request in the branch.";
}
break;
case ReleephRequestEvent::TYPE_COMMENT:
return "commented on this request.";
break;
default:
return "did event of type {$type}.";
break;
}
}
private function getEventComment(ReleephRequestEvent $event) {
switch ($event->getType()) {
case ReleephRequestEvent::TYPE_CREATE:
$commit_phid = $event->getDetail('commitPHID');
return sprintf(
"Commit %s was requested.",
$this->handles[$commit_phid]->getName());
break;
case ReleephRequestEvent::TYPE_STATUS:
case ReleephRequestEvent::TYPE_USER_INTENT:
case ReleephRequestEvent::TYPE_PICK_STATUS:
case ReleephRequestEvent::TYPE_MANUAL_ACTION:
// no comment!
break;
case ReleephRequestEvent::TYPE_COMMIT:
return sprintf(
"Closed by commit %s.",
$event->getDetail('newCommitIdentifier'));
break;
case ReleephRequestEvent::TYPE_DISCOVERY:
$author_phid = $event->getDetail('authorPHID');
$commit_phid = $event->getDetail('newCommitPHID');
if ($author_phid && $author_phid != $event->getActorPHID()) {
return sprintf(
"Closed by commit %s (with author set to @%s).",
$this->handles[$commit_phid]->getName(),
$this->handles[$author_phid]->getName());
} else {
return sprintf(
'Closed by commit %s.',
$this->handles[$commit_phid]->getName());
}
break;
case ReleephRequestEvent::TYPE_COMMENT:
return $event->getComment();
break;
}
}
private function getTransactionClass($event) {
switch ($event->getType()) {
case ReleephRequestEvent::TYPE_COMMIT:
case ReleephRequestEvent::TYPE_DISCOVERY:
$action = $event->getDetail('action');
if ($action == 'pick') {
return 'releeph-border-color-picked';
} else {
return 'releeph-border-color-abandoned';
}
break;
case ReleephRequestEvent::TYPE_COMMENT:
return 'releeph-border-color-comment';
break;
default:
$status_after = $event->getStatusAfter();
$class_suffix = ReleephRequest::getStatusClassSuffixFor($status_after);
return ' releeph-border-color-'.$class_suffix;
break;
}
}
}

View file

@ -1286,6 +1286,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20130508.search_namedquery.sql'),
),
'20130508.releephtransactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130508.releephtransactions.sql'),
),
'20130508.releephtransactionsmig.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130508.releephtransactionsmig.php'),
),
);
}
}