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

Allow excluding paths from package

Summary: Resolves T2149.

Test Plan:
$ bin/storage upgrade

# /owners/ - saw +
# /owners/package/1/ - saw +
# /owners/edit/1/ - added exclude paths, saw correct e-mail
# /rPabc123 - included paths are still highlighted and excluded not
# /owners/view/search/?path=/included/ - found
# /owners/view/search/?path=/excluded/ - not found
# owners.query - path: /included/
# owners.query - path: /excluded/
# new unit test

PhabricatorOwnersPackage::loadAffectedPackages(
$repository,
array('/excluded/b.php'));

PhabricatorOwnersPackage::loadAffectedPackages(
$repository,
array('/included/a.php'));

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T2149

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

vrana 4f615ad2 bf9bc885

+156 -48
+2
resources/sql/patches/owners-exclude.sql
··· 1 + ALTER TABLE {$NAMESPACE}_owners.owners_path 2 + ADD excluded bool NOT NULL DEFAULT '0';
+3 -2
src/__celerity_resource_map__.php
··· 2326 2326 ), 2327 2327 'owners-path-editor' => 2328 2328 array( 2329 - 'uri' => '/res/e6c51eb6/rsrc/js/application/owners/OwnersPathEditor.js', 2329 + 'uri' => '/res/29b68354/rsrc/js/application/owners/OwnersPathEditor.js', 2330 2330 'type' => 'js', 2331 2331 'requires' => 2332 2332 array( ··· 2335 2335 2 => 'path-typeahead', 2336 2336 3 => 'javelin-dom', 2337 2337 4 => 'javelin-util', 2338 + 5 => 'phabricator-prefab', 2338 2339 ), 2339 2340 'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js', 2340 2341 ), 2341 2342 'owners-path-editor-css' => 2342 2343 array( 2343 - 'uri' => '/res/9bc5332c/rsrc/css/application/owners/owners-path-editor.css', 2344 + 'uri' => '/res/4fcaabf6/rsrc/css/application/owners/owners-path-editor.css', 2344 2345 'type' => 'css', 2345 2346 'requires' => 2346 2347 array(
+2
src/__phutil_library_map__.php
··· 928 928 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 929 929 'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php', 930 930 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 931 + 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 931 932 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 932 933 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 933 934 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', ··· 2144 2145 1 => 'PhabricatorPolicyInterface', 2145 2146 ), 2146 2147 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2148 + 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', 2147 2149 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 2148 2150 'PhabricatorPHIDController' => 'PhabricatorController', 2149 2151 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController',
+6 -1
src/applications/diffusion/controller/DiffusionLintController.php
··· 193 193 foreach ($paths as $path) { 194 194 $branch = idx($branches, $repositories[$path->getRepositoryPHID()]); 195 195 if ($branch) { 196 - $or[] = qsprintf( 196 + $condition = qsprintf( 197 197 $conn, 198 198 '(branchID IN (%Ld) AND path LIKE %>)', 199 199 array_keys($branch), 200 200 $path->getPath()); 201 + if ($path->getExcluded()) { 202 + $where[] = 'NOT '.$condition; 203 + } else { 204 + $or[] = $condition; 205 + } 201 206 } 202 207 } 203 208 if (!$or) {
+5 -1
src/applications/diffusion/view/DiffusionCommitChangeTableView.php
··· 66 66 67 67 $row_class = null; 68 68 foreach ($this->ownersPaths as $owners_path) { 69 + $excluded = $owners_path->getExcluded(); 69 70 $owners_path = $owners_path->getPath(); 70 71 if (strncmp('/'.$path, $owners_path, strlen($owners_path)) == 0) { 72 + if ($excluded) { 73 + $row_class = null; 74 + break; 75 + } 71 76 $row_class = 'highlighted'; 72 - break; 73 77 } 74 78 } 75 79 $rowc[] = $row_class;
+3 -1
src/applications/owners/controller/PhabricatorOwnersDetailController.php
··· 100 100 'href' => (string) $href, 101 101 ), 102 102 phutil_escape_html($path->getPath())); 103 - $path_links[] = $repo_name.' '.$path_link; 103 + $path_links[] = 104 + ($path->getExcluded() ? '&ndash;' : '+').' '. 105 + $repo_name.' '.$path_link; 104 106 } 105 107 $path_links = implode('<br />', $path_links); 106 108 $rows[] = array(
+3
src/applications/owners/controller/PhabricatorOwnersEditController.php
··· 47 47 48 48 $paths = $request->getArr('path'); 49 49 $repos = $request->getArr('repo'); 50 + $excludes = $request->getArr('exclude'); 50 51 51 52 $path_refs = array(); 52 53 for ($ii = 0; $ii < count($paths); $ii++) { ··· 56 57 $path_refs[] = array( 57 58 'repositoryPHID' => $repos[$ii], 58 59 'path' => $paths[$ii], 60 + 'excluded' => $excludes[$ii], 59 61 ); 60 62 } 61 63 ··· 102 104 $path_refs[] = array( 103 105 'repositoryPHID' => $path->getRepositoryPHID(), 104 106 'path' => $path->getPath(), 107 + 'excluded' => $path->getExcluded(), 105 108 ); 106 109 } 107 110 }
+10 -3
src/applications/owners/controller/PhabricatorOwnersListController.php
··· 34 34 35 35 $where = array('1 = 1'); 36 36 $join = array(); 37 + $having = ''; 37 38 38 39 if ($request->getStr('name')) { 39 40 $where[] = qsprintf( ··· 59 60 if ($request->getStr('path')) { 60 61 $where[] = qsprintf( 61 62 $conn_r, 62 - 'path.path LIKE %~ OR %s LIKE CONCAT(path.path, %s)', 63 + '(path.path LIKE %~ AND NOT path.excluded) OR 64 + %s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)', 63 65 $request->getStr('path'), 64 66 $request->getStr('path'), 67 + '_', 68 + '\_', 65 69 '%'); 70 + $having = 'HAVING MAX(path.excluded) = 0'; 66 71 } 67 72 68 73 } ··· 80 85 81 86 $data = queryfx_all( 82 87 $conn_r, 83 - 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id', 88 + 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q', 84 89 $package->getTableName(), 85 90 implode(' ', $join), 86 - '('.implode(') AND (', $where).')'); 91 + '('.implode(') AND (', $where).')', 92 + $having); 87 93 $packages = $package->loadAllFromArray($data); 88 94 89 95 $header = 'Search Results'; ··· 254 260 'action' => 'browse', 255 261 )); 256 262 $pkg_paths[$key] = 263 + ($path->getExcluded() ? '&ndash;' : '+').' '. 257 264 '<strong>'.phutil_escape_html($repo->getName()).'</strong> '. 258 265 phutil_render_tag( 259 266 'a',
+7 -4
src/applications/owners/mail/PackageMail.php
··· 46 46 $section[] = ' In repository '.$handles[$repository_phid]->getName(). 47 47 ' - '. PhabricatorEnv::getProductionURI($handles[$repository_phid] 48 48 ->getURI()); 49 - foreach ($paths as $path => $ignored) { 50 - $section[] = ' '.$path; 49 + foreach ($paths as $path => $excluded) { 50 + $section[] = ' '.($excluded ? 'Excluded' : 'Included').' '.$path; 51 51 } 52 52 53 53 return implode("\n", $section); ··· 70 70 } 71 71 $this->mailTo = $mail_to; 72 72 73 - $paths = $package->loadPaths(); 74 - $this->paths = mgroup($paths, 'getRepositoryPHID', 'getPath'); 73 + $this->paths = array(); 74 + $repository_paths = mgroup($package->loadPaths(), 'getRepositoryPHID'); 75 + foreach ($repository_paths as $repository_phid => $paths) { 76 + $this->paths[$repository_phid] = mpull($paths, 'getExcluded', 'getPath'); 77 + } 75 78 76 79 $phids = array_merge( 77 80 $this->mailTo,
+64 -32
src/applications/owners/storage/PhabricatorOwnersPackage.php
··· 113 113 return array(); 114 114 } 115 115 116 - $fragments = array( 117 - '/' => true, 118 - ); 119 - 120 - foreach ($paths as $path) { 121 - $fragments += self::splitPath($path); 122 - } 123 - 124 - return self::loadPackagesForPaths($repository, array_keys($fragments)); 116 + return self::loadPackagesForPaths($repository, $paths); 125 117 } 126 118 127 119 public static function loadOwningPackages($repository, $path) { ··· 129 121 return array(); 130 122 } 131 123 132 - $fragments = self::splitPath($path); 133 - return self::loadPackagesForPaths($repository, array_keys($fragments), 1); 124 + return self::loadPackagesForPaths($repository, array($path), 1); 134 125 } 135 126 136 127 private static function loadPackagesForPaths( 137 128 PhabricatorRepository $repository, 138 129 array $paths, 139 130 $limit = 0) { 131 + 132 + $fragments = array(); 133 + foreach ($paths as $path) { 134 + foreach (self::splitPath($path) as $fragment) { 135 + $fragments[$fragment][$path] = true; 136 + } 137 + } 138 + 140 139 $package = new PhabricatorOwnersPackage(); 141 140 $path = new PhabricatorOwnersPath(); 142 141 $conn = $package->establishConnection('r'); ··· 151 150 // branch. Break it apart so that it will fit within 'max_allowed_packet', 152 151 // and then merge results in PHP. 153 152 154 - $ids = array(); 155 - foreach (array_chunk($paths, 128) as $chunk) { 156 - $rows = queryfx_all( 153 + $rows = array(); 154 + foreach (array_chunk(array_keys($fragments), 128) as $chunk) { 155 + $rows[] = queryfx_all( 157 156 $conn, 158 - 'SELECT pkg.id id, LENGTH(p.path) len 157 + 'SELECT pkg.id, p.excluded, p.path 159 158 FROM %T pkg JOIN %T p ON p.packageID = pkg.id 160 159 WHERE p.path IN (%Ls) %Q', 161 160 $package->getTableName(), 162 161 $path->getTableName(), 163 162 $chunk, 164 163 $repository_clause); 165 - 166 - foreach ($rows as $row) { 167 - $id = (int)$row['id']; 168 - $len = (int)$row['len']; 169 - if (isset($ids[$id])) { 170 - $ids[$id] = max($len, $ids[$id]); 171 - } else { 172 - $ids[$id] = $len; 173 - } 174 - } 175 164 } 165 + $rows = array_mergev($rows); 166 + 167 + $ids = self::findLongestPathsPerPackage($rows, $fragments); 176 168 177 169 if (!$ids) { 178 170 return array(); ··· 190 182 return $packages; 191 183 } 192 184 185 + public static function findLongestPathsPerPackage(array $rows, array $paths) { 186 + $ids = array(); 187 + 188 + foreach (igroup($rows, 'id') as $id => $package_paths) { 189 + $relevant_paths = array_select_keys( 190 + $paths, 191 + ipull($package_paths, 'path')); 192 + 193 + // For every package, remove all excluded paths. 194 + $remove = array(); 195 + foreach ($package_paths as $package_path) { 196 + if ($package_path['excluded']) { 197 + $remove += $relevant_paths[$package_path['path']]; 198 + unset($relevant_paths[$package_path['path']]); 199 + } 200 + } 201 + 202 + if ($remove) { 203 + foreach ($relevant_paths as $fragment => $fragment_paths) { 204 + $relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove); 205 + } 206 + } 207 + 208 + $relevant_paths = array_filter($relevant_paths); 209 + if ($relevant_paths) { 210 + $ids[$id] = max(array_map('strlen', array_keys($relevant_paths))); 211 + } 212 + } 213 + 214 + return $ids; 215 + } 216 + 193 217 public function save() { 194 218 195 219 if ($this->getID()) { ··· 238 262 $new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path'); 239 263 $cur_paths = $this->loadPaths(); 240 264 foreach ($cur_paths as $key => $path) { 241 - if (empty($new_paths[$path->getRepositoryPHID()][$path->getPath()])) { 242 - $touched_repos[$path->getRepositoryPHID()] = true; 243 - $remove_paths[$path->getRepositoryPHID()][$path->getPath()] = true; 265 + $repository_phid = $path->getRepositoryPHID(); 266 + $new_path = head(idx( 267 + idx($new_paths, $repository_phid, array()), 268 + $path->getPath(), 269 + array())); 270 + $excluded = $path->getExcluded(); 271 + if (!$new_path || $new_path['excluded'] != $excluded) { 272 + $touched_repos[$repository_phid] = true; 273 + $remove_paths[$repository_phid][$path->getPath()] = $excluded; 244 274 $path->delete(); 245 275 unset($cur_paths[$key]); 246 276 } ··· 255 285 if (!$repository) { 256 286 continue; 257 287 } 258 - foreach ($paths as $path => $ignored) { 288 + foreach ($paths as $path => $dicts) { 259 289 $path = ltrim($path, '/'); 260 290 // build query to validate path 261 291 $drequest = DiffusionRequest::newFromDictionary( ··· 286 316 } 287 317 if (empty($cur_paths[$repository_phid][$path]) && $valid) { 288 318 $touched_repos[$repository_phid] = true; 289 - $add_paths[$repository_phid][$path] = true; 319 + $excluded = idx(reset($dicts), 'excluded', 0); 320 + $add_paths[$repository_phid][$path] = $excluded; 290 321 $obj = new PhabricatorOwnersPath(); 291 322 $obj->setPackageID($this->getID()); 292 323 $obj->setRepositoryPHID($repository_phid); 293 324 $obj->setPath($path); 325 + $obj->setExcluded($excluded); 294 326 $obj->save(); 295 327 } 296 328 } ··· 340 372 } 341 373 342 374 private static function splitPath($path) { 343 - $result = array(); 375 + $result = array('/'); 344 376 $trailing_slash = preg_match('@/$@', $path) ? '/' : ''; 345 377 $path = trim($path, '/'); 346 378 $parts = explode('/', $path); 347 379 while (count($parts)) { 348 - $result['/'.implode('/', $parts).$trailing_slash] = true; 380 + $result[] = '/'.implode('/', $parts).$trailing_slash; 349 381 $trailing_slash = '/'; 350 382 array_pop($parts); 351 383 }
+1
src/applications/owners/storage/PhabricatorOwnersPath.php
··· 5 5 protected $packageID; 6 6 protected $repositoryPHID; 7 7 protected $path; 8 + protected $excluded; 8 9 9 10 public function getConfiguration() { 10 11 return array(
+34
src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase { 4 + 5 + function testFindLongestPathsPerPackage() { 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/'), 10 + ); 11 + 12 + $paths = array( 13 + 'src/' => array('src/a.php' => true, 'src/releeph/b.php' => true), 14 + 'src/releeph/' => array('src/releeph/b.php' => true), 15 + ); 16 + $this->assertEqual( 17 + array( 18 + 1 => strlen('src/'), 19 + 2 => strlen('src/releeph/'), 20 + ), 21 + PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); 22 + 23 + $paths = array( 24 + 'src/' => array('src/releeph/b.php' => true), 25 + 'src/releeph/' => array('src/releeph/b.php' => true), 26 + ); 27 + $this->assertEqual( 28 + array( 29 + 2 => strlen('src/releeph/'), 30 + ), 31 + PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); 32 + } 33 + 34 + }
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1040 1040 'type' => 'sql', 1041 1041 'name' => $this->getPatchPath('pholio.sql'), 1042 1042 ), 1043 + 'owners-exclude.sql' => array( 1044 + 'type' => 'sql', 1045 + 'name' => $this->getPatchPath('owners-exclude.sql'), 1046 + ), 1043 1047 ); 1044 1048 } 1045 1049
+2 -2
webroot/rsrc/css/application/owners/owners-path-editor.css
··· 10 10 padding: 2px 4px; 11 11 } 12 12 13 - .owners-path-editor-table select { 13 + .owners-path-editor-table select.owners-repo { 14 14 width: 150px; 15 15 } 16 16 17 17 .owners-path-editor-table input { 18 - width: 550px; 18 + width: 450px; 19 19 } 20 20 21 21 .owners-path-editor-table div.error-display {
+10 -2
webroot/rsrc/js/application/owners/OwnersPathEditor.js
··· 4 4 * path-typeahead 5 5 * javelin-dom 6 6 * javelin-util 7 + * phabricator-prefab 7 8 * @provides owners-path-editor 8 9 * @javelin 9 10 */ ··· 95 96 this._lastRepositoryChoice; 96 97 var options = this._buildRepositoryOptions(selected_repository); 97 98 var attrs = { 98 - name : "repo[" + this._count + "]" 99 + name : "repo[" + this._count + "]", 100 + className : 'owners-repo' 99 101 }; 100 102 var repo_select = JX.$N('select', attrs, options); 101 103 ··· 132 134 133 135 var error_display_cell = JX.$N('td', {}, error_display); 134 136 137 + var exclude = JX.Prefab.renderSelect( 138 + {'0' : 'Include', '1' : 'Exclude'}, 139 + path_ref.excluded, 140 + {name : 'exclude[' + this._count + ']'}); 141 + var exclude_cell = JX.$N('td', {}, exclude); 142 + 135 143 var row = this._rowManager.addRow( 136 - [repo_cell, typeahead_cell, error_display_cell]); 144 + [exclude_cell, repo_cell, typeahead_cell, error_display_cell]); 137 145 138 146 new JX.PathTypeahead({ 139 147 repositoryDefaultPaths : this._repositoryDefaultPaths,