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

Configure Feed

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

When performing complex edits, pause sub-editors before they publish to propagate "Must Encrypt" and other state

Summary:
See PHI1134. Previously, see T13082 and D19969 for some sort-of-related stuff.

Currently, edits work roughly like this:

- Suppose we're editing object X, and we're also going to edit some other object, Y, because X mentioned Y or the edit is making X a child or parent of Y, or unblocking Y.
- Do the actual edit to X, including inverse edits ("alice mentioned Y on X.", "alice added a child revision: X", etc) which apply to Y.
- Run Herald rules on X.
- Publish the edit to X.

The "inverse edits" currently do this whole process inline, in a sub-editor. So the flow expands like this:

- Begin editing X.
- Update properties on X.
- Begin inverse-edge editing Y.
- Update properties on Y.
- Run (actually, skip) Herald rules on Y.
- Publish edits to Y.
- Run Herald rules on X.
- Publish edits to X.

Notably, the "Y" stuff publishes before the "X" Herald rules run. This creates potential problems:

- Herald rules may change the name or visibility policy of "X", but we'll publish mail about it via the edits to Y before those edits apply. This is a problem only in theory, we don't ship any upstream rules like this today.
- Herald rules may "Require Secure Mail", but we won't know that at the time we're building mail about the indirect change to "Y". This is a problem in practice.

Instead, switch to this new flow, where we stop the sub-editors before they publish, then publish everything at the very end once all the edits are complete:

- Begin editing X.
- Update properties on X.
- Begin inverse-edge editing Y.
- Update properties on Y.
- Skip Herald on Y.
- Run Herald rules on X.
- Publish X.
- Publish all child-editors of X.
- Publish Y.

Test Plan:
- Created "Must Encrypt" Herald rules for Tasks and Revisions.
- Edited object "A", an object which the rules applied to directly, and set object "B" (a different object which the rules did not hit) as its parent/child and/or unblocked it.
- In `bin/mail list-outbound`, saw:
- Mail about object "A" all flagged as "Must Encrypt".
- Normal mail from object B not flagged "Must Encrypt".
- Mail from object B about changing relationships to object A flagged as "Must Encrypt".

Reviewers: amckinley

Reviewed By: amckinley

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

+72 -19
+1 -4
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 134 134 $parent_xaction->setMetadataValue('blocker.new', true); 135 135 } 136 136 137 - id(new ManiphestTransactionEditor()) 138 - ->setActor($this->getActor()) 139 - ->setActingAsPHID($this->getActingAsPHID()) 140 - ->setContentSource($this->getContentSource()) 137 + $this->newSubEditor() 141 138 ->setContinueOnNoEffect(true) 142 139 ->setContinueOnMissingFields(true) 143 140 ->applyTransactions($blocked_task, array($parent_xaction));
+71 -15
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 72 72 private $mailShouldSend = false; 73 73 private $modularTypes; 74 74 private $silent; 75 - private $mustEncrypt; 75 + private $mustEncrypt = array(); 76 76 private $stampTemplates = array(); 77 77 private $mailStamps = array(); 78 78 private $oldTo = array(); ··· 89 89 private $request; 90 90 private $cancelURI; 91 91 private $extensions; 92 + 93 + private $parentEditor; 94 + private $subEditors = array(); 95 + private $publishableObject; 96 + private $publishableTransactions; 92 97 93 98 const STORAGE_ENCODING_BINARY = 'binary'; 94 99 ··· 1272 1277 $herald_source = PhabricatorContentSource::newForSource( 1273 1278 PhabricatorHeraldContentSource::SOURCECONST); 1274 1279 1275 - $herald_editor = newv(get_class($this), array()) 1280 + $herald_editor = $this->newEditorCopy() 1276 1281 ->setContinueOnNoEffect(true) 1277 1282 ->setContinueOnMissingFields(true) 1278 - ->setParentMessageID($this->getParentMessageID()) 1279 1283 ->setIsHeraldEditor(true) 1280 1284 ->setActor($herald_actor) 1281 1285 ->setActingAsPHID($herald_phid) ··· 1330 1334 } 1331 1335 $this->heraldHeader = $herald_header; 1332 1336 1337 + // See PHI1134. If we're a subeditor, we don't publish information about 1338 + // the edit yet. Our parent editor still needs to finish applying 1339 + // transactions and execute Herald, which may change the information we 1340 + // publish. 1341 + 1342 + // For example, Herald actions may change the parent object's title or 1343 + // visibility, or Herald may apply rules like "Must Encrypt" that affect 1344 + // email. 1345 + 1346 + // Once the parent finishes work, it will queue its own publish step and 1347 + // then queue publish steps for its children. 1348 + 1349 + $this->publishableObject = $object; 1350 + $this->publishableTransactions = $xactions; 1351 + if (!$this->parentEditor) { 1352 + $this->queuePublishing(); 1353 + } 1354 + 1355 + return $xactions; 1356 + } 1357 + 1358 + final private function queuePublishing() { 1359 + $object = $this->publishableObject; 1360 + $xactions = $this->publishableTransactions; 1361 + 1362 + if (!$object) { 1363 + throw new Exception( 1364 + pht( 1365 + 'Editor method "queuePublishing()" was called, but no publishable '. 1366 + 'object is present. This Editor is not ready to publish.')); 1367 + } 1368 + 1333 1369 // We're going to compute some of the data we'll use to publish these 1334 1370 // transactions here, before queueing a worker. 1335 1371 // ··· 1392 1428 'priority' => PhabricatorWorker::PRIORITY_ALERTS, 1393 1429 )); 1394 1430 1395 - $this->flushTransactionQueue($object); 1431 + foreach ($this->subEditors as $sub_editor) { 1432 + $sub_editor->queuePublishing(); 1433 + } 1396 1434 1397 - return $xactions; 1435 + $this->flushTransactionQueue($object); 1398 1436 } 1399 1437 1400 1438 protected function didCatchDuplicateKeyException( ··· 3818 3856 3819 3857 $this->mustEncrypt = $adapter->getMustEncryptReasons(); 3820 3858 3859 + // See PHI1134. Propagate "Must Encrypt" state to sub-editors. 3860 + foreach ($this->subEditors as $sub_editor) { 3861 + $sub_editor->mustEncrypt = $this->mustEncrypt; 3862 + } 3863 + 3821 3864 $apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript); 3822 3865 assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction'); 3823 3866 ··· 4034 4077 ->setOldValue($old_phids) 4035 4078 ->setNewValue($new_phids); 4036 4079 4037 - $editor 4080 + $editor = $this->newSubEditor($editor) 4038 4081 ->setContinueOnNoEffect(true) 4039 4082 ->setContinueOnMissingFields(true) 4040 - ->setParentMessageID($this->getParentMessageID()) 4041 - ->setIsInverseEdgeEditor(true) 4042 - ->setIsSilent($this->getIsSilent()) 4043 - ->setActor($this->requireActor()) 4044 - ->setActingAsPHID($this->getActingAsPHID()) 4045 - ->setContentSource($this->getContentSource()); 4083 + ->setIsInverseEdgeEditor(true); 4046 4084 4047 4085 $editor->applyTransactions($node, array($template)); 4048 4086 } ··· 4551 4589 $xactions = $this->transactionQueue; 4552 4590 $this->transactionQueue = array(); 4553 4591 4554 - $editor = $this->newQueueEditor(); 4592 + $editor = $this->newEditorCopy(); 4555 4593 4556 4594 return $editor->applyTransactions($object, $xactions); 4557 4595 } 4558 4596 4559 - private function newQueueEditor() { 4560 - $editor = id(newv(get_class($this), array())) 4597 + final protected function newSubEditor( 4598 + PhabricatorApplicationTransactionEditor $template = null) { 4599 + $editor = $this->newEditorCopy($template); 4600 + 4601 + $editor->parentEditor = $this; 4602 + $this->subEditors[] = $editor; 4603 + 4604 + return $editor; 4605 + } 4606 + 4607 + private function newEditorCopy( 4608 + PhabricatorApplicationTransactionEditor $template = null) { 4609 + if ($template === null) { 4610 + $template = newv(get_class($this), array()); 4611 + } 4612 + 4613 + $editor = id(clone $template) 4561 4614 ->setActor($this->getActor()) 4562 4615 ->setContentSource($this->getContentSource()) 4563 4616 ->setContinueOnNoEffect($this->getContinueOnNoEffect()) 4564 4617 ->setContinueOnMissingFields($this->getContinueOnMissingFields()) 4618 + ->setParentMessageID($this->getParentMessageID()) 4565 4619 ->setIsSilent($this->getIsSilent()); 4566 4620 4567 4621 if ($this->actingAsPHID !== null) { 4568 4622 $editor->setActingAsPHID($this->actingAsPHID); 4569 4623 } 4624 + 4625 + $editor->mustEncrypt = $this->mustEncrypt; 4570 4626 4571 4627 return $editor; 4572 4628 }