Path: blob/master/src/applications/differential/controller/DifferentialChangesetViewController.php
12256 views
<?php12final class DifferentialChangesetViewController extends DifferentialController {34public function shouldAllowPublic() {5return true;6}78public function handleRequest(AphrontRequest $request) {9$viewer = $this->getViewer();1011$rendering_reference = $request->getStr('ref');12$parts = explode('/', $rendering_reference);13if (count($parts) == 2) {14list($id, $vs) = $parts;15} else {16$id = $parts[0];17$vs = 0;18}1920$id = (int)$id;21$vs = (int)$vs;2223$load_ids = array($id);24if ($vs && ($vs != -1)) {25$load_ids[] = $vs;26}2728$changesets = id(new DifferentialChangesetQuery())29->setViewer($viewer)30->withIDs($load_ids)31->needHunks(true)32->execute();33$changesets = mpull($changesets, null, 'getID');3435$changeset = idx($changesets, $id);36if (!$changeset) {37return new Aphront404Response();38}3940$vs_changeset = null;41if ($vs && ($vs != -1)) {42$vs_changeset = idx($changesets, $vs);43if (!$vs_changeset) {44return new Aphront404Response();45}46}4748$view = $request->getStr('view');49if ($view) {50$phid = idx($changeset->getMetadata(), "$view:binary-phid");51if ($phid) {52return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/");53}54switch ($view) {55case 'new':56return $this->buildRawFileResponse($changeset, $is_new = true);57case 'old':58if ($vs_changeset) {59return $this->buildRawFileResponse($vs_changeset, $is_new = true);60}61return $this->buildRawFileResponse($changeset, $is_new = false);62default:63return new Aphront400Response();64}65}6667$old = array();68$new = array();69if (!$vs) {70$right = $changeset;71$left = null;7273$right_source = $right->getID();74$right_new = true;75$left_source = $right->getID();76$left_new = false;7778$render_cache_key = $right->getID();7980$old[] = $changeset;81$new[] = $changeset;82} else if ($vs == -1) {83$right = null;84$left = $changeset;8586$right_source = $left->getID();87$right_new = false;88$left_source = $left->getID();89$left_new = true;9091$render_cache_key = null;9293$old[] = $changeset;94$new[] = $changeset;95} else {96$right = $changeset;97$left = $vs_changeset;9899$right_source = $right->getID();100$right_new = true;101$left_source = $left->getID();102$left_new = true;103104$render_cache_key = null;105106$new[] = $left;107$new[] = $right;108}109110if ($left) {111$changeset = $left->newComparisonChangeset($right);112}113114if ($left_new || $right_new) {115$diff_map = array();116if ($left) {117$diff_map[] = $left->getDiff();118}119if ($right) {120$diff_map[] = $right->getDiff();121}122$diff_map = mpull($diff_map, null, 'getPHID');123124$buildables = id(new HarbormasterBuildableQuery())125->setViewer($viewer)126->withBuildablePHIDs(array_keys($diff_map))127->withManualBuildables(false)128->needBuilds(true)129->needTargets(true)130->execute();131$buildables = mpull($buildables, null, 'getBuildablePHID');132foreach ($diff_map as $diff_phid => $changeset_diff) {133$changeset_diff->attachBuildable(idx($buildables, $diff_phid));134}135}136137$coverage = null;138if ($right_new) {139$coverage = $this->loadCoverage($right);140}141142$spec = $request->getStr('range');143list($range_s, $range_e, $mask) =144DifferentialChangesetParser::parseRangeSpecification($spec);145146$diff = $changeset->getDiff();147$revision_id = $diff->getRevisionID();148149$can_mark = false;150$object_owner_phid = null;151$revision = null;152if ($revision_id) {153$revision = id(new DifferentialRevisionQuery())154->setViewer($viewer)155->withIDs(array($revision_id))156->executeOne();157if ($revision) {158$can_mark = ($revision->getAuthorPHID() == $viewer->getPHID());159$object_owner_phid = $revision->getAuthorPHID();160}161}162163if ($revision) {164$container_phid = $revision->getPHID();165} else {166$container_phid = $diff->getPHID();167}168169$viewstate_engine = id(new PhabricatorChangesetViewStateEngine())170->setViewer($viewer)171->setObjectPHID($container_phid)172->setChangeset($changeset);173174$viewstate = $viewstate_engine->newViewStateFromRequest($request);175176if ($viewstate->getDiscardResponse()) {177return new AphrontAjaxResponse();178}179180$parser = id(new DifferentialChangesetParser())181->setViewer($viewer)182->setViewState($viewstate)183->setCoverage($coverage)184->setChangeset($changeset)185->setRenderingReference($rendering_reference)186->setRenderCacheKey($render_cache_key)187->setRightSideCommentMapping($right_source, $right_new)188->setLeftSideCommentMapping($left_source, $left_new);189190if ($left && $right) {191$parser->setOriginals($left, $right);192}193194// Load both left-side and right-side inline comments.195if ($revision) {196$inlines = id(new DifferentialDiffInlineCommentQuery())197->setViewer($viewer)198->withRevisionPHIDs(array($revision->getPHID()))199->withPublishableComments(true)200->withPublishedComments(true)201->needHidden(true)202->needInlineContext(true)203->execute();204205$inlines = mpull($inlines, 'newInlineCommentObject');206207$inlines = id(new PhabricatorInlineCommentAdjustmentEngine())208->setViewer($viewer)209->setRevision($revision)210->setOldChangesets($old)211->setNewChangesets($new)212->setInlines($inlines)213->execute();214} else {215$inlines = array();216}217218if ($left_new) {219$inlines = array_merge(220$inlines,221$this->buildLintInlineComments($left));222}223224if ($right_new) {225$inlines = array_merge(226$inlines,227$this->buildLintInlineComments($right));228}229230$phids = array();231foreach ($inlines as $inline) {232$parser->parseInlineComment($inline);233if ($inline->getAuthorPHID()) {234$phids[$inline->getAuthorPHID()] = true;235}236}237$phids = array_keys($phids);238239$handles = $this->loadViewerHandles($phids);240$parser->setHandles($handles);241242$engine = new PhabricatorMarkupEngine();243$engine->setViewer($viewer);244245foreach ($inlines as $inline) {246$engine->addObject(247$inline,248PhabricatorInlineComment::MARKUP_FIELD_BODY);249}250251$engine->process();252253$parser254->setViewer($viewer)255->setMarkupEngine($engine)256->setShowEditAndReplyLinks(true)257->setCanMarkDone($can_mark)258->setObjectOwnerPHID($object_owner_phid)259->setRange($range_s, $range_e)260->setMask($mask);261262if ($request->isAjax()) {263// NOTE: We must render the changeset before we render coverage264// information, since it builds some caches.265$response = $parser->newChangesetResponse();266267$mcov = $parser->renderModifiedCoverage();268269$coverage_data = array(270'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov,271);272273$response->setCoverage($coverage_data);274275return $response;276}277278$detail = id(new DifferentialChangesetListView())279->setUser($this->getViewer())280->setChangesets(array($changeset))281->setVisibleChangesets(array($changeset))282->setRenderingReferences(array($rendering_reference))283->setRenderURI('/differential/changeset/')284->setDiff($diff)285->setTitle(pht('Standalone View'))286->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)287->setIsStandalone(true)288->setParser($parser);289290if ($revision_id) {291$detail->setInlineCommentControllerURI(292'/differential/comment/inline/edit/'.$revision_id.'/');293}294295$crumbs = $this->buildApplicationCrumbs();296297if ($revision_id) {298$crumbs->addTextCrumb('D'.$revision_id, '/D'.$revision_id);299}300301$diff_id = $diff->getID();302if ($diff_id) {303$crumbs->addTextCrumb(304pht('Diff %d', $diff_id),305$this->getApplicationURI('diff/'.$diff_id));306}307308$crumbs->addTextCrumb($changeset->getDisplayFilename());309$crumbs->setBorder(true);310311$header = id(new PHUIHeaderView())312->setHeader(pht('Changeset View'))313->setHeaderIcon('fa-gear');314315$view = id(new PHUITwoColumnView())316->setHeader($header)317->setFooter($detail);318319return $this->newPage()320->setTitle(pht('Changeset View'))321->setCrumbs($crumbs)322->appendChild($view);323}324325private function buildRawFileResponse(326DifferentialChangeset $changeset,327$is_new) {328329$viewer = $this->getViewer();330331if ($is_new) {332$key = 'raw:new:phid';333} else {334$key = 'raw:old:phid';335}336337$metadata = $changeset->getMetadata();338339$file = null;340$phid = idx($metadata, $key);341if ($phid) {342$file = id(new PhabricatorFileQuery())343->setViewer($viewer)344->withPHIDs(array($phid))345->execute();346if ($file) {347$file = head($file);348}349}350351if (!$file) {352// This is just building a cache of the changeset content in the file353// tool, and is safe to run on a read pathway.354$unguard = AphrontWriteGuard::beginScopedUnguardedWrites();355356if ($is_new) {357$data = $changeset->makeNewFile();358} else {359$data = $changeset->makeOldFile();360}361362$diff = $changeset->getDiff();363364$file = PhabricatorFile::newFromFileData(365$data,366array(367'name' => $changeset->getFilename(),368'mime-type' => 'text/plain',369'ttl.relative' => phutil_units('24 hours in seconds'),370'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,371));372373$file->attachToObject($diff->getPHID());374375$metadata[$key] = $file->getPHID();376$changeset->setMetadata($metadata);377$changeset->save();378379unset($unguard);380}381382return $file->getRedirectResponse();383}384385private function buildLintInlineComments($changeset) {386$diff = $changeset->getDiff();387388$target_phids = $diff->getBuildTargetPHIDs();389if (!$target_phids) {390return array();391}392393$messages = id(new HarbormasterBuildLintMessage())->loadAllWhere(394'buildTargetPHID IN (%Ls) AND path = %s',395$target_phids,396$changeset->getFilename());397398if (!$messages) {399return array();400}401402$change_type = $changeset->getChangeType();403if (DifferentialChangeType::isDeleteChangeType($change_type)) {404// If this is a lint message on a deleted file, show it on the left405// side of the UI because there are no source code lines on the right406// side of the UI so inlines don't have anywhere to render. See PHI416.407$is_new = 0;408} else {409$is_new = 1;410}411412$template = id(new DifferentialInlineComment())413->setChangesetID($changeset->getID())414->setIsNewFile($is_new)415->setLineLength(0);416417$inlines = array();418foreach ($messages as $message) {419$description = $message->getProperty('description');420421$inlines[] = id(clone $template)422->setSyntheticAuthor(pht('Lint: %s', $message->getName()))423->setLineNumber($message->getLine())424->setContent($description);425}426427return $inlines;428}429430private function loadCoverage(DifferentialChangeset $changeset) {431$viewer = $this->getViewer();432433$target_phids = $changeset->getDiff()->getBuildTargetPHIDs();434if (!$target_phids) {435return null;436}437438$unit = id(new HarbormasterBuildUnitMessageQuery())439->setViewer($viewer)440->withBuildTargetPHIDs($target_phids)441->execute();442if (!$unit) {443return null;444}445446$coverage = array();447foreach ($unit as $message) {448$test_coverage = $message->getProperty('coverage');449if ($test_coverage === null) {450continue;451}452$coverage_data = idx($test_coverage, $changeset->getFileName());453if (!strlen($coverage_data)) {454continue;455}456$coverage[] = $coverage_data;457}458459if (!$coverage) {460return null;461}462463return ArcanistUnitTestResult::mergeCoverage($coverage);464}465466}467468469