private function validateDifferentialAction(DifferentialRevision $revision, $type, DifferentialTransaction $xaction, $action)
 {
     $author_phid = $revision->getAuthorPHID();
     $actor_phid = $this->getActingAsPHID();
     $actor_is_author = $author_phid == $actor_phid;
     $config_abandon_key = 'differential.always-allow-abandon';
     $always_allow_abandon = PhabricatorEnv::getEnvConfig($config_abandon_key);
     $config_close_key = 'differential.always-allow-close';
     $always_allow_close = PhabricatorEnv::getEnvConfig($config_close_key);
     $config_reopen_key = 'differential.allow-reopen';
     $allow_reopen = PhabricatorEnv::getEnvConfig($config_reopen_key);
     $config_self_accept_key = 'differential.allow-self-accept';
     $allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
     $revision_status = $revision->getStatus();
     $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
     $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED;
     $status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
     switch ($action) {
         case DifferentialAction::ACTION_ACCEPT:
             if ($actor_is_author && !$allow_self_accept) {
                 return pht('You can not accept this revision because you are the owner.');
             }
             if ($revision_status == $status_abandoned) {
                 return pht('You can not accept this revision because it has been ' . 'abandoned.');
             }
             if ($revision_status == $status_closed) {
                 return pht('You can not accept this revision because it has already been ' . 'closed.');
             }
             // TODO: It would be nice to make this generic at some point.
             $signatures = DifferentialRequiredSignaturesField::loadForRevision($revision);
             foreach ($signatures as $phid => $signed) {
                 if (!$signed) {
                     return pht('You can not accept this revision because the author has ' . 'not signed all of the required legal documents.');
                 }
             }
             break;
         case DifferentialAction::ACTION_REJECT:
             if ($actor_is_author) {
                 return pht('You can not request changes to your own revision.');
             }
             if ($revision_status == $status_abandoned) {
                 return pht('You can not request changes to this revision because it has been ' . 'abandoned.');
             }
             if ($revision_status == $status_closed) {
                 return pht('You can not request changes to this revision because it has ' . 'already been closed.');
             }
             break;
         case DifferentialAction::ACTION_RESIGN:
             // You can always resign from a revision if you're a reviewer. If you
             // aren't, this is a no-op rather than invalid.
             break;
         case DifferentialAction::ACTION_CLAIM:
             // You can claim a revision if you're not the owner. If you are, this
             // is a no-op rather than invalid.
             if ($revision_status == $status_closed) {
                 return pht('You can not commandeer this revision because it has already been ' . 'closed.');
             }
             break;
         case DifferentialAction::ACTION_ABANDON:
             if (!$actor_is_author && !$always_allow_abandon) {
                 return pht('You can not abandon this revision because you do not own it. ' . 'You can only abandon revisions you own.');
             }
             if ($revision_status == $status_closed) {
                 return pht('You can not abandon this revision because it has already been ' . 'closed.');
             }
             // NOTE: Abandons of already-abandoned revisions are treated as no-op
             // instead of invalid. Other abandons are OK.
             break;
         case DifferentialAction::ACTION_RECLAIM:
             if (!$actor_is_author) {
                 return pht('You can not reclaim this revision because you do not own ' . 'it. You can only reclaim revisions you own.');
             }
             if ($revision_status == $status_closed) {
                 return pht('You can not reclaim this revision because it has already been ' . 'closed.');
             }
             // NOTE: Reclaims of other non-abandoned revisions are treated as no-op
             // instead of invalid.
             break;
         case DifferentialAction::ACTION_REOPEN:
             if (!$allow_reopen) {
                 return pht('The reopen action is not enabled on this Phabricator install. ' . 'Adjust your configuration to enable it.');
             }
             // NOTE: If the revision is not closed, this is caught as a no-op
             // instead of an invalid transaction.
             break;
         case DifferentialAction::ACTION_RETHINK:
             if (!$actor_is_author) {
                 return pht('You can not plan changes to this revision because you do not ' . 'own it. To plan changes to a revision, you must be its owner.');
             }
             switch ($revision_status) {
                 case ArcanistDifferentialRevisionStatus::ACCEPTED:
                 case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
                 case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
                     // These are OK.
                     break;
                 case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
                     // Let this through, it's a no-op.
                     break;
                 case ArcanistDifferentialRevisionStatus::ABANDONED:
                     return pht('You can not plan changes to this revision because it has ' . 'been abandoned.');
                 case ArcanistDifferentialRevisionStatus::CLOSED:
                     return pht('You can not plan changes to this revision because it has ' . 'already been closed.');
                 default:
                     throw new Exception(pht('Encountered unexpected revision status ("%s") when ' . 'validating "%s" action.', $revision_status, $action));
             }
             break;
         case DifferentialAction::ACTION_REQUEST:
             if (!$actor_is_author) {
                 return pht('You can not request review of this revision because you do ' . 'not own it. To request review of a revision, you must be its ' . 'owner.');
             }
             switch ($revision_status) {
                 case ArcanistDifferentialRevisionStatus::ACCEPTED:
                 case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
                 case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
                     // These are OK.
                     break;
                 case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
                     // This will be caught as "no effect" later on.
                     break;
                 case ArcanistDifferentialRevisionStatus::ABANDONED:
                     return pht('You can not request review of this revision because it has ' . 'been abandoned. Instead, reclaim it.');
                 case ArcanistDifferentialRevisionStatus::CLOSED:
                     return pht('You can not request review of this revision because it has ' . 'already been closed.');
                 default:
                     throw new Exception(pht('Encountered unexpected revision status ("%s") when ' . 'validating "%s" action.', $revision_status, $action));
             }
             break;
         case DifferentialAction::ACTION_CLOSE:
             // We force revisions closed when we discover a corresponding commit.
             // In this case, revisions are allowed to transition to closed from
             // any state. This is an automated action taken by the daemons.
             if (!$this->getIsCloseByCommit()) {
                 if (!$actor_is_author && !$always_allow_close) {
                     return pht('You can not close this revision because you do not own it. To ' . 'close a revision, you must be its owner.');
                 }
                 if ($revision_status != $status_accepted) {
                     return pht('You can not close this revision because it has not been ' . 'accepted. You can only close accepted revisions.');
                 }
             }
             break;
     }
     return null;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $viewer_is_anonymous = !$user->isLoggedIn();
     $revision = id(new DifferentialRevisionQuery())->withIDs(array($this->revisionID))->setViewer($request->getUser())->needRelationships(true)->needReviewerStatus(true)->needReviewerAuthority(true)->executeOne();
     if (!$revision) {
         return new Aphront404Response();
     }
     $diffs = id(new DifferentialDiffQuery())->setViewer($request->getUser())->withRevisionIDs(array($this->revisionID))->execute();
     $diffs = array_reverse($diffs, $preserve_keys = true);
     if (!$diffs) {
         throw new Exception(pht('This revision has no diffs. Something has gone quite wrong.'));
     }
     $revision->attachActiveDiff(last($diffs));
     $diff_vs = $request->getInt('vs');
     $target_id = $request->getInt('id');
     $target = idx($diffs, $target_id, end($diffs));
     $target_manual = $target;
     if (!$target_id) {
         foreach ($diffs as $diff) {
             if ($diff->getCreationMethod() != 'commit') {
                 $target_manual = $diff;
             }
         }
     }
     if (empty($diffs[$diff_vs])) {
         $diff_vs = null;
     }
     $repository = null;
     $repository_phid = $target->getRepositoryPHID();
     if ($repository_phid) {
         if ($repository_phid == $revision->getRepositoryPHID()) {
             $repository = $revision->getRepository();
         } else {
             $repository = id(new PhabricatorRepositoryQuery())->setViewer($user)->withPHIDs(array($repository_phid))->executeOne();
         }
     }
     list($changesets, $vs_map, $vs_changesets, $rendering_references) = $this->loadChangesetsAndVsMap($target, idx($diffs, $diff_vs), $repository);
     if ($request->getExists('download')) {
         return $this->buildRawDiffResponse($revision, $changesets, $vs_changesets, $vs_map, $repository);
     }
     $map = $vs_map;
     if (!$map) {
         $map = array_fill_keys(array_keys($changesets), 0);
     }
     $old_ids = array();
     $new_ids = array();
     foreach ($map as $id => $vs) {
         if ($vs <= 0) {
             $old_ids[] = $id;
             $new_ids[] = $id;
         } else {
             $new_ids[] = $id;
             $new_ids[] = $vs;
         }
     }
     $props = id(new DifferentialDiffProperty())->loadAllWhere('diffID = %d', $target_manual->getID());
     $props = mpull($props, 'getData', 'getName');
     $object_phids = array_merge($revision->getReviewers(), $revision->getCCPHIDs(), $revision->loadCommitPHIDs(), array($revision->getAuthorPHID(), $user->getPHID()));
     foreach ($revision->getAttached() as $type => $phids) {
         foreach ($phids as $phid => $info) {
             $object_phids[] = $phid;
         }
     }
     $field_list = PhabricatorCustomField::getObjectFields($revision, PhabricatorCustomField::ROLE_VIEW);
     $field_list->setViewer($user);
     $field_list->readFieldsFromStorage($revision);
     $warning_handle_map = array();
     foreach ($field_list->getFields() as $key => $field) {
         $req = $field->getRequiredHandlePHIDsForRevisionHeaderWarnings();
         foreach ($req as $phid) {
             $warning_handle_map[$key][] = $phid;
             $object_phids[] = $phid;
         }
     }
     $handles = $this->loadViewerHandles($object_phids);
     $request_uri = $request->getRequestURI();
     $limit = 100;
     $large = $request->getStr('large');
     if (count($changesets) > $limit && !$large) {
         $count = count($changesets);
         $warning = new PHUIInfoView();
         $warning->setTitle(pht('Very Large Diff'));
         $warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
         $warning->appendChild(hsprintf('%s <strong>%s</strong>', pht('This diff is very large and affects %s files. ' . 'You may load each file individually or ', new PhutilNumber($count)), phutil_tag('a', array('class' => 'button grey', 'href' => $request_uri->alter('large', 'true')->setFragment('toc')), pht('Show All Files Inline'))));
         $warning = $warning->render();
         $old = array_select_keys($changesets, $old_ids);
         $new = array_select_keys($changesets, $new_ids);
         $query = id(new DifferentialInlineCommentQuery())->setViewer($user)->needHidden(true)->withRevisionPHIDs(array($revision->getPHID()));
         $inlines = $query->execute();
         $inlines = $query->adjustInlinesForChangesets($inlines, $old, $new, $revision);
         $visible_changesets = array();
         foreach ($inlines as $inline) {
             $changeset_id = $inline->getChangesetID();
             if (isset($changesets[$changeset_id])) {
                 $visible_changesets[$changeset_id] = $changesets[$changeset_id];
             }
         }
     } else {
         $warning = null;
         $visible_changesets = $changesets;
     }
     $commit_hashes = mpull($diffs, 'getSourceControlBaseRevision');
     $local_commits = idx($props, 'local:commits', array());
     foreach ($local_commits as $local_commit) {
         $commit_hashes[] = idx($local_commit, 'tree');
         $commit_hashes[] = idx($local_commit, 'local');
     }
     $commit_hashes = array_unique(array_filter($commit_hashes));
     if ($commit_hashes) {
         $commits_for_links = id(new DiffusionCommitQuery())->setViewer($user)->withIdentifiers($commit_hashes)->execute();
         $commits_for_links = mpull($commits_for_links, null, 'getCommitIdentifier');
     } else {
         $commits_for_links = array();
     }
     $revision_detail = id(new DifferentialRevisionDetailView())->setUser($user)->setRevision($revision)->setDiff(end($diffs))->setCustomFields($field_list)->setURI($request->getRequestURI());
     $actions = $this->getRevisionActions($revision);
     $whitespace = $request->getStr('whitespace', DifferentialChangesetParser::WHITESPACE_IGNORE_MOST);
     $repository = $revision->getRepository();
     if ($repository) {
         $symbol_indexes = $this->buildSymbolIndexes($repository, $visible_changesets);
     } else {
         $symbol_indexes = array();
     }
     $revision_detail->setActions($actions);
     $revision_detail->setUser($user);
     $revision_detail_box = $revision_detail->render();
     $revision_warnings = $this->buildRevisionWarnings($revision, $field_list, $warning_handle_map, $handles);
     if ($revision_warnings) {
         $revision_warnings = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors($revision_warnings);
         $revision_detail_box->setInfoView($revision_warnings);
     }
     $detail_diffs = array_select_keys($diffs, array($diff_vs, $target->getID()));
     $detail_diffs = mpull($detail_diffs, null, 'getPHID');
     $buildables = id(new HarbormasterBuildableQuery())->setViewer($user)->withBuildablePHIDs(array_keys($detail_diffs))->withManualBuildables(false)->needBuilds(true)->needTargets(true)->execute();
     $buildables = mpull($buildables, null, 'getBuildablePHID');
     foreach ($detail_diffs as $diff_phid => $detail_diff) {
         $detail_diff->attachBuildable(idx($buildables, $diff_phid));
     }
     $diff_detail_box = $this->buildDiffDetailView($detail_diffs, $revision, $field_list);
     $comment_view = $this->buildTransactions($revision, $diff_vs ? $diffs[$diff_vs] : $target, $target, $old_ids, $new_ids);
     if (!$viewer_is_anonymous) {
         $comment_view->setQuoteRef('D' . $revision->getID());
         $comment_view->setQuoteTargetID('comment-content');
     }
     $wrap_id = celerity_generate_unique_node_id();
     $comment_view = phutil_tag('div', array('id' => $wrap_id), $comment_view);
     $changeset_view = new DifferentialChangesetListView();
     $changeset_view->setChangesets($changesets);
     $changeset_view->setVisibleChangesets($visible_changesets);
     if (!$viewer_is_anonymous) {
         $changeset_view->setInlineCommentControllerURI('/differential/comment/inline/edit/' . $revision->getID() . '/');
     }
     $changeset_view->setStandaloneURI('/differential/changeset/');
     $changeset_view->setRawFileURIs('/differential/changeset/?view=old', '/differential/changeset/?view=new');
     $changeset_view->setUser($user);
     $changeset_view->setDiff($target);
     $changeset_view->setRenderingReferences($rendering_references);
     $changeset_view->setVsMap($vs_map);
     $changeset_view->setWhitespace($whitespace);
     if ($repository) {
         $changeset_view->setRepository($repository);
     }
     $changeset_view->setSymbolIndexes($symbol_indexes);
     $changeset_view->setTitle(pht('Diff %s', $target->getID()));
     $diff_history = id(new DifferentialRevisionUpdateHistoryView())->setUser($user)->setDiffs($diffs)->setSelectedVersusDiffID($diff_vs)->setSelectedDiffID($target->getID())->setSelectedWhitespace($whitespace)->setCommitsForLinks($commits_for_links);
     $local_view = id(new DifferentialLocalCommitsView())->setUser($user)->setLocalCommits(idx($props, 'local:commits'))->setCommitsForLinks($commits_for_links);
     if ($repository) {
         $other_revisions = $this->loadOtherRevisions($changesets, $target, $repository);
     } else {
         $other_revisions = array();
     }
     $other_view = null;
     if ($other_revisions) {
         $other_view = $this->renderOtherRevisions($other_revisions);
     }
     $toc_view = $this->buildTableOfContents($changesets, $visible_changesets, $target->loadCoverageMap($user));
     $comment_form = null;
     if (!$viewer_is_anonymous) {
         $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), 'differential-comment-' . $revision->getID());
         $reviewers = array();
         $ccs = array();
         if ($draft) {
             $reviewers = idx($draft->getMetadata(), 'reviewers', array());
             $ccs = idx($draft->getMetadata(), 'ccs', array());
             if ($reviewers || $ccs) {
                 $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
                 $reviewers = array_select_keys($handles, $reviewers);
                 $ccs = array_select_keys($handles, $ccs);
             }
         }
         $comment_form = new DifferentialAddCommentView();
         $comment_form->setRevision($revision);
         $review_warnings = array();
         foreach ($field_list->getFields() as $field) {
             $review_warnings[] = $field->getWarningsForDetailView();
         }
         $review_warnings = array_mergev($review_warnings);
         if ($review_warnings) {
             $review_warnings_panel = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors($review_warnings);
             $comment_form->setInfoView($review_warnings_panel);
         }
         $comment_form->setActions($this->getRevisionCommentActions($revision));
         $action_uri = $this->getApplicationURI('comment/save/' . $revision->getID() . '/');
         $comment_form->setActionURI($action_uri);
         $comment_form->setUser($user);
         $comment_form->setDraft($draft);
         $comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'));
         $comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
         // TODO: This just makes the "Z" key work. Generalize this and remove
         // it at some point.
         $comment_form = phutil_tag('div', array('class' => 'differential-add-comment-panel'), $comment_form);
     }
     $pane_id = celerity_generate_unique_node_id();
     Javelin::initBehavior('differential-keyboard-navigation', array('haunt' => $pane_id));
     Javelin::initBehavior('differential-user-select');
     $page_pane = id(new DifferentialPrimaryPaneView())->setID($pane_id)->appendChild($comment_view);
     $signatures = DifferentialRequiredSignaturesField::loadForRevision($revision);
     $missing_signatures = false;
     foreach ($signatures as $phid => $signed) {
         if (!$signed) {
             $missing_signatures = true;
         }
     }
     if ($missing_signatures) {
         $signature_message = id(new PHUIInfoView())->setErrors(array(array(phutil_tag('strong', array(), pht('Content Hidden:')), ' ', pht('The content of this revision is hidden until the author has ' . 'signed all of the required legal agreements.'))));
         $page_pane->appendChild($signature_message);
     } else {
         $page_pane->appendChild(array($diff_history, $warning, $local_view, $toc_view, $other_view, $changeset_view));
     }
     if ($comment_form) {
         $page_pane->appendChild($comment_form);
     } else {
         // TODO: For now, just use this to get "Login to Comment".
         $page_pane->appendChild(id(new PhabricatorApplicationTransactionCommentView())->setUser($user)->setRequestURI($request->getRequestURI()));
     }
     $object_id = 'D' . $revision->getID();
     $content = array($revision_detail_box, $diff_detail_box, $page_pane);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($object_id, '/' . $object_id);
     $prefs = $user->loadPreferences();
     $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
     if ($prefs->getPreference($pref_filetree)) {
         $collapsed = $prefs->getPreference(PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED, false);
         $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())->setTitle('D' . $revision->getID())->setBaseURI(new PhutilURI('/D' . $revision->getID()))->setCollapsed((bool) $collapsed)->build($changesets);
         $nav->appendChild($content);
         $nav->setCrumbs($crumbs);
         $content = $nav;
     } else {
         array_unshift($content, $crumbs);
     }
     return $this->buildApplicationPage($content, array('title' => $object_id . ' ' . $revision->getTitle(), 'pageObjects' => array($revision->getPHID())));
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $this->revisionID = $request->getURIData('id');
     $viewer_is_anonymous = !$viewer->isLoggedIn();
     $revision = id(new DifferentialRevisionQuery())->withIDs(array($this->revisionID))->setViewer($request->getUser())->needRelationships(true)->needReviewerStatus(true)->needReviewerAuthority(true)->executeOne();
     if (!$revision) {
         return new Aphront404Response();
     }
     $diffs = id(new DifferentialDiffQuery())->setViewer($request->getUser())->withRevisionIDs(array($this->revisionID))->execute();
     $diffs = array_reverse($diffs, $preserve_keys = true);
     if (!$diffs) {
         throw new Exception(pht('This revision has no diffs. Something has gone quite wrong.'));
     }
     $revision->attachActiveDiff(last($diffs));
     $diff_vs = $request->getInt('vs');
     $target_id = $request->getInt('id');
     $target = idx($diffs, $target_id, end($diffs));
     $target_manual = $target;
     if (!$target_id) {
         foreach ($diffs as $diff) {
             if ($diff->getCreationMethod() != 'commit') {
                 $target_manual = $diff;
             }
         }
     }
     if (empty($diffs[$diff_vs])) {
         $diff_vs = null;
     }
     $repository = null;
     $repository_phid = $target->getRepositoryPHID();
     if ($repository_phid) {
         if ($repository_phid == $revision->getRepositoryPHID()) {
             $repository = $revision->getRepository();
         } else {
             $repository = id(new PhabricatorRepositoryQuery())->setViewer($viewer)->withPHIDs(array($repository_phid))->executeOne();
         }
     }
     list($changesets, $vs_map, $vs_changesets, $rendering_references) = $this->loadChangesetsAndVsMap($target, idx($diffs, $diff_vs), $repository);
     if ($request->getExists('download')) {
         return $this->buildRawDiffResponse($revision, $changesets, $vs_changesets, $vs_map, $repository);
     }
     $map = $vs_map;
     if (!$map) {
         $map = array_fill_keys(array_keys($changesets), 0);
     }
     $old_ids = array();
     $new_ids = array();
     foreach ($map as $id => $vs) {
         if ($vs <= 0) {
             $old_ids[] = $id;
             $new_ids[] = $id;
         } else {
             $new_ids[] = $id;
             $new_ids[] = $vs;
         }
     }
     $this->loadDiffProperties($diffs);
     $props = $target_manual->getDiffProperties();
     $object_phids = array_merge($revision->getReviewers(), $revision->getCCPHIDs(), $revision->loadCommitPHIDs(), array($revision->getAuthorPHID(), $viewer->getPHID()));
     foreach ($revision->getAttached() as $type => $phids) {
         foreach ($phids as $phid => $info) {
             $object_phids[] = $phid;
         }
     }
     $field_list = PhabricatorCustomField::getObjectFields($revision, PhabricatorCustomField::ROLE_VIEW);
     $field_list->setViewer($viewer);
     $field_list->readFieldsFromStorage($revision);
     $warning_handle_map = array();
     foreach ($field_list->getFields() as $key => $field) {
         $req = $field->getRequiredHandlePHIDsForRevisionHeaderWarnings();
         foreach ($req as $phid) {
             $warning_handle_map[$key][] = $phid;
             $object_phids[] = $phid;
         }
     }
     $handles = $this->loadViewerHandles($object_phids);
     $request_uri = $request->getRequestURI();
     $limit = 100;
     $large = $request->getStr('large');
     if (count($changesets) > $limit && !$large) {
         $count = count($changesets);
         $warning = new PHUIInfoView();
         $warning->setTitle(pht('Very Large Diff'));
         $warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
         $warning->appendChild(hsprintf('%s <strong>%s</strong>', pht('This diff is very large and affects %s files. ' . 'You may load each file individually or ', new PhutilNumber($count)), phutil_tag('a', array('class' => 'button grey', 'href' => $request_uri->alter('large', 'true')->setFragment('toc')), pht('Show All Files Inline'))));
         $warning = $warning->render();
         $old = array_select_keys($changesets, $old_ids);
         $new = array_select_keys($changesets, $new_ids);
         $query = id(new DifferentialInlineCommentQuery())->setViewer($viewer)->needHidden(true)->withRevisionPHIDs(array($revision->getPHID()));
         $inlines = $query->execute();
         $inlines = $query->adjustInlinesForChangesets($inlines, $old, $new, $revision);
         $visible_changesets = array();
         foreach ($inlines as $inline) {
             $changeset_id = $inline->getChangesetID();
             if (isset($changesets[$changeset_id])) {
                 $visible_changesets[$changeset_id] = $changesets[$changeset_id];
             }
         }
     } else {
         $warning = null;
         $visible_changesets = $changesets;
     }
     $commit_hashes = mpull($diffs, 'getSourceControlBaseRevision');
     $local_commits = idx($props, 'local:commits', array());
     foreach ($local_commits as $local_commit) {
         $commit_hashes[] = idx($local_commit, 'tree');
         $commit_hashes[] = idx($local_commit, 'local');
     }
     $commit_hashes = array_unique(array_filter($commit_hashes));
     if ($commit_hashes) {
         $commits_for_links = id(new DiffusionCommitQuery())->setViewer($viewer)->withIdentifiers($commit_hashes)->execute();
         $commits_for_links = mpull($commits_for_links, null, 'getCommitIdentifier');
     } else {
         $commits_for_links = array();
     }
     $header = $this->buildHeader($revision);
     $subheader = $this->buildSubheaderView($revision);
     $details = $this->buildDetails($revision, $field_list);
     $curtain = $this->buildCurtain($revision);
     $whitespace = $request->getStr('whitespace', DifferentialChangesetParser::WHITESPACE_IGNORE_MOST);
     $repository = $revision->getRepository();
     if ($repository) {
         $symbol_indexes = $this->buildSymbolIndexes($repository, $visible_changesets);
     } else {
         $symbol_indexes = array();
     }
     $revision_warnings = $this->buildRevisionWarnings($revision, $field_list, $warning_handle_map, $handles);
     $info_view = null;
     if ($revision_warnings) {
         $info_view = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors($revision_warnings);
     }
     $detail_diffs = array_select_keys($diffs, array($diff_vs, $target->getID()));
     $detail_diffs = mpull($detail_diffs, null, 'getPHID');
     $this->loadHarbormasterData($detail_diffs);
     $diff_detail_box = $this->buildDiffDetailView($detail_diffs, $revision, $field_list);
     $unit_box = $this->buildUnitMessagesView($target, $revision);
     $comment_view = $this->buildTransactions($revision, $diff_vs ? $diffs[$diff_vs] : $target, $target, $old_ids, $new_ids);
     if (!$viewer_is_anonymous) {
         $comment_view->setQuoteRef('D' . $revision->getID());
         $comment_view->setQuoteTargetID('comment-content');
     }
     $changeset_view = id(new DifferentialChangesetListView())->setChangesets($changesets)->setVisibleChangesets($visible_changesets)->setStandaloneURI('/differential/changeset/')->setRawFileURIs('/differential/changeset/?view=old', '/differential/changeset/?view=new')->setUser($viewer)->setDiff($target)->setRenderingReferences($rendering_references)->setVsMap($vs_map)->setWhitespace($whitespace)->setSymbolIndexes($symbol_indexes)->setTitle(pht('Diff %s', $target->getID()))->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
     if ($repository) {
         $changeset_view->setRepository($repository);
     }
     if (!$viewer_is_anonymous) {
         $changeset_view->setInlineCommentControllerURI('/differential/comment/inline/edit/' . $revision->getID() . '/');
     }
     $history = id(new DifferentialRevisionUpdateHistoryView())->setUser($viewer)->setDiffs($diffs)->setSelectedVersusDiffID($diff_vs)->setSelectedDiffID($target->getID())->setSelectedWhitespace($whitespace)->setCommitsForLinks($commits_for_links);
     $local_table = id(new DifferentialLocalCommitsView())->setUser($viewer)->setLocalCommits(idx($props, 'local:commits'))->setCommitsForLinks($commits_for_links);
     if ($repository) {
         $other_revisions = $this->loadOtherRevisions($changesets, $target, $repository);
     } else {
         $other_revisions = array();
     }
     $other_view = null;
     if ($other_revisions) {
         $other_view = $this->renderOtherRevisions($other_revisions);
     }
     $toc_view = $this->buildTableOfContents($changesets, $visible_changesets, $target->loadCoverageMap($viewer));
     $tab_group = id(new PHUITabGroupView())->addTab(id(new PHUITabView())->setName(pht('Files'))->setKey('files')->appendChild($toc_view))->addTab(id(new PHUITabView())->setName(pht('History'))->setKey('history')->appendChild($history))->addTab(id(new PHUITabView())->setName(pht('Commits'))->setKey('commits')->appendChild($local_table));
     $stack_graph = id(new DifferentialRevisionGraph())->setViewer($viewer)->setSeedPHID($revision->getPHID())->setLoadEntireGraph(true)->loadGraph();
     if (!$stack_graph->isEmpty()) {
         $stack_table = $stack_graph->newGraphTable();
         $parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST;
         $reachable = $stack_graph->getReachableObjects($parent_type);
         foreach ($reachable as $key => $reachable_revision) {
             if ($reachable_revision->isClosed()) {
                 unset($reachable[$key]);
             }
         }
         if ($reachable) {
             $stack_name = pht('Stack (%s Open)', phutil_count($reachable));
             $stack_color = PHUIListItemView::STATUS_FAIL;
         } else {
             $stack_name = pht('Stack');
             $stack_color = null;
         }
         $tab_group->addTab(id(new PHUITabView())->setName($stack_name)->setKey('stack')->setColor($stack_color)->appendChild($stack_table));
     }
     if ($other_view) {
         $tab_group->addTab(id(new PHUITabView())->setName(pht('Similar'))->setKey('similar')->appendChild($other_view));
     }
     $tab_view = id(new PHUIObjectBoxView())->setHeaderText(pht('Revision Contents'))->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->addTabGroup($tab_group);
     $comment_form = null;
     if (!$viewer_is_anonymous) {
         $comment_form = $this->buildCommentForm($revision, $field_list);
     }
     $signatures = DifferentialRequiredSignaturesField::loadForRevision($revision);
     $missing_signatures = false;
     foreach ($signatures as $phid => $signed) {
         if (!$signed) {
             $missing_signatures = true;
         }
     }
     $footer = array();
     $signature_message = null;
     if ($missing_signatures) {
         $signature_message = id(new PHUIInfoView())->setTitle(pht('Content Hidden'))->appendChild(pht('The content of this revision is hidden until the author has ' . 'signed all of the required legal agreements.'));
     } else {
         $anchor = id(new PhabricatorAnchorView())->setAnchorName('toc')->setNavigationMarker(true);
         $footer[] = array($anchor, $warning, $tab_view, $changeset_view);
     }
     if ($comment_form) {
         $footer[] = $comment_form;
     } else {
         // TODO: For now, just use this to get "Login to Comment".
         $footer[] = id(new PhabricatorApplicationTransactionCommentView())->setUser($viewer)->setRequestURI($request->getRequestURI());
     }
     $object_id = 'D' . $revision->getID();
     $operations_box = $this->buildOperationsBox($revision);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($object_id, '/' . $object_id);
     $crumbs->setBorder(true);
     $filetree_on = $viewer->compareUserSetting(PhabricatorShowFiletreeSetting::SETTINGKEY, PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE);
     $nav = null;
     if ($filetree_on) {
         $collapsed_key = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
         $collapsed_value = $viewer->getUserSetting($collapsed_key);
         $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())->setTitle('D' . $revision->getID())->setBaseURI(new PhutilURI('/D' . $revision->getID()))->setCollapsed((bool) $collapsed_value)->build($changesets);
     }
     // Haunt Mode
     $pane_id = celerity_generate_unique_node_id();
     Javelin::initBehavior('differential-keyboard-navigation', array('haunt' => $pane_id));
     Javelin::initBehavior('differential-user-select');
     $view = id(new PHUITwoColumnView())->setHeader($header)->setSubheader($subheader)->setCurtain($curtain)->setID($pane_id)->setMainColumn(array($operations_box, $info_view, $details, $diff_detail_box, $unit_box, $comment_view, $signature_message))->setFooter($footer);
     $page = $this->newPage()->setTitle($object_id . ' ' . $revision->getTitle())->setCrumbs($crumbs)->setPageObjectPHIDs(array($revision->getPHID()))->appendChild($view);
     if ($nav) {
         $page->setNavigation($nav);
     }
     return $page;
 }