@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 "Dominion" rules for Owners Packages

Summary:
Ref T10939. This supports two settings for packages (although they can't be configured yet):

- **Strong Dominion**: If the package owns `a/`, it always owns every subpath, even if another package also owns the subpath. For example, if I own `src/differential/`, I always own it even if someone else claims `src/differential/js/` as part of the "Javascript" package. This is the current behavior, and the default.
- **Weak Dominion**: If the package owns `a/`, but another package owns `a/b/`, the package gives up control of those paths and no longer owns paths in `a/b/`. This is a new behavior which can make defining some types of packages easier.

In the next change, I'll allow users to switch these modes and document what they mean.

Test Plan:
- Ran existing unit tests.
- Added new unit tests.

Reviewers: chad

Reviewed By: chad

Subscribers: joel

Maniphest Tasks: T10939

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

+161 -22
+87 -19
src/applications/owners/storage/PhabricatorOwnersPackage.php
··· 34 34 const AUTOREVIEW_REVIEW = 'review'; 35 35 const AUTOREVIEW_BLOCK = 'block'; 36 36 37 + const DOMINION_STRONG = 'strong'; 38 + const DOMINION_WEAK = 'weak'; 39 + 37 40 public static function initializeNewPackage(PhabricatorUser $actor) { 38 41 $app = id(new PhabricatorApplicationQuery()) 39 42 ->setViewer($actor) ··· 190 193 foreach (array_chunk(array_keys($fragments), 128) as $chunk) { 191 194 $rows[] = queryfx_all( 192 195 $conn, 193 - 'SELECT pkg.id, p.excluded, p.path 196 + 'SELECT pkg.id, "strong" dominion, p.excluded, p.path 194 197 FROM %T pkg JOIN %T p ON p.packageID = pkg.id 195 198 WHERE p.path IN (%Ls) %Q', 196 199 $package->getTableName(), ··· 232 235 } 233 236 234 237 public static function findLongestPathsPerPackage(array $rows, array $paths) { 235 - $ids = array(); 236 238 237 - foreach (igroup($rows, 'id') as $id => $package_paths) { 238 - $relevant_paths = array_select_keys( 239 - $paths, 240 - ipull($package_paths, 'path')); 239 + // Build a map from each path to all the package paths which match it. 240 + $path_hits = array(); 241 + $weak = array(); 242 + foreach ($rows as $row) { 243 + $id = $row['id']; 244 + $path = $row['path']; 245 + $length = strlen($path); 246 + $excluded = $row['excluded']; 241 247 242 - // For every package, remove all excluded paths. 243 - $remove = array(); 244 - foreach ($package_paths as $package_path) { 245 - if ($package_path['excluded']) { 246 - $remove += idx($relevant_paths, $package_path['path'], array()); 247 - unset($relevant_paths[$package_path['path']]); 248 + if ($row['dominion'] === self::DOMINION_WEAK) { 249 + $weak[$id] = true; 250 + } 251 + 252 + $matches = $paths[$path]; 253 + foreach ($matches as $match => $ignored) { 254 + $path_hits[$match][] = array( 255 + 'id' => $id, 256 + 'excluded' => $excluded, 257 + 'length' => $length, 258 + ); 259 + } 260 + } 261 + 262 + // For each path, process the matching package paths to figure out which 263 + // packages actually own it. 264 + $path_packages = array(); 265 + foreach ($path_hits as $match => $hits) { 266 + $hits = isort($hits, 'length'); 267 + 268 + $packages = array(); 269 + foreach ($hits as $hit) { 270 + $package_id = $hit['id']; 271 + if ($hit['excluded']) { 272 + unset($packages[$package_id]); 273 + } else { 274 + $packages[$package_id] = $hit; 248 275 } 249 276 } 250 277 251 - if ($remove) { 252 - foreach ($relevant_paths as $fragment => $fragment_paths) { 253 - $relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove); 278 + $path_packages[$match] = $packages; 279 + } 280 + 281 + // Remove packages with weak dominion rules that should cede control to 282 + // a more specific package. 283 + if ($weak) { 284 + foreach ($path_packages as $match => $packages) { 285 + $packages = isort($packages, 'length'); 286 + $packages = array_reverse($packages, true); 287 + 288 + $first = null; 289 + foreach ($packages as $package_id => $package) { 290 + // If this is the first package we've encountered, note it and 291 + // continue. We're iterating over the packages from longest to 292 + // shortest match, so this package always has the strongest claim 293 + // on the path. 294 + if ($first === null) { 295 + $first = $package_id; 296 + continue; 297 + } 298 + 299 + // If this is the first package we saw, its claim stands even if it 300 + // is a weak package. 301 + if ($first === $package_id) { 302 + continue; 303 + } 304 + 305 + // If this is a weak package and not the first package we saw, 306 + // cede its claim to the stronger package. 307 + if (isset($weak[$package_id])) { 308 + unset($packages[$package_id]); 309 + } 254 310 } 311 + 312 + $path_packages[$match] = $packages; 255 313 } 314 + } 256 315 257 - $relevant_paths = array_filter($relevant_paths); 258 - if ($relevant_paths) { 259 - $ids[$id] = max(array_map('strlen', array_keys($relevant_paths))); 316 + // For each package that owns at least one path, identify the longest 317 + // path it owns. 318 + $package_lengths = array(); 319 + foreach ($path_packages as $match => $hits) { 320 + foreach ($hits as $hit) { 321 + $length = $hit['length']; 322 + $id = $hit['id']; 323 + if (empty($package_lengths[$id])) { 324 + $package_lengths[$id] = $length; 325 + } else { 326 + $package_lengths[$id] = max($package_lengths[$id], $length); 327 + } 260 328 } 261 329 } 262 330 263 - return $ids; 331 + return $package_lengths; 264 332 } 265 333 266 334 public static function splitPath($path) {
+74 -3
src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php
··· 4 4 5 5 public function testFindLongestPathsPerPackage() { 6 6 $rows = array( 7 - array('id' => 1, 'excluded' => 0, 'path' => 'src/'), 8 - array('id' => 1, 'excluded' => 1, 'path' => 'src/releeph/'), 9 - array('id' => 2, 'excluded' => 0, 'path' => 'src/releeph/'), 7 + array( 8 + 'id' => 1, 9 + 'excluded' => 0, 10 + 'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG, 11 + 'path' => 'src/', 12 + ), 13 + array( 14 + 'id' => 1, 15 + 'excluded' => 1, 16 + 'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG, 17 + 'path' => 'src/releeph/', 18 + ), 19 + array( 20 + 'id' => 2, 21 + 'excluded' => 0, 22 + 'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG, 23 + 'path' => 'src/releeph/', 24 + ), 10 25 ); 11 26 12 27 $paths = array( ··· 29 44 2 => strlen('src/releeph/'), 30 45 ), 31 46 PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); 47 + 48 + 49 + // Test packages with weak dominion. Here, only package #2 should own the 50 + // path. Package #1's claim is ceded to Package #2 because it uses weak 51 + // rules. Package #2 gets the claim even though it also has weak rules 52 + // because there is no more-specific package. 53 + 54 + $rows = array( 55 + array( 56 + 'id' => 1, 57 + 'excluded' => 0, 58 + 'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK, 59 + 'path' => 'src/', 60 + ), 61 + array( 62 + 'id' => 2, 63 + 'excluded' => 0, 64 + 'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK, 65 + 'path' => 'src/applications/', 66 + ), 67 + ); 68 + 69 + $pvalue = array('src/applications/main/main.c' => true); 70 + 71 + $paths = array( 72 + 'src/' => $pvalue, 73 + 'src/applications/' => $pvalue, 74 + ); 75 + 76 + $this->assertEqual( 77 + array( 78 + 2 => strlen('src/applications/'), 79 + ), 80 + PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); 81 + 82 + 83 + // Now, add a more specific path to Package #1. This tests nested ownership 84 + // in packages with weak dominion rules. This time, Package #1 should end 85 + // up back on top, with Package #2 cedeing control to its more specific 86 + // path. 87 + $rows[] = array( 88 + 'id' => 1, 89 + 'excluded' => 0, 90 + 'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK, 91 + 'path' => 'src/applications/main/', 92 + ); 93 + 94 + $paths['src/applications/main/'] = $pvalue; 95 + 96 + $this->assertEqual( 97 + array( 98 + 1 => strlen('src/applications/main/'), 99 + ), 100 + PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); 101 + 102 + 32 103 } 33 104 34 105 }