@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

Make logic for streaming VCS stuff directly to Files more reusable

Summary:
Ref T11524. Ref T10423. Earlier, I converted `diffusion.filecontentquery` to put the actual file content in Files, then return a PHID for the file, instead of trying to send the content over Conduit.

In T11524, we have a similar set of problems with diffs that contain non-UTF8 data (and, in T10423, diffs that are simply enormous).

I want to provide an API method to do the same sort of thing with diff output (like from `git diff`), so we call the method, it shoves the data in Files, and then we go pull it out of Files.

To support this, take the "shove the output of a Future into Files" logic and put it in a new base `FileFuture` query. This will let me make `RawDiffQuery` share the logic more easily.

Test Plan: Browsed Diffusion, ran `diffusion.filecontentquery` to fetch file content.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10423, T11524

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

+161 -143
+3 -1
src/__phutil_library_map__.php
··· 656 656 'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php', 657 657 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 658 658 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', 659 + 'DiffusionFileFutureQuery' => 'applications/diffusion/query/DiffusionFileFutureQuery.php', 659 660 'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php', 660 661 'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php', 661 662 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php', ··· 5149 5150 'DiffusionExternalController' => 'DiffusionController', 5150 5151 'DiffusionExternalSymbolQuery' => 'Phobject', 5151 5152 'DiffusionExternalSymbolsSource' => 'Phobject', 5152 - 'DiffusionFileContentQuery' => 'DiffusionQuery', 5153 + 'DiffusionFileContentQuery' => 'DiffusionFileFutureQuery', 5153 5154 'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 5155 + 'DiffusionFileFutureQuery' => 'DiffusionQuery', 5154 5156 'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 5155 5157 'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod', 5156 5158 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod',
+3 -37
src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php
··· 19 19 return array( 20 20 'path' => 'required string', 21 21 'commit' => 'required string', 22 - 'timeout' => 'optional int', 23 - 'byteLimit' => 'optional int', 24 - ); 22 + ) + DiffusionFileFutureQuery::getConduitParameters(); 25 23 } 26 24 27 25 protected function getResult(ConduitAPIRequest $request) { 28 26 $drequest = $this->getDiffusionRequest(); 29 27 30 - $file_query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest); 31 - 32 - $timeout = $request->getValue('timeout'); 33 - if ($timeout) { 34 - $file_query->setTimeout($timeout); 35 - } 36 - 37 - $byte_limit = $request->getValue('byteLimit'); 38 - if ($byte_limit) { 39 - $file_query->setByteLimit($byte_limit); 40 - } 41 - 42 - $file = $file_query->execute(); 43 - 44 - $too_slow = (bool)$file_query->getExceededTimeLimit(); 45 - $too_huge = (bool)$file_query->getExceededByteLimit(); 46 - 47 - $file_phid = null; 48 - if (!$too_slow && !$too_huge) { 49 - $repository = $drequest->getRepository(); 50 - $repository_phid = $repository->getPHID(); 51 - 52 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 53 - $file->attachToObject($repository_phid); 54 - unset($unguarded); 55 - 56 - $file_phid = $file->getPHID(); 57 - } 58 - 59 - return array( 60 - 'tooSlow' => $too_slow, 61 - 'tooHuge' => $too_huge, 62 - 'filePHID' => $file_phid, 63 - ); 28 + return DiffusionFileContentQuery::newFromDiffusionRequest($drequest) 29 + ->respondToConduitRequest($request); 64 30 } 65 31 66 32 }
+153
src/applications/diffusion/query/DiffusionFileFutureQuery.php
··· 1 + <?php 2 + 3 + abstract class DiffusionFileFutureQuery 4 + extends DiffusionQuery { 5 + 6 + private $timeout; 7 + private $byteLimit; 8 + 9 + private $didHitByteLimit = false; 10 + private $didHitTimeLimit = false; 11 + 12 + public static function getConduitParameters() { 13 + return array( 14 + 'timeout' => 'optional int', 15 + 'byteLimit' => 'optional int', 16 + ); 17 + } 18 + 19 + public function setTimeout($timeout) { 20 + $this->timeout = $timeout; 21 + return $this; 22 + } 23 + 24 + public function getTimeout() { 25 + return $this->timeout; 26 + } 27 + 28 + public function setByteLimit($byte_limit) { 29 + $this->byteLimit = $byte_limit; 30 + return $this; 31 + } 32 + 33 + public function getByteLimit() { 34 + return $this->byteLimit; 35 + } 36 + 37 + final public function getExceededByteLimit() { 38 + return $this->didHitByteLimit; 39 + } 40 + 41 + final public function getExceededTimeLimit() { 42 + return $this->didHitTimeLimit; 43 + } 44 + 45 + abstract protected function getFileContentFuture(); 46 + 47 + final public function respondToConduitRequest(ConduitAPIRequest $request) { 48 + $drequest = $this->getRequest(); 49 + 50 + $timeout = $request->getValue('timeout'); 51 + if ($timeout) { 52 + $this->setTimeout($timeout); 53 + } 54 + 55 + $byte_limit = $request->getValue('byteLimit'); 56 + if ($byte_limit) { 57 + $this->setByteLimit($byte_limit); 58 + } 59 + 60 + $file = $this->execute(); 61 + 62 + $too_slow = (bool)$this->getExceededTimeLimit(); 63 + $too_huge = (bool)$this->getExceededByteLimit(); 64 + 65 + $file_phid = null; 66 + if (!$too_slow && !$too_huge) { 67 + $repository = $drequest->getRepository(); 68 + $repository_phid = $repository->getPHID(); 69 + 70 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 71 + $file->attachToObject($repository_phid); 72 + unset($unguarded); 73 + 74 + $file_phid = $file->getPHID(); 75 + } 76 + 77 + return array( 78 + 'tooSlow' => $too_slow, 79 + 'tooHuge' => $too_huge, 80 + 'filePHID' => $file_phid, 81 + ); 82 + } 83 + 84 + final public function executeInline() { 85 + $future = $this->getFileContentFuture(); 86 + list($stdout) = $future->resolvex(); 87 + return $future; 88 + } 89 + 90 + final protected function executeQuery() { 91 + $future = $this->newConfiguredFileContentFuture(); 92 + 93 + $drequest = $this->getRequest(); 94 + 95 + $name = basename($drequest->getPath()); 96 + $ttl = PhabricatorTime::getNow() + phutil_units('48 hours in seconds'); 97 + 98 + try { 99 + $threshold = PhabricatorFileStorageEngine::getChunkThreshold(); 100 + $future->setReadBufferSize($threshold); 101 + 102 + $source = id(new PhabricatorExecFutureFileUploadSource()) 103 + ->setName($name) 104 + ->setTTL($ttl) 105 + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) 106 + ->setExecFuture($future); 107 + 108 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 109 + $file = $source->uploadFile(); 110 + unset($unguarded); 111 + 112 + } catch (CommandException $ex) { 113 + if (!$future->getWasKilledByTimeout()) { 114 + throw $ex; 115 + } 116 + 117 + $this->didHitTimeLimit = true; 118 + $file = null; 119 + } 120 + 121 + $byte_limit = $this->getByteLimit(); 122 + 123 + if ($byte_limit && ($file->getByteSize() > $byte_limit)) { 124 + $this->didHitByteLimit = true; 125 + 126 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 127 + id(new PhabricatorDestructionEngine()) 128 + ->destroyObject($file); 129 + unset($unguarded); 130 + 131 + $file = null; 132 + } 133 + 134 + return $file; 135 + } 136 + 137 + private function newConfiguredFileContentFuture() { 138 + $future = $this->getFileContentFuture(); 139 + 140 + if ($this->getTimeout()) { 141 + $future->setTimeout($this->getTimeout()); 142 + } 143 + 144 + $byte_limit = $this->getByteLimit(); 145 + if ($byte_limit) { 146 + $future->setStdoutSizeLimit($byte_limit + 1); 147 + } 148 + 149 + return $future; 150 + } 151 + 152 + 153 + }
+2 -90
src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php
··· 1 1 <?php 2 2 3 - abstract class DiffusionFileContentQuery extends DiffusionQuery { 4 - 5 - private $timeout; 6 - private $byteLimit; 7 - 8 - private $didHitByteLimit = false; 9 - private $didHitTimeLimit = false; 10 - 11 - public function setTimeout($timeout) { 12 - $this->timeout = $timeout; 13 - return $this; 14 - } 15 - 16 - public function getTimeout() { 17 - return $this->timeout; 18 - } 19 - 20 - public function setByteLimit($byte_limit) { 21 - $this->byteLimit = $byte_limit; 22 - return $this; 23 - } 24 - 25 - public function getByteLimit() { 26 - return $this->byteLimit; 27 - } 3 + abstract class DiffusionFileContentQuery 4 + extends DiffusionFileFutureQuery { 28 5 29 6 final public static function newFromDiffusionRequest( 30 7 DiffusionRequest $request) { 31 8 return parent::newQueryObject(__CLASS__, $request); 32 - } 33 - 34 - final public function getExceededByteLimit() { 35 - return $this->didHitByteLimit; 36 - } 37 - 38 - final public function getExceededTimeLimit() { 39 - return $this->didHitTimeLimit; 40 - } 41 - 42 - abstract protected function getFileContentFuture(); 43 - abstract protected function resolveFileContentFuture(Future $future); 44 - 45 - final protected function executeQuery() { 46 - $future = $this->getFileContentFuture(); 47 - 48 - if ($this->getTimeout()) { 49 - $future->setTimeout($this->getTimeout()); 50 - } 51 - 52 - $byte_limit = $this->getByteLimit(); 53 - if ($byte_limit) { 54 - $future->setStdoutSizeLimit($byte_limit + 1); 55 - } 56 - 57 - $drequest = $this->getRequest(); 58 - 59 - $name = basename($drequest->getPath()); 60 - $ttl = PhabricatorTime::getNow() + phutil_units('48 hours in seconds'); 61 - 62 - try { 63 - $threshold = PhabricatorFileStorageEngine::getChunkThreshold(); 64 - $future->setReadBufferSize($threshold); 65 - 66 - $source = id(new PhabricatorExecFutureFileUploadSource()) 67 - ->setName($name) 68 - ->setTTL($ttl) 69 - ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) 70 - ->setExecFuture($future); 71 - 72 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 73 - $file = $source->uploadFile(); 74 - unset($unguarded); 75 - 76 - } catch (CommandException $ex) { 77 - if (!$future->getWasKilledByTimeout()) { 78 - throw $ex; 79 - } 80 - 81 - $this->didHitTimeLimit = true; 82 - $file = null; 83 - } 84 - 85 - if ($byte_limit && ($file->getByteSize() > $byte_limit)) { 86 - $this->didHitByteLimit = true; 87 - 88 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 89 - id(new PhabricatorDestructionEngine()) 90 - ->destroyObject($file); 91 - unset($unguarded); 92 - 93 - $file = null; 94 - } 95 - 96 - return $file; 97 9 } 98 10 99 11 }
-5
src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php
··· 15 15 $path); 16 16 } 17 17 18 - protected function resolveFileContentFuture(Future $future) { 19 - list($corpus) = $future->resolvex(); 20 - return $corpus; 21 - } 22 - 23 18 }
-5
src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php
··· 16 16 $path); 17 17 } 18 18 19 - protected function resolveFileContentFuture(Future $future) { 20 - list($corpus) = $future->resolvex(); 21 - return $corpus; 22 - } 23 - 24 19 }
-5
src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php
··· 14 14 $repository->getSubversionPathURI($path, $commit)); 15 15 } 16 16 17 - protected function resolveFileContentFuture(Future $future) { 18 - list($corpus) = $future->resolvex(); 19 - return $corpus; 20 - } 21 - 22 17 }