private static function loadPackagesForPaths(PhabricatorRepository $repository, array $paths, $limit = 0)
 {
     $fragments = array();
     foreach ($paths as $path) {
         foreach (self::splitPath($path) as $fragment) {
             $fragments[$fragment][$path] = true;
         }
     }
     $package = new PhabricatorOwnersPackage();
     $path = new PhabricatorOwnersPath();
     $conn = $package->establishConnection('r');
     $repository_clause = qsprintf($conn, 'AND p.repositoryPHID = %s', $repository->getPHID());
     // NOTE: The list of $paths may be very large if we're coming from
     // the OwnersWorker and processing, e.g., an SVN commit which created a new
     // branch. Break it apart so that it will fit within 'max_allowed_packet',
     // and then merge results in PHP.
     $rows = array();
     foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
         $rows[] = queryfx_all($conn, 'SELECT pkg.id, p.excluded, p.path
       FROM %T pkg JOIN %T p ON p.packageID = pkg.id
       WHERE p.path IN (%Ls) %Q', $package->getTableName(), $path->getTableName(), $chunk, $repository_clause);
     }
     $rows = array_mergev($rows);
     $ids = self::findLongestPathsPerPackage($rows, $fragments);
     if (!$ids) {
         return array();
     }
     arsort($ids);
     if ($limit) {
         $ids = array_slice($ids, 0, $limit, $preserve_keys = true);
     }
     $ids = array_keys($ids);
     $packages = $package->loadAllWhere('id in (%Ld)', $ids);
     $packages = array_select_keys($packages, $ids);
     return $packages;
 }
 public function save()
 {
     // TODO: Transactions!
     $ret = parent::save();
     if ($this->unsavedOwners) {
         $new_owners = array_fill_keys($this->unsavedOwners, true);
         $cur_owners = array();
         foreach ($this->loadOwners() as $owner) {
             if (empty($new_owners[$owner->getUserPHID()])) {
                 $owner->delete();
                 continue;
             }
             $cur_owners[$owner->getUserPHID()] = true;
         }
         $add_owners = array_diff_key($new_owners, $cur_owners);
         foreach ($add_owners as $phid => $ignored) {
             $owner = new PhabricatorOwnersOwner();
             $owner->setPackageID($this->getID());
             $owner->setUserPHID($phid);
             $owner->save();
         }
         unset($this->unsavedOwners);
     }
     if ($this->unsavedPaths) {
         $new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path');
         $cur_paths = $this->loadPaths();
         foreach ($cur_paths as $key => $path) {
             if (empty($new_paths[$path->getRepositoryPHID()][$path->getPath()])) {
                 $path->delete();
                 unset($cur_paths[$key]);
             }
         }
         $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath');
         foreach ($new_paths as $repository_phid => $paths) {
             foreach ($paths as $path => $ignored) {
                 if (empty($cur_paths[$repository_phid][$path])) {
                     $obj = new PhabricatorOwnersPath();
                     $obj->setPackageID($this->getID());
                     $obj->setRepositoryPHID($repository_phid);
                     $obj->setPath($path);
                     $obj->save();
                 }
             }
         }
         unset($this->unsavedPaths);
     }
     return $ret;
 }
 public function renderChangeDetails(PhabricatorUser $viewer)
 {
     switch ($this->getTransactionType()) {
         case self::TYPE_DESCRIPTION:
             $old = $this->getOldValue();
             $new = $this->getNewValue();
             return $this->renderTextCorpusChangeDetails($viewer, $old, $new);
         case self::TYPE_PATHS:
             $old = $this->getOldValue();
             $new = $this->getNewValue();
             $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
             list($rem, $add) = $diffs;
             $rows = array();
             foreach ($rem as $ref) {
                 $rows[] = array('class' => 'diff-removed', 'change' => '-') + $ref;
             }
             foreach ($add as $ref) {
                 $rows[] = array('class' => 'diff-added', 'change' => '+') + $ref;
             }
             $rowc = array();
             foreach ($rows as $key => $row) {
                 $rowc[] = $row['class'];
                 $rows[$key] = array($row['change'], $row['excluded'] ? pht('Exclude') : pht('Include'), $viewer->renderHandle($row['repositoryPHID']), $row['path']);
             }
             $table = id(new AphrontTableView($rows))->setRowClasses($rowc)->setHeaders(array(null, pht('Type'), pht('Repository'), pht('Path')))->setColumnClasses(array(null, null, null, 'wide'));
             return $table;
     }
     return parent::renderChangeDetails($viewer);
 }
 protected function applyCustomExternalTransaction(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction)
 {
     switch ($xaction->getTransactionType()) {
         case PhabricatorOwnersPackageTransaction::TYPE_NAME:
         case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY:
         case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
         case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
             return;
         case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
             $old = $xaction->getOldValue();
             $new = $xaction->getNewValue();
             // TODO: needOwners this
             $owners = $object->loadOwners();
             $owners = mpull($owners, null, 'getUserPHID');
             $rem = array_diff($old, $new);
             foreach ($rem as $phid) {
                 if (isset($owners[$phid])) {
                     $owners[$phid]->delete();
                     unset($owners[$phid]);
                 }
             }
             $add = array_diff($new, $old);
             foreach ($add as $phid) {
                 $owners[$phid] = id(new PhabricatorOwnersOwner())->setPackageID($object->getID())->setUserPHID($phid)->save();
             }
             // TODO: Attach owners here
             return;
         case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
             $old = $xaction->getOldValue();
             $new = $xaction->getNewValue();
             // TODO: needPaths this
             $paths = $object->loadPaths();
             $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
             list($rem, $add) = $diffs;
             $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem);
             foreach ($paths as $path) {
                 $ref = $path->getRef();
                 if (PhabricatorOwnersPath::isRefInSet($ref, $set)) {
                     $path->delete();
                 }
             }
             foreach ($add as $ref) {
                 $path = PhabricatorOwnersPath::newFromRef($ref)->setPackageID($object->getID())->save();
             }
             return;
     }
     return parent::applyCustomExternalTransaction($object, $xaction);
 }
 public function save()
 {
     if ($this->getID()) {
         $is_new = false;
     } else {
         $is_new = true;
     }
     $this->openTransaction();
     $ret = parent::save();
     $add_owners = array();
     $remove_owners = array();
     $all_owners = array();
     if ($this->unsavedOwners) {
         $new_owners = array_fill_keys($this->unsavedOwners, true);
         $cur_owners = array();
         foreach ($this->loadOwners() as $owner) {
             if (empty($new_owners[$owner->getUserPHID()])) {
                 $remove_owners[$owner->getUserPHID()] = true;
                 $owner->delete();
                 continue;
             }
             $cur_owners[$owner->getUserPHID()] = true;
         }
         $add_owners = array_diff_key($new_owners, $cur_owners);
         $all_owners = array_merge(array($this->getPrimaryOwnerPHID() => true), $new_owners, $remove_owners);
         foreach ($add_owners as $phid => $ignored) {
             $owner = new PhabricatorOwnersOwner();
             $owner->setPackageID($this->getID());
             $owner->setUserPHID($phid);
             $owner->save();
         }
         unset($this->unsavedOwners);
     }
     $add_paths = array();
     $remove_paths = array();
     $touched_repos = array();
     if ($this->unsavedPaths) {
         $new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path');
         $cur_paths = $this->loadPaths();
         foreach ($cur_paths as $key => $path) {
             if (empty($new_paths[$path->getRepositoryPHID()][$path->getPath()])) {
                 $touched_repos[$path->getRepositoryPHID()] = true;
                 $remove_paths[$path->getRepositoryPHID()][$path->getPath()] = true;
                 $path->delete();
                 unset($cur_paths[$key]);
             }
         }
         $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath');
         foreach ($new_paths as $repository_phid => $paths) {
             // get repository object for path validation
             $repository = id(new PhabricatorRepository())->loadOneWhere('phid = %s', $repository_phid);
             if (!$repository) {
                 continue;
             }
             foreach ($paths as $path => $ignored) {
                 $path = ltrim($path, '/');
                 // build query to validate path
                 $drequest = DiffusionRequest::newFromDictionary(array('repository' => $repository, 'path' => $path));
                 $query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest);
                 $query->needValidityOnly(true);
                 $valid = $query->loadPaths();
                 $is_directory = true;
                 if (!$valid) {
                     switch ($query->getReasonForEmptyResultSet()) {
                         case DiffusionBrowseQuery::REASON_IS_FILE:
                             $valid = true;
                             $is_directory = false;
                             break;
                         case DiffusionBrowseQuery::REASON_IS_EMPTY:
                             $valid = true;
                             break;
                     }
                 }
                 if ($is_directory && substr($path, -1) != '/') {
                     $path .= '/';
                 }
                 if (substr($path, 0, 1) != '/') {
                     $path = '/' . $path;
                 }
                 if (empty($cur_paths[$repository_phid][$path]) && $valid) {
                     $touched_repos[$repository_phid] = true;
                     $add_paths[$repository_phid][$path] = true;
                     $obj = new PhabricatorOwnersPath();
                     $obj->setPackageID($this->getID());
                     $obj->setRepositoryPHID($repository_phid);
                     $obj->setPath($path);
                     $obj->save();
                 }
             }
         }
         unset($this->unsavedPaths);
     }
     $this->saveTransaction();
     if ($is_new) {
         $mail = new PackageCreateMail($this);
     } else {
         $mail = new PackageModifyMail($this, array_keys($add_owners), array_keys($remove_owners), array_keys($all_owners), array_keys($touched_repos), $add_paths, $remove_paths);
     }
     $mail->send();
     return $ret;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $views = array('owned' => 'Owned Packages', 'all' => 'All Packages', 'search' => 'Search Results');
     if (empty($views[$this->view])) {
         reset($views);
         $this->view = key($views);
     }
     if ($this->view != 'search') {
         unset($views['search']);
     }
     $nav = new AphrontSideNavView();
     foreach ($views as $key => $name) {
         $nav->addNavItem(phutil_render_tag('a', array('href' => '/owners/view/' . $key . '/', 'class' => $this->view == $key ? 'aphront-side-nav-selected' : null), phutil_escape_html($name)));
     }
     $package = new PhabricatorOwnersPackage();
     $owner = new PhabricatorOwnersOwner();
     $path = new PhabricatorOwnersPath();
     switch ($this->view) {
         case 'search':
             $packages = array();
             $conn_r = $package->establishConnection('r');
             $where = array('1 = 1');
             $join = array();
             if ($request->getStr('name')) {
                 $where[] = qsprintf($conn_r, 'p.name LIKE %~', $request->getStr('name'));
             }
             if ($request->getStr('path')) {
                 $join[] = qsprintf($conn_r, 'JOIN %T path ON path.packageID = p.id', $path->getTableName());
                 $where[] = qsprintf($conn_r, 'path.path LIKE %~', $request->getStr('path'));
             }
             if ($request->getArr('owner')) {
                 $join[] = qsprintf($conn_r, 'JOIN %T o ON o.packageID = p.id', $owner->getTableName());
                 $where[] = qsprintf($conn_r, 'o.userPHID IN (%Ls)', $request->getArr('owner'));
             }
             $data = queryfx_all($conn_r, 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id', $package->getTableName(), implode(' ', $join), '(' . implode(') AND (', $where) . ')');
             $packages = $package->loadAllFromArray($data);
             $header = 'Search Results';
             $nodata = 'No packages match your query.';
             break;
         case 'owned':
             $data = queryfx_all($package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID
         WHERE o.userPHID = %s GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $user->getPHID());
             $packages = $package->loadAllFromArray($data);
             $header = 'Owned Packages';
             $nodata = 'No owned packages';
             break;
         case 'all':
             $packages = $package->loadAll();
             $header = 'All Packages';
             $nodata = 'There are no defined packages.';
             break;
     }
     $content = $this->renderPackageTable($packages, $header, $nodata);
     $filter = new AphrontListFilterView();
     $filter->addButton(phutil_render_tag('a', array('href' => '/owners/new/', 'class' => 'green button'), 'Create New Package'));
     $owners_search_value = array();
     if ($request->getArr('owner')) {
         $phids = $request->getArr('owner');
         $phid = reset($phids);
         $handles = id(new PhabricatorObjectHandleData(array($phid)))->loadHandles();
         $owners_search_value = array($phid => $handles[$phid]->getFullName());
     }
     $form = id(new AphrontFormView())->setUser($user)->setAction('/owners/view/search/')->appendChild(id(new AphrontFormTextControl())->setName('name')->setLabel('Name')->setValue($request->getStr('name')))->appendChild(id(new AphrontFormTokenizerControl())->setDatasource('/typeahead/common/users/')->setLimit(1)->setName('owner')->setLabel('Owner')->setValue($owners_search_value))->appendChild(id(new AphrontFormTextControl())->setName('path')->setLabel('Path')->setValue($request->getStr('path')))->appendChild(id(new AphrontFormSubmitControl())->setValue('Search for Packages'));
     $filter->appendChild($form);
     $nav->appendChild($filter);
     $nav->appendChild($content);
     return $this->buildStandardPageResponse($nav, array('title' => 'Package Index', 'tab' => 'index'));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $package = new PhabricatorOwnersPackage();
     $owner = new PhabricatorOwnersOwner();
     $path = new PhabricatorOwnersPath();
     $repository_phid = '';
     if ($request->getStr('repository') != '') {
         $repository_phid = id(new PhabricatorRepositoryQuery())->setViewer($user)->withCallsigns(array($request->getStr('repository')))->executeOne()->getPHID();
     }
     switch ($this->view) {
         case 'search':
             $packages = array();
             $conn_r = $package->establishConnection('r');
             $where = array('1 = 1');
             $join = array();
             $having = '';
             if ($request->getStr('name')) {
                 $where[] = qsprintf($conn_r, 'p.name LIKE %~', $request->getStr('name'));
             }
             if ($repository_phid || $request->getStr('path')) {
                 $join[] = qsprintf($conn_r, 'JOIN %T path ON path.packageID = p.id', $path->getTableName());
                 if ($repository_phid) {
                     $where[] = qsprintf($conn_r, 'path.repositoryPHID = %s', $repository_phid);
                 }
                 if ($request->getStr('path')) {
                     $where[] = qsprintf($conn_r, '(path.path LIKE %~ AND NOT path.excluded) OR
             %s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)', $request->getStr('path'), $request->getStr('path'), '_', '\\_', '%');
                     $having = 'HAVING MAX(path.excluded) = 0';
                 }
             }
             if ($request->getArr('owner')) {
                 $join[] = qsprintf($conn_r, 'JOIN %T o ON o.packageID = p.id', $owner->getTableName());
                 $where[] = qsprintf($conn_r, 'o.userPHID IN (%Ls)', $request->getArr('owner'));
             }
             $data = queryfx_all($conn_r, 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q', $package->getTableName(), implode(' ', $join), '(' . implode(') AND (', $where) . ')', $having);
             $packages = $package->loadAllFromArray($data);
             $header = pht('Search Results');
             $nodata = pht('No packages match your query.');
             break;
         case 'owned':
             $data = queryfx_all($package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID
         WHERE o.userPHID = %s GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $user->getPHID());
             $packages = $package->loadAllFromArray($data);
             $header = pht('Owned Packages');
             $nodata = pht('No owned packages');
             break;
         case 'projects':
             $projects = id(new PhabricatorProjectQuery())->setViewer($user)->withMemberPHIDs(array($user->getPHID()))->withStatus(PhabricatorProjectQuery::STATUS_ANY)->execute();
             $owner_phids = mpull($projects, 'getPHID');
             if ($owner_phids) {
                 $data = queryfx_all($package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID
           WHERE o.userPHID IN (%Ls) GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $owner_phids);
             } else {
                 $data = array();
             }
             $packages = $package->loadAllFromArray($data);
             $header = pht('Owned Packages');
             $nodata = pht('No owned packages');
             break;
         case 'all':
             $packages = $package->loadAll();
             $header = pht('All Packages');
             $nodata = pht('There are no defined packages.');
             break;
     }
     $content = $this->renderPackageTable($packages, $header, $nodata);
     $filter = new AphrontListFilterView();
     $owners_search_value = array();
     if ($request->getArr('owner')) {
         $phids = $request->getArr('owner');
         $phid = reset($phids);
         $handles = $this->loadViewerHandles(array($phid));
         $owners_search_value = array($handles[$phid]);
     }
     $callsigns = array('' => pht('(Any Repository)'));
     $repositories = id(new PhabricatorRepositoryQuery())->setViewer($user)->setOrder(PhabricatorRepositoryQuery::ORDER_CALLSIGN)->execute();
     foreach ($repositories as $repository) {
         $callsigns[$repository->getCallsign()] = $repository->getCallsign() . ': ' . $repository->getName();
     }
     $form = id(new AphrontFormView())->setUser($user)->setAction('/owners/view/search/')->setMethod('GET')->appendChild(id(new AphrontFormTextControl())->setName('name')->setLabel(pht('Name'))->setValue($request->getStr('name')))->appendChild(id(new AphrontFormTokenizerControl())->setDatasource(new PhabricatorProjectOrUserDatasource())->setLimit(1)->setName('owner')->setLabel(pht('Owner'))->setValue($owners_search_value))->appendChild(id(new AphrontFormSelectControl())->setName('repository')->setLabel(pht('Repository'))->setOptions($callsigns)->setValue($request->getStr('repository')))->appendChild(id(new AphrontFormTextControl())->setName('path')->setLabel(pht('Path'))->setValue($request->getStr('path')))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Search for Packages')));
     $filter->appendChild($form);
     $nav = $this->buildSideNavView();
     $nav->appendChild($filter);
     $nav->appendChild($content);
     return $this->buildApplicationPage(array($nav), array('title' => pht('Package Index')));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $package = new PhabricatorOwnersPackage();
     $owner = new PhabricatorOwnersOwner();
     $path = new PhabricatorOwnersPath();
     $repository_phid = '';
     if ($request->getStr('repository') != '') {
         $repository_phid = id(new PhabricatorRepository())->loadOneWhere('callsign = %s', $request->getStr('repository'))->getPHID();
     }
     switch ($this->view) {
         case 'search':
             $packages = array();
             $conn_r = $package->establishConnection('r');
             $where = array('1 = 1');
             $join = array();
             if ($request->getStr('name')) {
                 $where[] = qsprintf($conn_r, 'p.name LIKE %~', $request->getStr('name'));
             }
             if ($repository_phid || $request->getStr('path')) {
                 $join[] = qsprintf($conn_r, 'JOIN %T path ON path.packageID = p.id', $path->getTableName());
                 if ($repository_phid) {
                     $where[] = qsprintf($conn_r, 'path.repositoryPHID = %s', $repository_phid);
                 }
                 if ($request->getStr('path')) {
                     $where[] = qsprintf($conn_r, 'path.path LIKE %~ OR %s LIKE CONCAT(path.path, %s)', $request->getStr('path'), $request->getStr('path'), '%');
                 }
             }
             if ($request->getArr('owner')) {
                 $join[] = qsprintf($conn_r, 'JOIN %T o ON o.packageID = p.id', $owner->getTableName());
                 $where[] = qsprintf($conn_r, 'o.userPHID IN (%Ls)', $request->getArr('owner'));
             }
             $data = queryfx_all($conn_r, 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id', $package->getTableName(), implode(' ', $join), '(' . implode(') AND (', $where) . ')');
             $packages = $package->loadAllFromArray($data);
             $header = 'Search Results';
             $nodata = 'No packages match your query.';
             break;
         case 'owned':
             $data = queryfx_all($package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID
         WHERE o.userPHID = %s GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $user->getPHID());
             $packages = $package->loadAllFromArray($data);
             $header = 'Owned Packages';
             $nodata = 'No owned packages';
             break;
         case 'all':
             $packages = $package->loadAll();
             $header = 'All Packages';
             $nodata = 'There are no defined packages.';
             break;
     }
     $content = $this->renderPackageTable($packages, $header, $nodata);
     $filter = new AphrontListFilterView();
     $filter->addButton(phutil_render_tag('a', array('href' => '/owners/new/', 'class' => 'green button'), 'Create New Package'));
     $owners_search_value = array();
     if ($request->getArr('owner')) {
         $phids = $request->getArr('owner');
         $phid = reset($phids);
         $handles = id(new PhabricatorObjectHandleData(array($phid)))->loadHandles();
         $owners_search_value = array($phid => $handles[$phid]->getFullName());
     }
     $callsigns = array('' => '(Any Repository)');
     $repositories = id(new PhabricatorRepository())->loadAllWhere('1 = 1 ORDER BY callsign');
     foreach ($repositories as $repository) {
         $callsigns[$repository->getCallsign()] = $repository->getCallsign() . ': ' . $repository->getName();
     }
     $form = id(new AphrontFormView())->setUser($user)->setAction('/owners/view/search/')->setMethod('GET')->appendChild(id(new AphrontFormTextControl())->setName('name')->setLabel('Name')->setValue($request->getStr('name')))->appendChild(id(new AphrontFormTokenizerControl())->setDatasource('/typeahead/common/usersorprojects/')->setLimit(1)->setName('owner')->setLabel('Owner')->setValue($owners_search_value))->appendChild(id(new AphrontFormSelectControl())->setName('repository')->setLabel('Repository')->setOptions($callsigns)->setValue($request->getStr('repository')))->appendChild(id(new AphrontFormTextControl())->setName('path')->setLabel('Path')->setValue($request->getStr('path')))->appendChild(id(new AphrontFormSubmitControl())->setValue('Search for Packages'));
     $filter->appendChild($form);
     return $this->buildStandardPageResponse(array($filter, $content), array('title' => 'Package Index'));
 }
 protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions)
 {
     $errors = parent::validateTransaction($object, $type, $xactions);
     switch ($type) {
         case PhabricatorOwnersPackageTransaction::TYPE_NAME:
             $missing = $this->validateIsEmptyTextField($object->getName(), $xactions);
             if ($missing) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('Package name is required.'), nonempty(last($xactions), null));
                 $error->setIsMissingFieldError(true);
                 $errors[] = $error;
             }
             break;
         case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
             if (!$xactions) {
                 continue;
             }
             $old = mpull($object->getPaths(), 'getRef');
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 // Check that we have a list of paths.
                 if (!is_array($new)) {
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Path specification must be a list of paths.'), $xaction);
                     continue;
                 }
                 // Check that each item in the list is formatted properly.
                 $type_exception = null;
                 foreach ($new as $key => $value) {
                     try {
                         PhutilTypeSpec::checkMap($value, array('repositoryPHID' => 'string', 'path' => 'string', 'excluded' => 'optional wild'));
                     } catch (PhutilTypeCheckException $ex) {
                         $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Path specification list contains invalid value ' . 'in key "%s": %s.', $key, $ex->getMessage()), $xaction);
                         $type_exception = $ex;
                     }
                 }
                 if ($type_exception) {
                     continue;
                 }
                 // Check that any new paths reference legitimate repositories which
                 // the viewer has permission to see.
                 list($rem, $add) = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
                 if ($add) {
                     $repository_phids = ipull($add, 'repositoryPHID');
                     $repositories = id(new PhabricatorRepositoryQuery())->setViewer($this->getActor())->withPHIDs($repository_phids)->execute();
                     $repositories = mpull($repositories, null, 'getPHID');
                     foreach ($add as $ref) {
                         $repository_phid = $ref['repositoryPHID'];
                         if (isset($repositories[$repository_phid])) {
                             continue;
                         }
                         $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Path specification list references repository PHID "%s", ' . 'but that is not a valid, visible repository.', $repository_phid));
                     }
                 }
             }
             break;
     }
     return $errors;
 }
 public function newChangeDetailView()
 {
     $old = $this->getOldValue();
     $new = $this->getNewValue();
     $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
     list($rem, $add) = $diffs;
     $rows = array();
     foreach ($rem as $ref) {
         $rows[] = array('class' => 'diff-removed', 'change' => '-') + $ref;
     }
     foreach ($add as $ref) {
         $rows[] = array('class' => 'diff-added', 'change' => '+') + $ref;
     }
     $rowc = array();
     foreach ($rows as $key => $row) {
         $rowc[] = $row['class'];
         $rows[$key] = array($row['change'], $row['excluded'] ? pht('Exclude') : pht('Include'), $this->renderHandle($row['repositoryPHID']), $row['path']);
     }
     $table = id(new AphrontTableView($rows))->setViewer($this->getViewer())->setRowClasses($rowc)->setHeaders(array(null, pht('Type'), pht('Repository'), pht('Path')))->setColumnClasses(array(null, null, null, 'wide'));
     return $table;
 }