@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

Modularize Differential Diff transactions and attach binaries

Summary:
Fully modularizing DifferentialDiffTransaction, and then fix T16472 by attaching any binary files uploaded to the diff object.

Refs T16485 T16070.

Test Plan:
Set the default view-policy of new files to something that isn't very public.
Create new diffs using `arc diff` that includes a picture of a small cat. Other kinds of images are out of scope.

- Users that don't have permission to see the revision should not be able to see the file
- Users that have permission to see the revision, should be able to see the file.
- Changing the policies on the created file to "no one" will not be make a difference.
- The file's page will show the diff in the Attachment list.

Also:
- Herald can still block the creation of the diffs.

Reviewers: O1 Blessed Committers, valerio.bozzolan

Reviewed By: O1 Blessed Committers, valerio.bozzolan

Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno, jdelker

Maniphest Tasks: T16485, T16472, T16070

Differential Revision: https://we.phorge.it/D26724

+170 -227
+5 -1
src/__phutil_library_map__.php
··· 529 529 'DifferentialDiffContentHeraldField' => 'applications/differential/herald/DifferentialDiffContentHeraldField.php', 530 530 'DifferentialDiffContentRemovedHeraldField' => 'applications/differential/herald/DifferentialDiffContentRemovedHeraldField.php', 531 531 'DifferentialDiffCreateController' => 'applications/differential/controller/DifferentialDiffCreateController.php', 532 + 'DifferentialDiffCreateTransaction' => 'applications/differential/xaction/DifferentialDiffCreateTransaction.php', 532 533 'DifferentialDiffEditor' => 'applications/differential/editor/DifferentialDiffEditor.php', 533 534 'DifferentialDiffExtractionEngine' => 'applications/differential/engine/DifferentialDiffExtractionEngine.php', 534 535 'DifferentialDiffHeraldField' => 'applications/differential/herald/DifferentialDiffHeraldField.php', ··· 544 545 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 545 546 'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php', 546 547 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', 548 + 'DifferentialDiffTransactionType' => 'applications/differential/xaction/DifferentialDiffTransactionType.php', 547 549 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 548 550 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', 549 551 'DifferentialDraftField' => 'applications/differential/customfield/DifferentialDraftField.php', ··· 6590 6592 'DifferentialDiffContentHeraldField' => 'DifferentialDiffHeraldField', 6591 6593 'DifferentialDiffContentRemovedHeraldField' => 'DifferentialDiffHeraldField', 6592 6594 'DifferentialDiffCreateController' => 'DifferentialController', 6595 + 'DifferentialDiffCreateTransaction' => 'DifferentialDiffTransactionType', 6593 6596 'DifferentialDiffEditor' => 'PhabricatorApplicationTransactionEditor', 6594 6597 'DifferentialDiffExtractionEngine' => 'Phobject', 6595 6598 'DifferentialDiffHeraldField' => 'HeraldField', ··· 6603 6606 'DifferentialDiffSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 6604 6607 'DifferentialDiffSearchEngine' => 'PhabricatorApplicationSearchEngine', 6605 6608 'DifferentialDiffTestCase' => 'PhabricatorTestCase', 6606 - 'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction', 6609 + 'DifferentialDiffTransaction' => 'PhabricatorModularTransaction', 6607 6610 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6611 + 'DifferentialDiffTransactionType' => 'PhabricatorModularTransactionType', 6608 6612 'DifferentialDiffViewController' => 'DifferentialController', 6609 6613 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 6610 6614 'DifferentialDraftField' => 'DifferentialCoreCustomField',
+10 -154
src/applications/differential/editor/DifferentialDiffEditor.php
··· 3 3 final class DifferentialDiffEditor 4 4 extends PhabricatorApplicationTransactionEditor { 5 5 6 - private $diffDataDict; 7 6 private $lookupRepository = true; 8 7 9 8 public function setLookupRepository($bool) { ··· 28 27 return $types; 29 28 } 30 29 31 - protected function getCustomTransactionOldValue( 30 + protected function didApplyInternalEffects( 32 31 PhabricatorLiskDAO $object, 33 - PhabricatorApplicationTransaction $xaction) { 34 - 35 - switch ($xaction->getTransactionType()) { 36 - case DifferentialDiffTransaction::TYPE_DIFF_CREATE: 37 - return null; 38 - } 32 + array $xactions) { 39 33 40 - return parent::getCustomTransactionOldValue($object, $xaction); 41 - } 34 + // This method wasn't modularized. 42 35 43 - protected function getCustomTransactionNewValue( 44 - PhabricatorLiskDAO $object, 45 - PhabricatorApplicationTransaction $xaction) { 46 - 47 - switch ($xaction->getTransactionType()) { 48 - case DifferentialDiffTransaction::TYPE_DIFF_CREATE: 49 - $this->diffDataDict = $xaction->getNewValue(); 50 - return true; 51 - } 52 - 53 - return parent::getCustomTransactionNewValue($object, $xaction); 54 - } 55 - 56 - protected function applyCustomInternalTransaction( 57 - PhabricatorLiskDAO $object, 58 - PhabricatorApplicationTransaction $xaction) { 59 - 60 - switch ($xaction->getTransactionType()) { 61 - case DifferentialDiffTransaction::TYPE_DIFF_CREATE: 62 - $dict = $this->diffDataDict; 63 - $this->updateDiffFromDict($object, $dict); 64 - return; 36 + foreach ($xactions as $xaction) { 37 + switch ($xaction->getTransactionType()) { 38 + case DifferentialDiffTransaction::TYPE_DIFF_CREATE: 39 + $xaction->setNewValue(true); 40 + break; 41 + } 65 42 } 66 43 67 - return parent::applyCustomInternalTransaction($object, $xaction); 44 + return $xactions; 68 45 } 69 46 70 - protected function applyCustomExternalTransaction( 71 - PhabricatorLiskDAO $object, 72 - PhabricatorApplicationTransaction $xaction) { 73 - 74 - switch ($xaction->getTransactionType()) { 75 - case DifferentialDiffTransaction::TYPE_DIFF_CREATE: 76 - return; 77 - } 78 - 79 - return parent::applyCustomExternalTransaction($object, $xaction); 80 - } 81 47 82 48 protected function applyFinalEffects( 83 49 PhabricatorLiskDAO $object, ··· 102 68 return $xactions; 103 69 } 104 70 105 - /** 106 - * We run Herald as part of transaction validation because Herald can 107 - * block diff creation for Differential diffs. Its important to do this 108 - * separately so no Herald logs are saved; these logs could expose 109 - * information the Herald rules are intended to block. 110 - */ 111 - protected function validateTransaction( 112 - PhabricatorLiskDAO $object, 113 - $type, 114 - array $xactions) { 115 - 116 - $errors = parent::validateTransaction($object, $type, $xactions); 117 - 118 - foreach ($xactions as $xaction) { 119 - switch ($type) { 120 - case DifferentialDiffTransaction::TYPE_DIFF_CREATE: 121 - $diff = clone $object; 122 - $diff = $this->updateDiffFromDict($diff, $xaction->getNewValue()); 123 - 124 - $adapter = $this->buildHeraldAdapter($diff, $xactions); 125 - $adapter->setContentSource($this->getContentSource()); 126 - $adapter->setIsNewObject($this->getIsNewObject()); 127 - 128 - $engine = new HeraldEngine(); 129 - 130 - $rules = $engine->loadRulesForAdapter($adapter); 131 - $rules = mpull($rules, null, 'getID'); 132 - 133 - $effects = $engine->applyRules($rules, $adapter); 134 - $action_block = DifferentialBlockHeraldAction::ACTIONCONST; 135 - 136 - $blocking_effect = null; 137 - foreach ($effects as $effect) { 138 - if ($effect->getAction() == $action_block) { 139 - $blocking_effect = $effect; 140 - break; 141 - } 142 - } 143 - 144 - if ($blocking_effect) { 145 - $rule = $blocking_effect->getRule(); 146 - 147 - $message = $effect->getTarget(); 148 - if (!strlen($message)) { 149 - $message = pht('(None.)'); 150 - } 151 - 152 - $errors[] = new PhabricatorApplicationTransactionValidationError( 153 - $type, 154 - pht('Rejected by Herald'), 155 - pht( 156 - "Creation of this diff was rejected by Herald rule %s.\n". 157 - " Rule: %s\n". 158 - "Reason: %s", 159 - $rule->getMonogram(), 160 - $rule->getName(), 161 - $message)); 162 - } 163 - break; 164 - } 165 - } 166 - 167 - return $errors; 168 - } 169 - 170 - 171 - protected function shouldPublishFeedStory( 172 - PhabricatorLiskDAO $object, 173 - array $xactions) { 174 - return false; 175 - } 176 - 177 - protected function shouldSendMail( 178 - PhabricatorLiskDAO $object, 179 - array $xactions) { 180 - return false; 181 - } 182 - 183 - protected function supportsSearch() { 184 - return false; 185 - } 186 - 187 71 /* -( Herald Integration )------------------------------------------------- */ 188 72 189 73 /** ··· 198 82 return false; 199 83 } 200 84 201 - protected function buildHeraldAdapter( 202 - PhabricatorLiskDAO $object, 203 - array $xactions) { 204 - 205 - $adapter = id(new HeraldDifferentialDiffAdapter()) 206 - ->setDiff($object); 207 - 208 - return $adapter; 209 - } 210 - 211 - private function updateDiffFromDict(DifferentialDiff $diff, $dict) { 212 - $diff 213 - ->setSourcePath(idx($dict, 'sourcePath')) 214 - ->setSourceMachine(idx($dict, 'sourceMachine')) 215 - ->setBranch(idx($dict, 'branch')) 216 - ->setCreationMethod(idx($dict, 'creationMethod')) 217 - ->setAuthorPHID(idx($dict, 'authorPHID', $this->getActor())) 218 - ->setBookmark(idx($dict, 'bookmark')) 219 - ->setRepositoryPHID(idx($dict, 'repositoryPHID')) 220 - ->setRepositoryUUID(idx($dict, 'repositoryUUID')) 221 - ->setSourceControlSystem(idx($dict, 'sourceControlSystem')) 222 - ->setSourceControlPath(idx($dict, 'sourceControlPath')) 223 - ->setSourceControlBaseRevision(idx($dict, 'sourceControlBaseRevision')) 224 - ->setLintStatus(idx($dict, 'lintStatus')) 225 - ->setUnitStatus(idx($dict, 'unitStatus')); 226 - 227 - return $diff; 228 - } 229 85 }
+3
src/applications/differential/storage/DifferentialDiff.php
··· 175 175 return self::buildChangesetsFromRawChanges($diff, $changes); 176 176 } 177 177 178 + /** 179 + * @param array<ArcanistDiffChange> $changes 180 + */ 178 181 private static function buildChangesetsFromRawChanges( 179 182 DifferentialDiff $diff, 180 183 array $changes) {
+5 -49
src/applications/differential/storage/DifferentialDiffTransaction.php
··· 1 1 <?php 2 2 3 3 final class DifferentialDiffTransaction 4 - extends PhabricatorApplicationTransaction { 4 + extends PhabricatorModularTransaction { 5 5 6 6 const TYPE_DIFF_CREATE = 'differential:diff:create'; 7 + 8 + public function getBaseTransactionClass() { 9 + return DifferentialDiffTransactionType::class; 10 + } 7 11 8 12 public function getApplicationName() { 9 13 return 'differential'; ··· 11 15 12 16 public function getApplicationTransactionType() { 13 17 return DifferentialDiffPHIDType::TYPECONST; 14 - } 15 - 16 - public function shouldHideForMail(array $xactions) { 17 - return true; 18 - } 19 - 20 - public function getActionName() { 21 - switch ($this->getTransactionType()) { 22 - case self::TYPE_DIFF_CREATE: 23 - return pht('Created'); 24 - } 25 - 26 - return parent::getActionName(); 27 - } 28 - 29 - public function getTitle() { 30 - $author_phid = $this->getAuthorPHID(); 31 - $author_handle = $this->renderHandleLink($author_phid); 32 - 33 - $old = $this->getOldValue(); 34 - $new = $this->getNewValue(); 35 - 36 - switch ($this->getTransactionType()) { 37 - case self::TYPE_DIFF_CREATE: 38 - return pht( 39 - '%s created this diff.', 40 - $author_handle); 41 - } 42 - 43 - return parent::getTitle(); 44 - } 45 - 46 - public function getIcon() { 47 - switch ($this->getTransactionType()) { 48 - case self::TYPE_DIFF_CREATE: 49 - return 'fa-refresh'; 50 - } 51 - 52 - return parent::getIcon(); 53 - } 54 - 55 - public function getColor() { 56 - switch ($this->getTransactionType()) { 57 - case self::TYPE_DIFF_CREATE: 58 - return PhabricatorTransactions::COLOR_SKY; 59 - } 60 - 61 - return parent::getColor(); 62 18 } 63 19 64 20 }
+132
src/applications/differential/xaction/DifferentialDiffCreateTransaction.php
··· 1 + <?php 2 + 3 + final class DifferentialDiffCreateTransaction 4 + extends DifferentialDiffTransactionType { 5 + 6 + const TRANSACTIONTYPE = DifferentialDiffTransaction::TYPE_DIFF_CREATE; 7 + 8 + public function shouldHide() { 9 + return true; 10 + } 11 + 12 + public function generateOldValue($object) { 13 + return null; 14 + } 15 + 16 + public function getActionName() { 17 + return pht('Created'); 18 + } 19 + 20 + public function getTitle() { 21 + return pht('%s created this diff.', $this->renderAuthor()); 22 + } 23 + 24 + public function getIcon() { 25 + return 'fa-refresh'; 26 + } 27 + 28 + public function getColor() { 29 + return PhabricatorTransactions::COLOR_SKY; 30 + } 31 + 32 + public function extractFilePHIDs($object, $value) { 33 + 34 + /** @var array<DifferentialChangeset> $changesets */ 35 + $changesets = $object->getChangesets(); 36 + 37 + $file_phids = array(); 38 + foreach ($changesets as $change) { 39 + $file_phids[] = $change->getNewFileObjectPHID(); 40 + $file_phids[] = $change->getOldFileObjectPHID(); 41 + } 42 + 43 + return array_filter($file_phids); 44 + } 45 + 46 + public function applyInternalEffects($object, $value) { 47 + $this->updateDiffFromDict($object, $value); 48 + } 49 + 50 + 51 + /** 52 + * We run Herald as part of transaction validation because Herald can 53 + * block diff creation for Differential diffs. Its important to do this 54 + * separately so no Herald logs are saved; these logs could expose 55 + * information the Herald rules are intended to block. 56 + */ 57 + public function validateTransactions($object, array $xactions) { 58 + 59 + $errors = array(); 60 + 61 + foreach ($xactions as $xaction) { 62 + $diff = clone $object; 63 + $diff = $this->updateDiffFromDict($diff, $xaction->getNewValue()); 64 + 65 + $adapter = $this->buildHeraldAdapter($diff, $xactions); 66 + $adapter->setContentSource($xaction->getContentSource()); 67 + $adapter->setIsNewObject(true); 68 + 69 + $engine = new HeraldEngine(); 70 + 71 + $rules = $engine->loadRulesForAdapter($adapter); 72 + $rules = mpull($rules, null, 'getID'); 73 + 74 + $effects = $engine->applyRules($rules, $adapter); 75 + $action_block = DifferentialBlockHeraldAction::ACTIONCONST; 76 + 77 + $blocking_effect = null; 78 + foreach ($effects as $effect) { 79 + if ($effect->getAction() == $action_block) { 80 + $blocking_effect = $effect; 81 + break; 82 + } 83 + } 84 + 85 + if ($blocking_effect) { 86 + $rule = $blocking_effect->getRule(); 87 + 88 + $message = $blocking_effect->getTarget(); 89 + if (!is_string($message) || !strlen($message)) { 90 + $message = pht('(None.)'); 91 + } 92 + 93 + $errors[] = $this->newError( 94 + pht('Rejected by Herald'), 95 + pht( 96 + "Creation of this diff was rejected by Herald rule %s.\n". 97 + " Rule: %s\n". 98 + "Reason: %s", 99 + $rule->getMonogram(), 100 + $rule->getName(), 101 + $message)); 102 + } 103 + } 104 + 105 + return $errors; 106 + } 107 + 108 + private function buildHeraldAdapter($object) { 109 + 110 + return id(new HeraldDifferentialDiffAdapter()) 111 + ->setDiff($object); 112 + } 113 + 114 + private function updateDiffFromDict(DifferentialDiff $diff, $dict) { 115 + $diff 116 + ->setSourcePath(idx($dict, 'sourcePath')) 117 + ->setSourceMachine(idx($dict, 'sourceMachine')) 118 + ->setBranch(idx($dict, 'branch')) 119 + ->setCreationMethod(idx($dict, 'creationMethod')) 120 + ->setAuthorPHID(idx($dict, 'authorPHID', $this->getActor())) 121 + ->setBookmark(idx($dict, 'bookmark')) 122 + ->setRepositoryPHID(idx($dict, 'repositoryPHID')) 123 + ->setRepositoryUUID(idx($dict, 'repositoryUUID')) 124 + ->setSourceControlSystem(idx($dict, 'sourceControlSystem')) 125 + ->setSourceControlPath(idx($dict, 'sourceControlPath')) 126 + ->setSourceControlBaseRevision(idx($dict, 'sourceControlBaseRevision')) 127 + ->setLintStatus(idx($dict, 'lintStatus')) 128 + ->setUnitStatus(idx($dict, 'unitStatus')); 129 + 130 + return $diff; 131 + } 132 + }
+4
src/applications/differential/xaction/DifferentialDiffTransactionType.php
··· 1 + <?php 2 + 3 + abstract class DifferentialDiffTransactionType 4 + extends PhabricatorModularTransactionType {}
+4 -16
src/applications/files/controller/PhabricatorFileViewController.php
··· 140 140 } else { 141 141 $curtain->addAction( 142 142 id(new PhabricatorActionView()) 143 - ->setUser($viewer) 143 + ->setViewer($viewer) 144 144 ->setDownload($can_download) 145 145 ->setName(pht('Download File')) 146 146 ->setIcon('fa-download') ··· 390 390 } 391 391 392 392 private function loadStorageEngine(PhabricatorFile $file) { 393 - $engine = null; 394 393 395 394 try { 396 - $engine = $file->instantiateStorageEngine(); 397 - } catch (Exception $ex) { 395 + return $file->instantiateStorageEngine(); 396 + } catch (Throwable $ex) { 398 397 // Don't bother raising this anywhere for now. 399 398 } 400 399 401 - return $engine; 400 + return null; 402 401 } 403 402 404 403 private function newFileContent(PhabricatorFile $file) { ··· 426 425 $rows = array(); 427 426 428 427 $mode_map = PhabricatorFileAttachment::getModeNameMap(); 429 - $mode_attach = PhabricatorFileAttachment::MODE_ATTACH; 430 428 431 429 foreach ($attachments as $attachment) { 432 430 $object_phid = $attachment->getObjectPHID(); ··· 454 452 ->setColor(PHUIButtonView::GREY) 455 453 ->setSize(PHUIButtonView::SMALL) 456 454 ->setText(pht('Detach File')); 457 - 458 - javelin_tag( 459 - 'a', 460 - array( 461 - 'href' => $detach_uri, 462 - 'sigil' => 'workflow', 463 - 'disabled' => true, 464 - 'class' => 'small button button-grey disabled', 465 - ), 466 - pht('Detach File')); 467 455 468 456 $rows[] = array( 469 457 $handle->renderLink(),
+6 -6
src/applications/files/storage/PhabricatorFile.php
··· 80 80 $view_policy = $app->getPolicy( 81 81 FilesDefaultViewCapability::CAPABILITY); 82 82 83 - return id(new PhabricatorFile()) 83 + return id(new self()) 84 84 ->setViewPolicy($view_policy) 85 85 ->setIsPartial(0) 86 86 ->attachOriginalFile(null) ··· 396 396 // If an engine is outright misconfigured (or misimplemented), raise 397 397 // that immediately since it probably needs attention. 398 398 throw $ex; 399 - } catch (Exception $ex) { 399 + } catch (Throwable $ex) { 400 400 phlog($ex); 401 401 402 402 // If an engine doesn't work, keep trying all the other valid engines ··· 432 432 433 433 try { 434 434 $file->updateDimensions(false); 435 - } catch (Exception $ex) { 435 + } catch (Throwable $ex) { 436 436 // Do nothing. 437 437 } 438 438 ··· 682 682 683 683 return self::newFromFileData($body, $params); 684 684 } 685 - } catch (Exception $ex) { 685 + } catch (Throwable $ex) { 686 686 if ($redirects) { 687 687 throw new Exception( 688 688 pht( ··· 769 769 $handle) { 770 770 771 771 // Check to see if any files are using storage. 772 - $usage = id(new PhabricatorFile())->loadAllWhere( 772 + $usage = id(new self())->loadAllWhere( 773 773 'storageEngine = %s AND storageHandle = %s LIMIT 1', 774 774 $engine_identifier, 775 775 $handle); ··· 778 778 if (!$usage) { 779 779 try { 780 780 $engine->deleteFile($handle); 781 - } catch (Exception $ex) { 781 + } catch (Throwable $ex) { 782 782 // In the worst case, we're leaving some data stranded in a storage 783 783 // engine, which is not a big deal. 784 784 phlog($ex);
+1 -1
src/applications/herald/engine/HeraldEffect.php
··· 38 38 } 39 39 40 40 /** 41 - * @return array|null 41 + * @return mixed 42 42 */ 43 43 public function getTarget() { 44 44 return $this->target;