PageRenderTime 30ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php

http://github.com/facebook/phabricator
PHP | 671 lines | 546 code | 102 blank | 23 comment | 51 complexity | 4641a7f05028a95392f2b42bbe4ee045 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2012 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. class DifferentialRevisionViewController extends DifferentialController {
  18. private $revisionID;
  19. public function shouldRequireLogin() {
  20. return !$this->allowsAnonymousAccess();
  21. }
  22. public function willProcessRequest(array $data) {
  23. $this->revisionID = $data['id'];
  24. }
  25. public function processRequest() {
  26. $request = $this->getRequest();
  27. $user = $request->getUser();
  28. $viewer_is_anonymous = !$user->isLoggedIn();
  29. $revision = id(new DifferentialRevision())->load($this->revisionID);
  30. if (!$revision) {
  31. return new Aphront404Response();
  32. }
  33. $revision->loadRelationships();
  34. $diffs = $revision->loadDiffs();
  35. if (!$diffs) {
  36. throw new Exception(
  37. "This revision has no diffs. Something has gone quite wrong.");
  38. }
  39. $diff_vs = $request->getInt('vs');
  40. $target = end($diffs);
  41. $target_id = $request->getInt('id');
  42. if ($target_id) {
  43. if (isset($diffs[$target_id])) {
  44. $target = $diffs[$target_id];
  45. }
  46. }
  47. $diffs = mpull($diffs, null, 'getID');
  48. if (empty($diffs[$diff_vs])) {
  49. $diff_vs = null;
  50. }
  51. list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties(
  52. $revision,
  53. $target,
  54. array(
  55. 'local:commits',
  56. ));
  57. list($changesets, $vs_map, $rendering_references) =
  58. $this->loadChangesetsAndVsMap($diffs, $diff_vs, $target);
  59. $comments = $revision->loadComments();
  60. $comments = array_merge(
  61. $this->getImplicitComments($revision),
  62. $comments);
  63. $all_changesets = $changesets;
  64. $inlines = $this->loadInlineComments($comments, $all_changesets);
  65. $object_phids = array_merge(
  66. $revision->getReviewers(),
  67. $revision->getCCPHIDs(),
  68. $revision->loadCommitPHIDs(),
  69. array(
  70. $revision->getAuthorPHID(),
  71. $user->getPHID(),
  72. ),
  73. mpull($comments, 'getAuthorPHID'));
  74. foreach ($comments as $comment) {
  75. $metadata = $comment->getMetadata();
  76. $added_reviewers = idx(
  77. $metadata,
  78. DifferentialComment::METADATA_ADDED_REVIEWERS);
  79. if ($added_reviewers) {
  80. foreach ($added_reviewers as $phid) {
  81. $object_phids[] = $phid;
  82. }
  83. }
  84. $added_ccs = idx(
  85. $metadata,
  86. DifferentialComment::METADATA_ADDED_CCS);
  87. if ($added_ccs) {
  88. foreach ($added_ccs as $phid) {
  89. $object_phids[] = $phid;
  90. }
  91. }
  92. }
  93. foreach ($revision->getAttached() as $type => $phids) {
  94. foreach ($phids as $phid => $info) {
  95. $object_phids[] = $phid;
  96. }
  97. }
  98. $aux_phids = array();
  99. foreach ($aux_fields as $key => $aux_field) {
  100. $aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView();
  101. }
  102. $object_phids = array_merge($object_phids, array_mergev($aux_phids));
  103. $object_phids = array_unique($object_phids);
  104. $handles = id(new PhabricatorObjectHandleData($object_phids))
  105. ->loadHandles();
  106. foreach ($aux_fields as $key => $aux_field) {
  107. // Make sure each field only has access to handles it specifically
  108. // requested, not all handles. Otherwise you can get a field which works
  109. // only in the presence of other fields.
  110. $aux_field->setHandles(array_select_keys($handles, $aux_phids[$key]));
  111. }
  112. $reviewer_warning = null;
  113. $has_live_reviewer = false;
  114. foreach ($revision->getReviewers() as $reviewer) {
  115. if (!$handles[$reviewer]->isDisabled()) {
  116. $has_live_reviewer = true;
  117. }
  118. }
  119. if (!$has_live_reviewer) {
  120. $reviewer_warning = new AphrontErrorView();
  121. $reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
  122. $reviewer_warning->setTitle('No Active Reviewers');
  123. if ($revision->getReviewers()) {
  124. $reviewer_warning->appendChild(
  125. '<p>All specified reviewers are disabled. You may want to add '.
  126. 'some new reviewers.</p>');
  127. } else {
  128. $reviewer_warning->appendChild(
  129. '<p>This revision has no specified reviewers. You may want to '.
  130. 'add some.</p>');
  131. }
  132. }
  133. $request_uri = $request->getRequestURI();
  134. $limit = 100;
  135. $large = $request->getStr('large');
  136. if (count($changesets) > $limit && !$large) {
  137. $count = number_format(count($changesets));
  138. $warning = new AphrontErrorView();
  139. $warning->setTitle('Very Large Diff');
  140. $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
  141. $warning->setWidth(AphrontErrorView::WIDTH_WIDE);
  142. $warning->appendChild(
  143. "<p>This diff is very large and affects {$count} files. Use ".
  144. "Table of Contents to open files in a standalone view. ".
  145. "<strong>".
  146. phutil_render_tag(
  147. 'a',
  148. array(
  149. 'href' => $request_uri->alter('large', 'true'),
  150. ),
  151. 'Show All Files Inline').
  152. "</strong>");
  153. $warning = $warning->render();
  154. $visible_changesets = array();
  155. } else {
  156. $warning = null;
  157. $visible_changesets = $changesets;
  158. }
  159. $revision_detail = new DifferentialRevisionDetailView();
  160. $revision_detail->setRevision($revision);
  161. $revision_detail->setAuxiliaryFields($aux_fields);
  162. $actions = $this->getRevisionActions($revision);
  163. $custom_renderer_class = PhabricatorEnv::getEnvConfig(
  164. 'differential.revision-custom-detail-renderer');
  165. if ($custom_renderer_class) {
  166. // TODO: build a better version of the action links and deprecate the
  167. // whole DifferentialRevisionDetailRenderer class.
  168. PhutilSymbolLoader::loadClass($custom_renderer_class);
  169. $custom_renderer =
  170. newv($custom_renderer_class, array());
  171. $actions = array_merge(
  172. $actions,
  173. $custom_renderer->generateActionLinks($revision, $target));
  174. }
  175. $whitespace = $request->getStr(
  176. 'whitespace',
  177. DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
  178. $arc_project = $target->loadArcanistProject();
  179. if ($arc_project) {
  180. $symbol_indexes = $this->buildSymbolIndexes(
  181. $target,
  182. $arc_project,
  183. $visible_changesets);
  184. $repository = $arc_project->loadRepository();
  185. } else {
  186. $symbol_indexes = array();
  187. $repository = null;
  188. }
  189. $revision_detail->setActions($actions);
  190. $revision_detail->setUser($user);
  191. $comment_view = new DifferentialRevisionCommentListView();
  192. $comment_view->setComments($comments);
  193. $comment_view->setHandles($handles);
  194. $comment_view->setInlineComments($inlines);
  195. $comment_view->setChangesets($all_changesets);
  196. $comment_view->setUser($user);
  197. $comment_view->setTargetDiff($target);
  198. $comment_view->setVersusDiffID($diff_vs);
  199. $changeset_view = new DifferentialChangesetListView();
  200. $changeset_view->setChangesets($visible_changesets);
  201. $changeset_view->setEditable(!$viewer_is_anonymous);
  202. $changeset_view->setStandaloneViews(true);
  203. $changeset_view->setUser($user);
  204. $changeset_view->setRevision($revision);
  205. $changeset_view->setDiff($target);
  206. $changeset_view->setRenderingReferences($rendering_references);
  207. $changeset_view->setWhitespace($whitespace);
  208. if ($repository) {
  209. $changeset_view->setRepository($repository, $target);
  210. }
  211. $changeset_view->setSymbolIndexes($symbol_indexes);
  212. $diff_history = new DifferentialRevisionUpdateHistoryView();
  213. $diff_history->setDiffs($diffs);
  214. $diff_history->setSelectedVersusDiffID($diff_vs);
  215. $diff_history->setSelectedDiffID($target->getID());
  216. $diff_history->setSelectedWhitespace($whitespace);
  217. $diff_history->setUser($user);
  218. $local_view = new DifferentialLocalCommitsView();
  219. $local_view->setUser($user);
  220. $local_view->setLocalCommits(idx($props, 'local:commits'));
  221. $toc_view = new DifferentialDiffTableOfContentsView();
  222. $toc_view->setChangesets($changesets);
  223. $toc_view->setStandaloneViewLink(empty($visible_changesets));
  224. $toc_view->setVsMap($vs_map);
  225. $toc_view->setRevisionID($revision->getID());
  226. $toc_view->setWhitespace($whitespace);
  227. if (!$viewer_is_anonymous) {
  228. $draft = id(new PhabricatorDraft())->loadOneWhere(
  229. 'authorPHID = %s AND draftKey = %s',
  230. $user->getPHID(),
  231. 'differential-comment-'.$revision->getID());
  232. if ($draft) {
  233. $draft = $draft->getDraft();
  234. } else {
  235. $draft = null;
  236. }
  237. $comment_form = new DifferentialAddCommentView();
  238. $comment_form->setRevision($revision);
  239. $comment_form->setActions($this->getRevisionCommentActions($revision));
  240. $comment_form->setActionURI('/differential/comment/save/');
  241. $comment_form->setUser($user);
  242. $comment_form->setDraft($draft);
  243. }
  244. $pane_id = celerity_generate_unique_node_id();
  245. Javelin::initBehavior(
  246. 'differential-keyboard-navigation',
  247. array(
  248. 'haunt' => $pane_id,
  249. ));
  250. $page_pane = id(new DifferentialPrimaryPaneView())
  251. ->setLineWidthFromChangesets($changesets)
  252. ->setID($pane_id)
  253. ->appendChild($reviewer_warning)
  254. ->appendChild(
  255. $revision_detail->render().
  256. $comment_view->render().
  257. $diff_history->render().
  258. $warning.
  259. $local_view->render().
  260. $toc_view->render().
  261. $changeset_view->render());
  262. if ($comment_form) {
  263. $page_pane->appendChild($comment_form->render());
  264. }
  265. return $this->buildStandardPageResponse(
  266. $page_pane,
  267. array(
  268. 'title' => 'D'.$revision->getID().' '.$revision->getTitle(),
  269. ));
  270. }
  271. private function getImplicitComments(DifferentialRevision $revision) {
  272. $template = new DifferentialComment();
  273. $template->setAuthorPHID($revision->getAuthorPHID());
  274. $template->setRevisionID($revision->getID());
  275. $template->setDateCreated($revision->getDateCreated());
  276. $comments = array();
  277. if (strlen($revision->getSummary())) {
  278. $summary_comment = clone $template;
  279. $summary_comment->setContent($revision->getSummary());
  280. $summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE);
  281. $comments[] = $summary_comment;
  282. }
  283. if (strlen($revision->getTestPlan())) {
  284. $testplan_comment = clone $template;
  285. $testplan_comment->setContent($revision->getTestPlan());
  286. $testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN);
  287. $comments[] = $testplan_comment;
  288. }
  289. return $comments;
  290. }
  291. private function getRevisionActions(DifferentialRevision $revision) {
  292. $viewer_phid = $this->getRequest()->getUser()->getPHID();
  293. $viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid);
  294. $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
  295. $viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs());
  296. $viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn();
  297. $status = $revision->getStatus();
  298. $revision_id = $revision->getID();
  299. $revision_phid = $revision->getPHID();
  300. $links = array();
  301. if ($viewer_is_owner) {
  302. $links[] = array(
  303. 'class' => 'revision-edit',
  304. 'href' => "/differential/revision/edit/{$revision_id}/",
  305. 'name' => 'Edit Revision',
  306. );
  307. }
  308. if (!$viewer_is_anonymous) {
  309. if (!$viewer_is_owner && !$viewer_is_reviewer) {
  310. $action = $viewer_is_cc ? 'rem' : 'add';
  311. $links[] = array(
  312. 'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add',
  313. 'href' => "/differential/subscribe/{$action}/{$revision_id}/",
  314. 'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe',
  315. 'instant' => true,
  316. );
  317. } else {
  318. $links[] = array(
  319. 'class' => 'subscribe-rem unavailable',
  320. 'name' => 'Automatically Subscribed',
  321. );
  322. }
  323. require_celerity_resource('phabricator-object-selector-css');
  324. require_celerity_resource('javelin-behavior-phabricator-object-selector');
  325. $links[] = array(
  326. 'class' => 'action-dependencies',
  327. 'name' => 'Edit Dependencies',
  328. 'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
  329. 'sigil' => 'workflow',
  330. );
  331. if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
  332. $links[] = array(
  333. 'class' => 'attach-maniphest',
  334. 'name' => 'Edit Maniphest Tasks',
  335. 'href' => "/search/attach/{$revision_phid}/TASK/",
  336. 'sigil' => 'workflow',
  337. );
  338. }
  339. $links[] = array(
  340. 'class' => 'transcripts-metamta',
  341. 'name' => 'MetaMTA Transcripts',
  342. 'href' => "/mail/?phid={$revision_phid}",
  343. );
  344. $links[] = array(
  345. 'class' => 'transcripts-herald',
  346. 'name' => 'Herald Transcripts',
  347. 'href' => "/herald/transcript/?phid={$revision_phid}",
  348. );
  349. }
  350. return $links;
  351. }
  352. private function getRevisionCommentActions(DifferentialRevision $revision) {
  353. $actions = array(
  354. DifferentialAction::ACTION_COMMENT => true,
  355. );
  356. $admin_actions = array();
  357. $viewer = $this->getRequest()->getUser();
  358. $viewer_phid = $viewer->getPHID();
  359. $viewer_is_admin = $viewer->getIsAdmin();
  360. $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
  361. $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
  362. $viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy());
  363. if ($viewer_is_owner) {
  364. switch ($revision->getStatus()) {
  365. case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
  366. $actions[DifferentialAction::ACTION_ABANDON] = true;
  367. $actions[DifferentialAction::ACTION_RETHINK] = true;
  368. break;
  369. case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
  370. $actions[DifferentialAction::ACTION_ABANDON] = true;
  371. $actions[DifferentialAction::ACTION_REQUEST] = true;
  372. break;
  373. case ArcanistDifferentialRevisionStatus::ACCEPTED:
  374. $actions[DifferentialAction::ACTION_ABANDON] = true;
  375. $actions[DifferentialAction::ACTION_REQUEST] = true;
  376. $actions[DifferentialAction::ACTION_RETHINK] = true;
  377. break;
  378. case ArcanistDifferentialRevisionStatus::COMMITTED:
  379. break;
  380. case ArcanistDifferentialRevisionStatus::ABANDONED:
  381. $actions[DifferentialAction::ACTION_RECLAIM] = true;
  382. break;
  383. }
  384. } else {
  385. switch ($revision->getStatus()) {
  386. case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
  387. $admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
  388. $actions[DifferentialAction::ACTION_ACCEPT] = true;
  389. $actions[DifferentialAction::ACTION_REJECT] = true;
  390. $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
  391. break;
  392. case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
  393. $admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
  394. $actions[DifferentialAction::ACTION_ACCEPT] = true;
  395. $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
  396. break;
  397. case ArcanistDifferentialRevisionStatus::ACCEPTED:
  398. $admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
  399. $actions[DifferentialAction::ACTION_REJECT] = true;
  400. $actions[DifferentialAction::ACTION_RESIGN] =
  401. $viewer_is_reviewer && !$viewer_did_accept;
  402. break;
  403. case ArcanistDifferentialRevisionStatus::COMMITTED:
  404. case ArcanistDifferentialRevisionStatus::ABANDONED:
  405. break;
  406. }
  407. }
  408. $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
  409. $actions[DifferentialAction::ACTION_ADDCCS] = true;
  410. $actions = array_keys(array_filter($actions));
  411. $admin_actions = array_keys(array_filter($admin_actions));
  412. $actions_dict = array();
  413. foreach ($actions as $action) {
  414. $actions_dict[$action] = DifferentialAction::getActionVerb($action);
  415. }
  416. foreach ($admin_actions as $action) {
  417. $actions_dict[$action] =
  418. '(Admin) ' . DifferentialAction::getActionVerb($action);
  419. }
  420. return $actions_dict;
  421. }
  422. private function loadInlineComments(array $comments, array &$changesets) {
  423. $inline_comments = array();
  424. $comment_ids = array_filter(mpull($comments, 'getID'));
  425. if (!$comment_ids) {
  426. return $inline_comments;
  427. }
  428. $inline_comments = id(new DifferentialInlineComment())
  429. ->loadAllWhere(
  430. 'commentID in (%Ld)',
  431. $comment_ids);
  432. $load_changesets = array();
  433. foreach ($inline_comments as $inline) {
  434. $changeset_id = $inline->getChangesetID();
  435. if (isset($changesets[$changeset_id])) {
  436. continue;
  437. }
  438. $load_changesets[$changeset_id] = true;
  439. }
  440. $more_changesets = array();
  441. if ($load_changesets) {
  442. $changeset_ids = array_keys($load_changesets);
  443. $more_changesets += id(new DifferentialChangeset())
  444. ->loadAllWhere(
  445. 'id IN (%Ld)',
  446. $changeset_ids);
  447. }
  448. if ($more_changesets) {
  449. $changesets += $more_changesets;
  450. $changesets = msort($changesets, 'getSortKey');
  451. }
  452. return $inline_comments;
  453. }
  454. private function loadChangesetsAndVsMap(array $diffs, $diff_vs, $target) {
  455. $load_ids = array();
  456. if ($diff_vs) {
  457. $load_ids[] = $diff_vs;
  458. }
  459. $load_ids[] = $target->getID();
  460. $raw_changesets = id(new DifferentialChangeset())
  461. ->loadAllWhere(
  462. 'diffID IN (%Ld)',
  463. $load_ids);
  464. $changeset_groups = mgroup($raw_changesets, 'getDiffID');
  465. $changesets = idx($changeset_groups, $target->getID(), array());
  466. $changesets = mpull($changesets, null, 'getID');
  467. $refs = array();
  468. foreach ($changesets as $changeset) {
  469. $refs[$changeset->getID()] = $changeset->getID();
  470. }
  471. $vs_map = array();
  472. if ($diff_vs) {
  473. $vs_changesets = idx($changeset_groups, $diff_vs, array());
  474. $vs_changesets = mpull($vs_changesets, null, 'getFilename');
  475. foreach ($changesets as $key => $changeset) {
  476. $file = $changeset->getFilename();
  477. if (isset($vs_changesets[$file])) {
  478. $vs_map[$changeset->getID()] = $vs_changesets[$file]->getID();
  479. $refs[$changeset->getID()] =
  480. $changeset->getID().'/'.$vs_changesets[$file]->getID();
  481. unset($vs_changesets[$file]);
  482. } else {
  483. $refs[$changeset->getID()] = $changeset->getID();
  484. }
  485. }
  486. foreach ($vs_changesets as $changeset) {
  487. $changesets[$changeset->getID()] = $changeset;
  488. $vs_map[$changeset->getID()] = -1;
  489. $refs[$changeset->getID()] = $changeset->getID().'/-1';
  490. }
  491. }
  492. $changesets = msort($changesets, 'getSortKey');
  493. return array($changesets, $vs_map, $refs);
  494. }
  495. private function loadAuxiliaryFieldsAndProperties(
  496. DifferentialRevision $revision,
  497. DifferentialDiff $diff,
  498. array $special_properties) {
  499. $aux_fields = DifferentialFieldSelector::newSelector()
  500. ->getFieldSpecifications();
  501. foreach ($aux_fields as $key => $aux_field) {
  502. if (!$aux_field->shouldAppearOnRevisionView()) {
  503. unset($aux_fields[$key]);
  504. }
  505. }
  506. $aux_fields = DifferentialAuxiliaryField::loadFromStorage(
  507. $revision,
  508. $aux_fields);
  509. $aux_props = array();
  510. foreach ($aux_fields as $key => $aux_field) {
  511. $aux_field->setDiff($diff);
  512. $aux_props[$key] = $aux_field->getRequiredDiffProperties();
  513. }
  514. $required_properties = array_mergev($aux_props);
  515. $required_properties = array_merge(
  516. $required_properties,
  517. $special_properties);
  518. $property_map = array();
  519. if ($required_properties) {
  520. $properties = id(new DifferentialDiffProperty())->loadAllWhere(
  521. 'diffID = %d AND name IN (%Ls)',
  522. $diff->getID(),
  523. $required_properties);
  524. $property_map = mpull($properties, 'getData', 'getName');
  525. }
  526. foreach ($aux_fields as $key => $aux_field) {
  527. // Give each field only the properties it specifically required, and
  528. // set 'null' for each requested key which we didn't actually load a
  529. // value for (otherwise, getDiffProperty() will throw).
  530. if ($aux_props[$key]) {
  531. $props = array_select_keys($property_map, $aux_props[$key]) +
  532. array_fill_keys($aux_props[$key], null);
  533. } else {
  534. $props = array();
  535. }
  536. $aux_field->setDiffProperties($props);
  537. }
  538. return array(
  539. $aux_fields,
  540. array_select_keys(
  541. $property_map,
  542. $special_properties));
  543. }
  544. private function buildSymbolIndexes(
  545. DifferentialDiff $target,
  546. PhabricatorRepositoryArcanistProject $arc_project,
  547. array $visible_changesets) {
  548. $engine = PhabricatorSyntaxHighlighter::newEngine();
  549. $langs = $arc_project->getSymbolIndexLanguages();
  550. if (!$langs) {
  551. return array();
  552. }
  553. $symbol_indexes = array();
  554. $project_phids = array_merge(
  555. array($arc_project->getPHID()),
  556. nonempty($arc_project->getSymbolIndexProjects(), array()));
  557. $indexed_langs = array_fill_keys($langs, true);
  558. foreach ($visible_changesets as $key => $changeset) {
  559. $lang = $engine->getLanguageFromFilename($changeset->getFilename());
  560. if (isset($indexed_langs[$lang])) {
  561. $symbol_indexes[$key] = array(
  562. 'lang' => $lang,
  563. 'projects' => $project_phids,
  564. );
  565. }
  566. }
  567. return $symbol_indexes;
  568. }
  569. }