@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 466 lines 12 kB view raw
1<?php 2 3final class DifferentialChangesetViewController extends DifferentialController { 4 5 public function shouldAllowPublic() { 6 return true; 7 } 8 9 public function handleRequest(AphrontRequest $request) { 10 $viewer = $this->getViewer(); 11 12 $rendering_reference = $request->getStr('ref'); 13 $parts = explode('/', $rendering_reference); 14 if (count($parts) == 2) { 15 list($id, $vs) = $parts; 16 } else { 17 $id = $parts[0]; 18 $vs = 0; 19 } 20 21 $id = (int)$id; 22 $vs = (int)$vs; 23 24 $load_ids = array($id); 25 if ($vs && ($vs != -1)) { 26 $load_ids[] = $vs; 27 } 28 29 $changesets = id(new DifferentialChangesetQuery()) 30 ->setViewer($viewer) 31 ->withIDs($load_ids) 32 ->needHunks(true) 33 ->execute(); 34 $changesets = mpull($changesets, null, 'getID'); 35 36 $changeset = idx($changesets, $id); 37 if (!$changeset) { 38 return new Aphront404Response(); 39 } 40 41 $vs_changeset = null; 42 if ($vs && ($vs != -1)) { 43 $vs_changeset = idx($changesets, $vs); 44 if (!$vs_changeset) { 45 return new Aphront404Response(); 46 } 47 } 48 49 $view = $request->getStr('view'); 50 if ($view) { 51 $phid = idx($changeset->getMetadata(), "$view:binary-phid"); 52 if ($phid) { 53 return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/"); 54 } 55 switch ($view) { 56 case 'new': 57 return $this->buildRawFileResponse($changeset, $is_new = true); 58 case 'old': 59 if ($vs_changeset) { 60 return $this->buildRawFileResponse($vs_changeset, $is_new = true); 61 } 62 return $this->buildRawFileResponse($changeset, $is_new = false); 63 default: 64 return new Aphront400Response(); 65 } 66 } 67 68 $old = array(); 69 $new = array(); 70 if (!$vs) { 71 $right = $changeset; 72 $left = null; 73 74 $right_source = $right->getID(); 75 $right_new = true; 76 $left_source = $right->getID(); 77 $left_new = false; 78 79 $render_cache_key = $right->getID(); 80 81 $old[] = $changeset; 82 $new[] = $changeset; 83 } else if ($vs == -1) { 84 $right = null; 85 $left = $changeset; 86 87 $right_source = $left->getID(); 88 $right_new = false; 89 $left_source = $left->getID(); 90 $left_new = true; 91 92 $render_cache_key = null; 93 94 $old[] = $changeset; 95 $new[] = $changeset; 96 } else { 97 $right = $changeset; 98 $left = $vs_changeset; 99 100 $right_source = $right->getID(); 101 $right_new = true; 102 $left_source = $left->getID(); 103 $left_new = true; 104 105 $render_cache_key = null; 106 107 $new[] = $left; 108 $new[] = $right; 109 } 110 111 if ($left) { 112 $changeset = $left->newComparisonChangeset($right); 113 } 114 115 if ($left_new || $right_new) { 116 $diff_map = array(); 117 if ($left) { 118 $diff_map[] = $left->getDiff(); 119 } 120 if ($right) { 121 $diff_map[] = $right->getDiff(); 122 } 123 $diff_map = mpull($diff_map, null, 'getPHID'); 124 125 $buildables = id(new HarbormasterBuildableQuery()) 126 ->setViewer($viewer) 127 ->withBuildablePHIDs(array_keys($diff_map)) 128 ->withManualBuildables(false) 129 ->needBuilds(true) 130 ->needTargets(true) 131 ->execute(); 132 $buildables = mpull($buildables, null, 'getBuildablePHID'); 133 foreach ($diff_map as $diff_phid => $changeset_diff) { 134 $changeset_diff->attachBuildable(idx($buildables, $diff_phid)); 135 } 136 } 137 138 $coverage = null; 139 if ($right_new) { 140 $coverage = $this->loadCoverage($right); 141 } 142 143 $spec = $request->getStr('range'); 144 list($range_s, $range_e, $mask) = 145 DifferentialChangesetParser::parseRangeSpecification($spec); 146 147 $diff = $changeset->getDiff(); 148 $revision_id = $diff->getRevisionID(); 149 150 $can_mark = false; 151 $object_owner_phid = null; 152 $revision = null; 153 if ($revision_id) { 154 $revision = id(new DifferentialRevisionQuery()) 155 ->setViewer($viewer) 156 ->withIDs(array($revision_id)) 157 ->executeOne(); 158 if ($revision) { 159 $can_mark = ($revision->getAuthorPHID() == $viewer->getPHID()); 160 $object_owner_phid = $revision->getAuthorPHID(); 161 } 162 } 163 164 if ($revision) { 165 $container_phid = $revision->getPHID(); 166 } else { 167 $container_phid = $diff->getPHID(); 168 } 169 170 $viewstate_engine = id(new PhabricatorChangesetViewStateEngine()) 171 ->setViewer($viewer) 172 ->setObjectPHID($container_phid) 173 ->setChangeset($changeset); 174 175 $viewstate = $viewstate_engine->newViewStateFromRequest($request); 176 177 if ($viewstate->getDiscardResponse()) { 178 return new AphrontAjaxResponse(); 179 } 180 181 $parser = id(new DifferentialChangesetParser()) 182 ->setViewer($viewer) 183 ->setViewState($viewstate) 184 ->setCoverage($coverage) 185 ->setChangeset($changeset) 186 ->setRenderingReference($rendering_reference) 187 ->setRenderCacheKey($render_cache_key) 188 ->setRightSideCommentMapping($right_source, $right_new) 189 ->setLeftSideCommentMapping($left_source, $left_new); 190 191 if ($left && $right) { 192 $parser->setOriginals($left, $right); 193 } 194 195 // Load both left-side and right-side inline comments. 196 if ($revision) { 197 $inlines = id(new DifferentialDiffInlineCommentQuery()) 198 ->setViewer($viewer) 199 ->withRevisionPHIDs(array($revision->getPHID())) 200 ->withPublishableComments(true) 201 ->withPublishedComments(true) 202 ->needHidden(true) 203 ->needInlineContext(true) 204 ->execute(); 205 206 $inlines = mpull($inlines, 'newInlineCommentObject'); 207 208 $inlines = id(new PhabricatorInlineCommentAdjustmentEngine()) 209 ->setViewer($viewer) 210 ->setRevision($revision) 211 ->setOldChangesets($old) 212 ->setNewChangesets($new) 213 ->setInlines($inlines) 214 ->execute(); 215 } else { 216 $inlines = array(); 217 } 218 219 if ($left_new) { 220 $inlines = array_merge( 221 $inlines, 222 $this->buildLintInlineComments($left)); 223 } 224 225 if ($right_new) { 226 $inlines = array_merge( 227 $inlines, 228 $this->buildLintInlineComments($right)); 229 } 230 231 $phids = array(); 232 foreach ($inlines as $inline) { 233 $parser->parseInlineComment($inline); 234 if ($inline->getAuthorPHID()) { 235 $phids[$inline->getAuthorPHID()] = true; 236 } 237 } 238 $phids = array_keys($phids); 239 240 $handles = $this->loadViewerHandles($phids); 241 $parser->setHandles($handles); 242 243 $engine = new PhabricatorMarkupEngine(); 244 $engine->setViewer($viewer); 245 246 foreach ($inlines as $inline) { 247 $engine->addObject( 248 $inline, 249 PhabricatorInlineComment::MARKUP_FIELD_BODY); 250 } 251 252 $engine->process(); 253 254 $parser 255 ->setViewer($viewer) 256 ->setMarkupEngine($engine) 257 ->setShowEditAndReplyLinks(true) 258 ->setCanMarkDone($can_mark) 259 ->setObjectOwnerPHID($object_owner_phid) 260 ->setRange($range_s, $range_e) 261 ->setMask($mask); 262 263 if ($request->isAjax()) { 264 // NOTE: We must render the changeset before we render coverage 265 // information, since it builds some caches. 266 $response = $parser->newChangesetResponse(); 267 268 $mcov = $parser->renderModifiedCoverage(); 269 270 $coverage_data = array( 271 'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov, 272 ); 273 274 $response->setCoverage($coverage_data); 275 276 return $response; 277 } 278 279 $detail = id(new DifferentialChangesetListView()) 280 ->setUser($this->getViewer()) 281 ->setChangesets(array($changeset)) 282 ->setVisibleChangesets(array($changeset)) 283 ->setRenderingReferences(array($rendering_reference)) 284 ->setRenderURI('/differential/changeset/') 285 ->setDiff($diff) 286 ->setTitle(pht('Standalone View')) 287 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 288 ->setIsStandalone(true) 289 ->setParser($parser); 290 291 if ($revision_id) { 292 $detail->setInlineCommentControllerURI( 293 '/differential/comment/inline/edit/'.$revision_id.'/'); 294 } 295 296 $crumbs = $this->buildApplicationCrumbs(); 297 298 if ($revision_id) { 299 $crumbs->addTextCrumb('D'.$revision_id, '/D'.$revision_id); 300 } 301 302 $diff_id = $diff->getID(); 303 if ($diff_id) { 304 $crumbs->addTextCrumb( 305 pht('Diff %d', $diff_id), 306 $this->getApplicationURI('diff/'.$diff_id)); 307 } 308 309 $crumbs->addTextCrumb($changeset->getDisplayFilename()); 310 $crumbs->setBorder(true); 311 312 $header = id(new PHUIHeaderView()) 313 ->setHeader(pht('Changeset View')) 314 ->setHeaderIcon('fa-gear'); 315 316 $view = id(new PHUITwoColumnView()) 317 ->setHeader($header) 318 ->setFooter($detail); 319 320 return $this->newPage() 321 ->setTitle(pht('Changeset View')) 322 ->setCrumbs($crumbs) 323 ->appendChild($view); 324 } 325 326 private function buildRawFileResponse( 327 DifferentialChangeset $changeset, 328 $is_new) { 329 330 $viewer = $this->getViewer(); 331 332 if ($is_new) { 333 $key = 'raw:new:phid'; 334 } else { 335 $key = 'raw:old:phid'; 336 } 337 338 $metadata = $changeset->getMetadata(); 339 340 $file = null; 341 $phid = idx($metadata, $key); 342 if ($phid) { 343 $file = id(new PhabricatorFileQuery()) 344 ->setViewer($viewer) 345 ->withPHIDs(array($phid)) 346 ->execute(); 347 if ($file) { 348 $file = head($file); 349 } 350 } 351 352 if (!$file) { 353 // This is just building a cache of the changeset content in the file 354 // tool, and is safe to run on a read pathway. 355 $unguard = AphrontWriteGuard::beginScopedUnguardedWrites(); 356 357 if ($is_new) { 358 $data = $changeset->makeNewFile(); 359 } else { 360 $data = $changeset->makeOldFile(); 361 } 362 363 $diff = $changeset->getDiff(); 364 365 $file = PhabricatorFile::newFromFileData( 366 $data, 367 array( 368 'name' => $changeset->getFilename(), 369 'mime-type' => 'text/plain', 370 'ttl.relative' => phutil_units('24 hours in seconds'), 371 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, 372 )); 373 374 $file->attachToObject($diff->getPHID()); 375 376 $metadata[$key] = $file->getPHID(); 377 $changeset->setMetadata($metadata); 378 $changeset->save(); 379 380 unset($unguard); 381 } 382 383 return $file->getRedirectResponse(); 384 } 385 386 private function buildLintInlineComments($changeset) { 387 $diff = $changeset->getDiff(); 388 389 $target_phids = $diff->getBuildTargetPHIDs(); 390 if (!$target_phids) { 391 return array(); 392 } 393 394 $messages = id(new HarbormasterBuildLintMessage())->loadAllWhere( 395 'buildTargetPHID IN (%Ls) AND path = %s', 396 $target_phids, 397 $changeset->getFilename()); 398 399 if (!$messages) { 400 return array(); 401 } 402 403 $change_type = $changeset->getChangeType(); 404 if (DifferentialChangeType::isDeleteChangeType($change_type)) { 405 // If this is a lint message on a deleted file, show it on the left 406 // side of the UI because there are no source code lines on the right 407 // side of the UI so inlines don't have anywhere to render. See PHI416. 408 $is_new = 0; 409 } else { 410 $is_new = 1; 411 } 412 413 $template = id(new DifferentialInlineComment()) 414 ->setChangesetID($changeset->getID()) 415 ->setIsNewFile($is_new) 416 ->setLineLength(0); 417 418 $inlines = array(); 419 foreach ($messages as $message) { 420 $description = $message->getProperty('description'); 421 422 $inlines[] = id(clone $template) 423 ->setSyntheticAuthor(pht('Lint: %s', $message->getName())) 424 ->setLineNumber($message->getLine()) 425 ->setContent($description); 426 } 427 428 return $inlines; 429 } 430 431 private function loadCoverage(DifferentialChangeset $changeset) { 432 $viewer = $this->getViewer(); 433 434 $target_phids = $changeset->getDiff()->getBuildTargetPHIDs(); 435 if (!$target_phids) { 436 return null; 437 } 438 439 $unit = id(new HarbormasterBuildUnitMessageQuery()) 440 ->setViewer($viewer) 441 ->withBuildTargetPHIDs($target_phids) 442 ->execute(); 443 if (!$unit) { 444 return null; 445 } 446 447 $coverage = array(); 448 foreach ($unit as $message) { 449 $test_coverage = $message->getProperty('coverage'); 450 if ($test_coverage === null) { 451 continue; 452 } 453 $coverage_data = idx($test_coverage, $changeset->getFileName()); 454 if (phutil_nonempty_string($coverage_data)) { 455 $coverage[] = $coverage_data; 456 } 457 } 458 459 if (!$coverage) { 460 return null; 461 } 462 463 return ArcanistUnitTestResult::mergeCoverage($coverage); 464 } 465 466}