@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.

Add a "touched paths" limit to repositories, limiting the maximum number of paths any commit may touch

Summary:
Depends on D19831. Ref T13216. See PHI908. Allegedly, a user copied a large repository into itself and then pushed it. Great backup strategy, but it can create headaches for administrators.

Allow a "maximum paths you can touch with one commit" limit to be configured, to make it harder for users to make this push this kind of commit by accident.

If you actually intended to do this, you can work around this by breaking your commit into pieces (or temporarily removing the limit). This isn't a security/policy sort of option, it's just a guard against silly mistakes.

Test Plan: Set limit to 2, tried to push 3 files, got rejected. Raised limit, pushed changes successfully.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13216

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

+190 -2
+2
src/__phutil_library_map__.php
··· 4203 4203 'PhabricatorRepositorySyncEventPHIDType' => 'applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php', 4204 4204 'PhabricatorRepositorySyncEventQuery' => 'applications/repository/query/PhabricatorRepositorySyncEventQuery.php', 4205 4205 'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php', 4206 + 'PhabricatorRepositoryTouchLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php', 4206 4207 'PhabricatorRepositoryTrackOnlyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryTrackOnlyTransaction.php', 4207 4208 'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php', 4208 4209 'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php', ··· 10198 10199 'PhabricatorRepositorySyncEventPHIDType' => 'PhabricatorPHIDType', 10199 10200 'PhabricatorRepositorySyncEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10200 10201 'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase', 10202 + 'PhabricatorRepositoryTouchLimitTransaction' => 'PhabricatorRepositoryTransactionType', 10201 10203 'PhabricatorRepositoryTrackOnlyTransaction' => 'PhabricatorRepositoryTransactionType', 10202 10204 'PhabricatorRepositoryTransaction' => 'PhabricatorModularTransaction', 10203 10205 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+9
src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
··· 491 491 ->setConduitDescription(pht('Change the copy time limit.')) 492 492 ->setConduitTypeDescription(pht('New repository copy time limit.')) 493 493 ->setValue($object->getCopyTimeLimit()), 494 + id(new PhabricatorTextEditField()) 495 + ->setKey('touchLimit') 496 + ->setLabel(pht('Touched Paths Limit')) 497 + ->setTransactionType( 498 + PhabricatorRepositoryTouchLimitTransaction::TRANSACTIONTYPE) 499 + ->setDescription(pht('Maximum permitted paths touched per commit.')) 500 + ->setConduitDescription(pht('Change the touch limit.')) 501 + ->setConduitTypeDescription(pht('New repository touch limit.')) 502 + ->setValue($object->getTouchLimit()), 494 503 ); 495 504 } 496 505
+53 -2
src/applications/diffusion/engine/DiffusionCommitHookEngine.php
··· 39 39 private $emailPHIDs = array(); 40 40 private $changesets = array(); 41 41 private $changesetsSize = 0; 42 + private $filesizeCache = array(); 42 43 43 44 44 45 /* -( Config )------------------------------------------------------------- */ ··· 171 172 } catch (DiffusionCommitHookRejectException $ex) { 172 173 // If we're rejecting oversized files, flag everything. 173 174 $this->rejectCode = PhabricatorRepositoryPushLog::REJECT_OVERSIZED; 175 + throw $ex; 176 + } 177 + 178 + try { 179 + if (!$is_initial_import) { 180 + $this->rejectCommitsAffectingTooManyPaths($content_updates); 181 + } 182 + } catch (DiffusionCommitHookRejectException $ex) { 183 + $this->rejectCode = PhabricatorRepositoryPushLog::REJECT_TOUCHES; 174 184 throw $ex; 175 185 } 176 186 ··· 1276 1286 foreach ($content_updates as $update) { 1277 1287 $identifier = $update->getRefNew(); 1278 1288 1279 - $sizes = $this->loadFileSizesForCommit($identifier); 1289 + $sizes = $this->getFileSizesForCommit($identifier); 1290 + 1280 1291 foreach ($sizes as $path => $size) { 1281 1292 if ($size <= $limit) { 1282 1293 continue; ··· 1301 1312 } 1302 1313 } 1303 1314 1304 - public function loadFileSizesForCommit($identifier) { 1315 + private function rejectCommitsAffectingTooManyPaths(array $content_updates) { 1316 + $repository = $this->getRepository(); 1317 + 1318 + $limit = $repository->getTouchLimit(); 1319 + if (!$limit) { 1320 + return; 1321 + } 1322 + 1323 + foreach ($content_updates as $update) { 1324 + $identifier = $update->getRefNew(); 1325 + 1326 + $sizes = $this->getFileSizesForCommit($identifier); 1327 + if (count($sizes) > $limit) { 1328 + $message = pht( 1329 + 'COMMIT AFFECTS TOO MANY PATHS'. 1330 + "\n". 1331 + 'This repository ("%s") is configured with a touched files limit '. 1332 + 'that caps the maximum number of paths any single commit may '. 1333 + 'affect. You are pushing a change ("%s") which exceeds this '. 1334 + 'limit: it affects %s paths, but the largest number of paths any '. 1335 + 'commit may affect is %s paths.', 1336 + $repository->getDisplayName(), 1337 + $identifier, 1338 + phutil_count($sizes), 1339 + new PhutilNumber($limit)); 1340 + 1341 + throw new DiffusionCommitHookRejectException($message); 1342 + } 1343 + } 1344 + } 1345 + 1346 + public function getFileSizesForCommit($identifier) { 1347 + if (!isset($this->filesizeCache[$identifier])) { 1348 + $file_sizes = $this->loadFileSizesForCommit($identifier); 1349 + $this->filesizeCache[$identifier] = $file_sizes; 1350 + } 1351 + 1352 + return $this->filesizeCache[$identifier]; 1353 + } 1354 + 1355 + private function loadFileSizesForCommit($identifier) { 1305 1356 $repository = $this->getRepository(); 1306 1357 1307 1358 return id(new DiffusionLowLevelFilesizeQuery())
+11
src/applications/diffusion/management/DiffusionRepositoryLimitsManagementPanel.php
··· 38 38 return array( 39 39 'filesizeLimit', 40 40 'copyTimeLimit', 41 + 'touchLimit', 41 42 ); 42 43 } 43 44 ··· 94 95 } 95 96 96 97 $view->addProperty(pht('Clone/Fetch Timeout'), $copy_display); 98 + 99 + $touch_limit = $repository->getTouchLimit(); 100 + if ($touch_limit) { 101 + $touch_display = pht('%s Paths', new PhutilNumber($touch_limit)); 102 + } else { 103 + $touch_display = pht('Unlimited'); 104 + $touch_display = phutil_tag('em', array(), $touch_display); 105 + } 106 + 107 + $view->addProperty(pht('Touched Paths Limit'), $touch_display); 97 108 98 109 return $this->newBox(pht('Limits'), $view); 99 110 }
+8
src/applications/repository/storage/PhabricatorRepository.php
··· 1926 1926 return $this->setDetail('limit.filesize', $limit); 1927 1927 } 1928 1928 1929 + public function getTouchLimit() { 1930 + return $this->getDetail('limit.touch'); 1931 + } 1932 + 1933 + public function setTouchLimit($limit) { 1934 + return $this->setDetail('limit.touch', $limit); 1935 + } 1936 + 1929 1937 /** 1930 1938 * Retrieve the service URI for the device hosting this repository. 1931 1939 *
+4
src/applications/repository/storage/PhabricatorRepositoryPushLog.php
··· 25 25 const CHANGEFLAG_DANGEROUS = 16; 26 26 const CHANGEFLAG_ENORMOUS = 32; 27 27 const CHANGEFLAG_OVERSIZED = 64; 28 + const CHANGEFLAG_TOUCHES = 128; 28 29 29 30 const REJECT_ACCEPT = 0; 30 31 const REJECT_DANGEROUS = 1; ··· 33 34 const REJECT_BROKEN = 4; 34 35 const REJECT_ENORMOUS = 5; 35 36 const REJECT_OVERSIZED = 6; 37 + const REJECT_TOUCHES = 7; 36 38 37 39 protected $repositoryPHID; 38 40 protected $epoch; ··· 66 68 self::CHANGEFLAG_DANGEROUS => pht('Dangerous'), 67 69 self::CHANGEFLAG_ENORMOUS => pht('Enormous'), 68 70 self::CHANGEFLAG_OVERSIZED => pht('Oversized'), 71 + self::CHANGEFLAG_TOUCHES => pht('Touches Too Many Paths'), 69 72 ); 70 73 } 71 74 ··· 78 81 self::REJECT_BROKEN => pht('Rejected: Broken'), 79 82 self::REJECT_ENORMOUS => pht('Rejected: Enormous'), 80 83 self::REJECT_OVERSIZED => pht('Rejected: Oversized File'), 84 + self::REJECT_TOUCHES => pht('Rejected: Touches Too Many Paths'), 81 85 ); 82 86 } 83 87
+76
src/applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryTouchLimitTransaction 4 + extends PhabricatorRepositoryTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'limit.touch'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getTouchLimit(); 10 + } 11 + 12 + public function generateNewValue($object, $value) { 13 + if (!strlen($value)) { 14 + return null; 15 + } 16 + 17 + $value = (int)$value; 18 + if (!$value) { 19 + return null; 20 + } 21 + 22 + return $value; 23 + } 24 + 25 + public function applyInternalEffects($object, $value) { 26 + $object->setTouchLimit($value); 27 + } 28 + 29 + public function getTitle() { 30 + $old = $this->getOldValue(); 31 + $new = $this->getNewValue(); 32 + 33 + if ($old && $new) { 34 + return pht( 35 + '%s changed the touch limit for this repository from %s paths to '. 36 + '%s paths.', 37 + $this->renderAuthor(), 38 + $this->renderOldValue(), 39 + $this->renderNewValue()); 40 + } else if ($new) { 41 + return pht( 42 + '%s set the touch limit for this repository to %s paths.', 43 + $this->renderAuthor(), 44 + $this->renderNewValue()); 45 + } else { 46 + return pht( 47 + '%s removed the touch limit (%s paths) for this repository.', 48 + $this->renderAuthor(), 49 + $this->renderOldValue()); 50 + } 51 + } 52 + 53 + public function validateTransactions($object, array $xactions) { 54 + $errors = array(); 55 + 56 + foreach ($xactions as $xaction) { 57 + $new = $xaction->getNewValue(); 58 + 59 + if (!strlen($new)) { 60 + continue; 61 + } 62 + 63 + if (!preg_match('/^\d+\z/', $new)) { 64 + $errors[] = $this->newInvalidError( 65 + pht( 66 + 'Unable to parse touch limit, specify a positive number of '. 67 + 'paths.'), 68 + $xaction); 69 + continue; 70 + } 71 + } 72 + 73 + return $errors; 74 + } 75 + 76 + }
+27
src/docs/user/userguide/diffusion_managing.diviner
··· 246 246 it becomes too large) it will be rejected. This option only applies to hosted 247 247 repositories. 248 248 249 + This limit is primarily intended to make it more difficult to accidentally push 250 + very large files that shouldn't be version controlled (like logs, binaries, 251 + machine learning data, or media assets). Pushing huge datafiles by mistake can 252 + make the repository unwieldy by dramatically increasing how much data must be 253 + transferred over the network to clone it, and simply reverting the changes 254 + doesn't reduce the impact of this kind of mistake. 255 + 249 256 **Clone/Fetch Timeout**: Configure the internal timeout for creating copies 250 257 of this repository during operations like intracluster synchronization and 251 258 Drydock working copy construction. This timeout does not affect external 252 259 users. 260 + 261 + **Touch Limit**: Apply a limit to the maximum number of paths that any commit 262 + may touch. If a commit affects more paths than this limit, it will be rejected. 263 + This option only applies to hosted repositories. Users may work around this 264 + limit by breaking the commit into several smaller commits which each affect 265 + fewer paths. 266 + 267 + This limit is intended to offer a guard rail against users making silly 268 + mistakes that create obviously mistaken changes, like copying an entire 269 + repository into itself and pushing the result. This kind of change can take 270 + some effort to clean up if it becomes part of repository history. 271 + 272 + Note that if you move a file, both the old and new locations count as touched 273 + paths. You should generally configure this limit to be more than twice the 274 + number of files you anticipate any user ever legitimately wanting to move in 275 + a single commit. For example, a limit of `20000` will let users move up to 276 + 10,000 files in a single commit, but will reject users mistakenly trying to 277 + push a copy of another repository or a directory with a million logfiles or 278 + whatever other kind of creative nonsense they manage to dream up. 279 + 253 280 254 281 Branches 255 282 ========