1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

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
This commit is contained in:
vrana 2012-12-06 17:23:56 -08:00
parent bf9bc885b7
commit 4f615ad2a9
15 changed files with 156 additions and 48 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_owners.owners_path
ADD excluded bool NOT NULL DEFAULT '0';

View file

@ -2326,7 +2326,7 @@ celerity_register_resource_map(array(
), ),
'owners-path-editor' => 'owners-path-editor' =>
array( array(
'uri' => '/res/e6c51eb6/rsrc/js/application/owners/OwnersPathEditor.js', 'uri' => '/res/29b68354/rsrc/js/application/owners/OwnersPathEditor.js',
'type' => 'js', 'type' => 'js',
'requires' => 'requires' =>
array( array(
@ -2335,12 +2335,13 @@ celerity_register_resource_map(array(
2 => 'path-typeahead', 2 => 'path-typeahead',
3 => 'javelin-dom', 3 => 'javelin-dom',
4 => 'javelin-util', 4 => 'javelin-util',
5 => 'phabricator-prefab',
), ),
'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js', 'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js',
), ),
'owners-path-editor-css' => 'owners-path-editor-css' =>
array( array(
'uri' => '/res/9bc5332c/rsrc/css/application/owners/owners-path-editor.css', 'uri' => '/res/4fcaabf6/rsrc/css/application/owners/owners-path-editor.css',
'type' => 'css', 'type' => 'css',
'requires' => 'requires' =>
array( array(

View file

@ -928,6 +928,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php',
'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php', 'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php',
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php',
'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php',
'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
@ -2144,6 +2145,7 @@ phutil_register_library_map(array(
1 => 'PhabricatorPolicyInterface', 1 => 'PhabricatorPolicyInterface',
), ),
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase',
'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO',
'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDController' => 'PhabricatorController',
'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController',

View file

@ -193,11 +193,16 @@ final class DiffusionLintController extends DiffusionController {
foreach ($paths as $path) { foreach ($paths as $path) {
$branch = idx($branches, $repositories[$path->getRepositoryPHID()]); $branch = idx($branches, $repositories[$path->getRepositoryPHID()]);
if ($branch) { if ($branch) {
$or[] = qsprintf( $condition = qsprintf(
$conn, $conn,
'(branchID IN (%Ld) AND path LIKE %>)', '(branchID IN (%Ld) AND path LIKE %>)',
array_keys($branch), array_keys($branch),
$path->getPath()); $path->getPath());
if ($path->getExcluded()) {
$where[] = 'NOT '.$condition;
} else {
$or[] = $condition;
}
} }
} }
if (!$or) { if (!$or) {

View file

@ -66,10 +66,14 @@ final class DiffusionCommitChangeTableView extends DiffusionView {
$row_class = null; $row_class = null;
foreach ($this->ownersPaths as $owners_path) { foreach ($this->ownersPaths as $owners_path) {
$excluded = $owners_path->getExcluded();
$owners_path = $owners_path->getPath(); $owners_path = $owners_path->getPath();
if (strncmp('/'.$path, $owners_path, strlen($owners_path)) == 0) { if (strncmp('/'.$path, $owners_path, strlen($owners_path)) == 0) {
if ($excluded) {
$row_class = null;
break;
}
$row_class = 'highlighted'; $row_class = 'highlighted';
break;
} }
} }
$rowc[] = $row_class; $rowc[] = $row_class;

View file

@ -100,7 +100,9 @@ final class PhabricatorOwnersDetailController
'href' => (string) $href, 'href' => (string) $href,
), ),
phutil_escape_html($path->getPath())); phutil_escape_html($path->getPath()));
$path_links[] = $repo_name.' '.$path_link; $path_links[] =
($path->getExcluded() ? '–' : '+').' '.
$repo_name.' '.$path_link;
} }
$path_links = implode('<br />', $path_links); $path_links = implode('<br />', $path_links);
$rows[] = array( $rows[] = array(

View file

@ -47,6 +47,7 @@ final class PhabricatorOwnersEditController
$paths = $request->getArr('path'); $paths = $request->getArr('path');
$repos = $request->getArr('repo'); $repos = $request->getArr('repo');
$excludes = $request->getArr('exclude');
$path_refs = array(); $path_refs = array();
for ($ii = 0; $ii < count($paths); $ii++) { for ($ii = 0; $ii < count($paths); $ii++) {
@ -56,6 +57,7 @@ final class PhabricatorOwnersEditController
$path_refs[] = array( $path_refs[] = array(
'repositoryPHID' => $repos[$ii], 'repositoryPHID' => $repos[$ii],
'path' => $paths[$ii], 'path' => $paths[$ii],
'excluded' => $excludes[$ii],
); );
} }
@ -102,6 +104,7 @@ final class PhabricatorOwnersEditController
$path_refs[] = array( $path_refs[] = array(
'repositoryPHID' => $path->getRepositoryPHID(), 'repositoryPHID' => $path->getRepositoryPHID(),
'path' => $path->getPath(), 'path' => $path->getPath(),
'excluded' => $path->getExcluded(),
); );
} }
} }

View file

@ -34,6 +34,7 @@ final class PhabricatorOwnersListController
$where = array('1 = 1'); $where = array('1 = 1');
$join = array(); $join = array();
$having = '';
if ($request->getStr('name')) { if ($request->getStr('name')) {
$where[] = qsprintf( $where[] = qsprintf(
@ -59,10 +60,14 @@ final class PhabricatorOwnersListController
if ($request->getStr('path')) { if ($request->getStr('path')) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn_r,
'path.path LIKE %~ OR %s LIKE CONCAT(path.path, %s)', '(path.path LIKE %~ AND NOT path.excluded) OR
%s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)',
$request->getStr('path'), $request->getStr('path'),
$request->getStr('path'), $request->getStr('path'),
'_',
'\_',
'%'); '%');
$having = 'HAVING MAX(path.excluded) = 0';
} }
} }
@ -80,10 +85,11 @@ final class PhabricatorOwnersListController
$data = queryfx_all( $data = queryfx_all(
$conn_r, $conn_r,
'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id', 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q',
$package->getTableName(), $package->getTableName(),
implode(' ', $join), implode(' ', $join),
'('.implode(') AND (', $where).')'); '('.implode(') AND (', $where).')',
$having);
$packages = $package->loadAllFromArray($data); $packages = $package->loadAllFromArray($data);
$header = 'Search Results'; $header = 'Search Results';
@ -254,6 +260,7 @@ final class PhabricatorOwnersListController
'action' => 'browse', 'action' => 'browse',
)); ));
$pkg_paths[$key] = $pkg_paths[$key] =
($path->getExcluded() ? '&ndash;' : '+').' '.
'<strong>'.phutil_escape_html($repo->getName()).'</strong> '. '<strong>'.phutil_escape_html($repo->getName()).'</strong> '.
phutil_render_tag( phutil_render_tag(
'a', 'a',

View file

@ -46,8 +46,8 @@ abstract class PackageMail {
$section[] = ' In repository '.$handles[$repository_phid]->getName(). $section[] = ' In repository '.$handles[$repository_phid]->getName().
' - '. PhabricatorEnv::getProductionURI($handles[$repository_phid] ' - '. PhabricatorEnv::getProductionURI($handles[$repository_phid]
->getURI()); ->getURI());
foreach ($paths as $path => $ignored) { foreach ($paths as $path => $excluded) {
$section[] = ' '.$path; $section[] = ' '.($excluded ? 'Excluded' : 'Included').' '.$path;
} }
return implode("\n", $section); return implode("\n", $section);
@ -70,8 +70,11 @@ abstract class PackageMail {
} }
$this->mailTo = $mail_to; $this->mailTo = $mail_to;
$paths = $package->loadPaths(); $this->paths = array();
$this->paths = mgroup($paths, 'getRepositoryPHID', 'getPath'); $repository_paths = mgroup($package->loadPaths(), 'getRepositoryPHID');
foreach ($repository_paths as $repository_phid => $paths) {
$this->paths[$repository_phid] = mpull($paths, 'getExcluded', 'getPath');
}
$phids = array_merge( $phids = array_merge(
$this->mailTo, $this->mailTo,

View file

@ -113,15 +113,7 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
return array(); return array();
} }
$fragments = array( return self::loadPackagesForPaths($repository, $paths);
'/' => true,
);
foreach ($paths as $path) {
$fragments += self::splitPath($path);
}
return self::loadPackagesForPaths($repository, array_keys($fragments));
} }
public static function loadOwningPackages($repository, $path) { public static function loadOwningPackages($repository, $path) {
@ -129,14 +121,21 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
return array(); return array();
} }
$fragments = self::splitPath($path); return self::loadPackagesForPaths($repository, array($path), 1);
return self::loadPackagesForPaths($repository, array_keys($fragments), 1);
} }
private static function loadPackagesForPaths( private static function loadPackagesForPaths(
PhabricatorRepository $repository, PhabricatorRepository $repository,
array $paths, array $paths,
$limit = 0) { $limit = 0) {
$fragments = array();
foreach ($paths as $path) {
foreach (self::splitPath($path) as $fragment) {
$fragments[$fragment][$path] = true;
}
}
$package = new PhabricatorOwnersPackage(); $package = new PhabricatorOwnersPackage();
$path = new PhabricatorOwnersPath(); $path = new PhabricatorOwnersPath();
$conn = $package->establishConnection('r'); $conn = $package->establishConnection('r');
@ -151,28 +150,21 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
// branch. Break it apart so that it will fit within 'max_allowed_packet', // branch. Break it apart so that it will fit within 'max_allowed_packet',
// and then merge results in PHP. // and then merge results in PHP.
$ids = array(); $rows = array();
foreach (array_chunk($paths, 128) as $chunk) { foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
$rows = queryfx_all( $rows[] = queryfx_all(
$conn, $conn,
'SELECT pkg.id id, LENGTH(p.path) len 'SELECT pkg.id, p.excluded, p.path
FROM %T pkg JOIN %T p ON p.packageID = pkg.id FROM %T pkg JOIN %T p ON p.packageID = pkg.id
WHERE p.path IN (%Ls) %Q', WHERE p.path IN (%Ls) %Q',
$package->getTableName(), $package->getTableName(),
$path->getTableName(), $path->getTableName(),
$chunk, $chunk,
$repository_clause); $repository_clause);
foreach ($rows as $row) {
$id = (int)$row['id'];
$len = (int)$row['len'];
if (isset($ids[$id])) {
$ids[$id] = max($len, $ids[$id]);
} else {
$ids[$id] = $len;
}
}
} }
$rows = array_mergev($rows);
$ids = self::findLongestPathsPerPackage($rows, $fragments);
if (!$ids) { if (!$ids) {
return array(); return array();
@ -190,6 +182,38 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
return $packages; return $packages;
} }
public static function findLongestPathsPerPackage(array $rows, array $paths) {
$ids = array();
foreach (igroup($rows, 'id') as $id => $package_paths) {
$relevant_paths = array_select_keys(
$paths,
ipull($package_paths, 'path'));
// For every package, remove all excluded paths.
$remove = array();
foreach ($package_paths as $package_path) {
if ($package_path['excluded']) {
$remove += $relevant_paths[$package_path['path']];
unset($relevant_paths[$package_path['path']]);
}
}
if ($remove) {
foreach ($relevant_paths as $fragment => $fragment_paths) {
$relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove);
}
}
$relevant_paths = array_filter($relevant_paths);
if ($relevant_paths) {
$ids[$id] = max(array_map('strlen', array_keys($relevant_paths)));
}
}
return $ids;
}
public function save() { public function save() {
if ($this->getID()) { if ($this->getID()) {
@ -238,9 +262,15 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
$new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path'); $new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path');
$cur_paths = $this->loadPaths(); $cur_paths = $this->loadPaths();
foreach ($cur_paths as $key => $path) { foreach ($cur_paths as $key => $path) {
if (empty($new_paths[$path->getRepositoryPHID()][$path->getPath()])) { $repository_phid = $path->getRepositoryPHID();
$touched_repos[$path->getRepositoryPHID()] = true; $new_path = head(idx(
$remove_paths[$path->getRepositoryPHID()][$path->getPath()] = true; idx($new_paths, $repository_phid, array()),
$path->getPath(),
array()));
$excluded = $path->getExcluded();
if (!$new_path || $new_path['excluded'] != $excluded) {
$touched_repos[$repository_phid] = true;
$remove_paths[$repository_phid][$path->getPath()] = $excluded;
$path->delete(); $path->delete();
unset($cur_paths[$key]); unset($cur_paths[$key]);
} }
@ -255,7 +285,7 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
if (!$repository) { if (!$repository) {
continue; continue;
} }
foreach ($paths as $path => $ignored) { foreach ($paths as $path => $dicts) {
$path = ltrim($path, '/'); $path = ltrim($path, '/');
// build query to validate path // build query to validate path
$drequest = DiffusionRequest::newFromDictionary( $drequest = DiffusionRequest::newFromDictionary(
@ -286,11 +316,13 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
} }
if (empty($cur_paths[$repository_phid][$path]) && $valid) { if (empty($cur_paths[$repository_phid][$path]) && $valid) {
$touched_repos[$repository_phid] = true; $touched_repos[$repository_phid] = true;
$add_paths[$repository_phid][$path] = true; $excluded = idx(reset($dicts), 'excluded', 0);
$add_paths[$repository_phid][$path] = $excluded;
$obj = new PhabricatorOwnersPath(); $obj = new PhabricatorOwnersPath();
$obj->setPackageID($this->getID()); $obj->setPackageID($this->getID());
$obj->setRepositoryPHID($repository_phid); $obj->setRepositoryPHID($repository_phid);
$obj->setPath($path); $obj->setPath($path);
$obj->setExcluded($excluded);
$obj->save(); $obj->save();
} }
} }
@ -340,12 +372,12 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
} }
private static function splitPath($path) { private static function splitPath($path) {
$result = array(); $result = array('/');
$trailing_slash = preg_match('@/$@', $path) ? '/' : ''; $trailing_slash = preg_match('@/$@', $path) ? '/' : '';
$path = trim($path, '/'); $path = trim($path, '/');
$parts = explode('/', $path); $parts = explode('/', $path);
while (count($parts)) { while (count($parts)) {
$result['/'.implode('/', $parts).$trailing_slash] = true; $result[] = '/'.implode('/', $parts).$trailing_slash;
$trailing_slash = '/'; $trailing_slash = '/';
array_pop($parts); array_pop($parts);
} }

View file

@ -5,6 +5,7 @@ final class PhabricatorOwnersPath extends PhabricatorOwnersDAO {
protected $packageID; protected $packageID;
protected $repositoryPHID; protected $repositoryPHID;
protected $path; protected $path;
protected $excluded;
public function getConfiguration() { public function getConfiguration() {
return array( return array(

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase {
function testFindLongestPathsPerPackage() {
$rows = array(
array('id' => 1, 'excluded' => 0, 'path' => 'src/'),
array('id' => 1, 'excluded' => 1, 'path' => 'src/releeph/'),
array('id' => 2, 'excluded' => 0, 'path' => 'src/releeph/'),
);
$paths = array(
'src/' => array('src/a.php' => true, 'src/releeph/b.php' => true),
'src/releeph/' => array('src/releeph/b.php' => true),
);
$this->assertEqual(
array(
1 => strlen('src/'),
2 => strlen('src/releeph/'),
),
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
$paths = array(
'src/' => array('src/releeph/b.php' => true),
'src/releeph/' => array('src/releeph/b.php' => true),
);
$this->assertEqual(
array(
2 => strlen('src/releeph/'),
),
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
}
}

View file

@ -1040,6 +1040,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('pholio.sql'), 'name' => $this->getPatchPath('pholio.sql'),
), ),
'owners-exclude.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('owners-exclude.sql'),
),
); );
} }

View file

@ -10,12 +10,12 @@
padding: 2px 4px; padding: 2px 4px;
} }
.owners-path-editor-table select { .owners-path-editor-table select.owners-repo {
width: 150px; width: 150px;
} }
.owners-path-editor-table input { .owners-path-editor-table input {
width: 550px; width: 450px;
} }
.owners-path-editor-table div.error-display { .owners-path-editor-table div.error-display {

View file

@ -4,6 +4,7 @@
* path-typeahead * path-typeahead
* javelin-dom * javelin-dom
* javelin-util * javelin-util
* phabricator-prefab
* @provides owners-path-editor * @provides owners-path-editor
* @javelin * @javelin
*/ */
@ -95,7 +96,8 @@ JX.install('OwnersPathEditor', {
this._lastRepositoryChoice; this._lastRepositoryChoice;
var options = this._buildRepositoryOptions(selected_repository); var options = this._buildRepositoryOptions(selected_repository);
var attrs = { var attrs = {
name : "repo[" + this._count + "]" name : "repo[" + this._count + "]",
className : 'owners-repo'
}; };
var repo_select = JX.$N('select', attrs, options); var repo_select = JX.$N('select', attrs, options);
@ -132,8 +134,14 @@ JX.install('OwnersPathEditor', {
var error_display_cell = JX.$N('td', {}, error_display); var error_display_cell = JX.$N('td', {}, error_display);
var exclude = JX.Prefab.renderSelect(
{'0' : 'Include', '1' : 'Exclude'},
path_ref.excluded,
{name : 'exclude[' + this._count + ']'});
var exclude_cell = JX.$N('td', {}, exclude);
var row = this._rowManager.addRow( var row = this._rowManager.addRow(
[repo_cell, typeahead_cell, error_display_cell]); [exclude_cell, repo_cell, typeahead_cell, error_display_cell]);
new JX.PathTypeahead({ new JX.PathTypeahead({
repositoryDefaultPaths : this._repositoryDefaultPaths, repositoryDefaultPaths : this._repositoryDefaultPaths,