@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve Diffusion behavior for no-longer-existing commits

Summary:
Ref T9028. When users push a commit, then later delete it (e.g., by deleting the branch which contained it) we currently explode when trying to view it.

Instead, degrade gradually if some information is not available.

Test Plan:
- Looked at valid commits with parents, refs, branches and merges.
- Looked at invalid commits.
- Looked at a previously valid, now-deleted + gc'd commit:

{F859273}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9028

Differential Revision: https://secure.phabricator.com/D14227

+188 -89
+188 -89
src/applications/diffusion/controller/DiffusionCommitController.php
··· 7 7 private $auditAuthorityPHIDs; 8 8 private $highlightedAudits; 9 9 10 + private $commitParents; 11 + private $commitRefs; 12 + private $commitMerges; 13 + private $commitErrors; 14 + private $commitExists; 15 + 10 16 public function shouldAllowPublic() { 11 17 return true; 12 18 } ··· 17 23 18 24 protected function processDiffusionRequest(AphrontRequest $request) { 19 25 $user = $request->getUser(); 26 + 20 27 // This controller doesn't use blob/path stuff, just pass the dictionary 21 28 // in directly instead of using the AphrontRequest parsing mechanism. 22 29 $data = $request->getURIMap(); ··· 45 52 )); 46 53 47 54 if (!$commit) { 48 - $exists = $this->callConduitWithDiffusionRequest( 49 - 'diffusion.existsquery', 50 - array('commit' => $drequest->getCommit())); 51 - if (!$exists) { 55 + if (!$this->getCommitExists()) { 52 56 return new Aphront404Response(); 53 57 } 54 58 ··· 95 99 96 100 require_celerity_resource('phabricator-remarkup-css'); 97 101 98 - $parents = $this->callConduitWithDiffusionRequest( 99 - 'diffusion.commitparentsquery', 100 - array('commit' => $drequest->getCommit())); 101 - 102 - if ($parents) { 103 - $parents = id(new DiffusionCommitQuery()) 104 - ->setViewer($user) 105 - ->withRepository($repository) 106 - ->withIdentifiers($parents) 107 - ->execute(); 108 - } 109 - 110 102 $headsup_view = id(new PHUIHeaderView()) 111 103 ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail'))); 112 104 ··· 115 107 $commit_properties = $this->loadCommitProperties( 116 108 $commit, 117 109 $commit_data, 118 - $parents, 119 110 $audit_requests); 120 111 $property_list = id(new PHUIPropertyListView()) 121 112 ->setHasKeyboardShortcuts(true) ··· 148 139 $message)); 149 140 150 141 $headsup_view->setTall(true); 142 + 151 143 $object_box = id(new PHUIObjectBoxView()) 152 144 ->setHeader($headsup_view) 145 + ->setFormErrors($this->getCommitErrors()) 153 146 ->addPropertyList($property_list) 154 147 ->addPropertyList($detail_list); 155 148 ··· 216 209 'This commit is enormous, and affects more than %d files. '. 217 210 'Changes are not shown.', 218 211 $hard_limit)); 212 + } else if (!$this->getCommitExists()) { 213 + $content[] = $this->renderStatusMessage( 214 + pht('Commit No Longer Exists'), 215 + pht('This commit no longer exists in the repository.')); 219 216 } else { 220 217 $show_changesets = true; 221 218 ··· 382 379 private function loadCommitProperties( 383 380 PhabricatorRepositoryCommit $commit, 384 381 PhabricatorRepositoryCommitData $data, 385 - array $parents, 386 382 array $audit_requests) { 387 383 388 - assert_instances_of($parents, 'PhabricatorRepositoryCommit'); 389 384 $viewer = $this->getRequest()->getUser(); 390 385 $commit_phid = $commit->getPHID(); 391 386 $drequest = $this->getDiffusionRequest(); ··· 423 418 if ($data->getCommitDetail('committerPHID')) { 424 419 $phids[] = $data->getCommitDetail('committerPHID'); 425 420 } 426 - if ($parents) { 427 - foreach ($parents as $parent) { 428 - $phids[] = $parent->getPHID(); 429 - } 430 - } 431 421 432 422 // NOTE: We should never normally have more than a single push log, but 433 423 // it can occur naturally if a commit is pushed, then the branch it was ··· 564 554 $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); 565 555 } 566 556 557 + $parents = $this->getCommitParents(); 567 558 if ($parents) { 568 - $parent_links = array(); 569 - foreach ($parents as $parent) { 570 - $parent_links[] = $handles[$parent->getPHID()]->renderLink(); 571 - } 572 - $props['Parents'] = phutil_implode_html(" \xC2\xB7 ", $parent_links); 559 + $props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID')); 573 560 } 574 561 575 - $props['Branches'] = phutil_tag( 576 - 'span', 577 - array( 578 - 'id' => 'commit-branches', 579 - ), 580 - pht('Unknown')); 581 - $props['Tags'] = phutil_tag( 582 - 'span', 583 - array( 584 - 'id' => 'commit-tags', 585 - ), 586 - pht('Unknown')); 562 + if ($this->getCommitExists()) { 563 + $props['Branches'] = phutil_tag( 564 + 'span', 565 + array( 566 + 'id' => 'commit-branches', 567 + ), 568 + pht('Unknown')); 569 + $props['Tags'] = phutil_tag( 570 + 'span', 571 + array( 572 + 'id' => 'commit-tags', 573 + ), 574 + pht('Unknown')); 587 575 588 - $callsign = $repository->getCallsign(); 589 - $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); 590 - Javelin::initBehavior( 591 - 'diffusion-commit-branches', 592 - array( 593 - $root.'/branches/' => 'commit-branches', 594 - $root.'/tags/' => 'commit-tags', 595 - )); 576 + $callsign = $repository->getCallsign(); 577 + $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); 578 + Javelin::initBehavior( 579 + 'diffusion-commit-branches', 580 + array( 581 + $root.'/branches/' => 'commit-branches', 582 + $root.'/tags/' => 'commit-tags', 583 + )); 584 + } 596 585 597 - $refs = $this->buildRefs($drequest); 586 + $refs = $this->getCommitRefs(); 598 587 if ($refs) { 599 - $props['References'] = $refs; 588 + $ref_links = array(); 589 + foreach ($refs as $ref_data) { 590 + $ref_links[] = phutil_tag( 591 + 'a', 592 + array( 593 + 'href' => $ref_data['href'], 594 + ), 595 + $ref_data['ref']); 596 + } 597 + $props['References'] = phutil_implode_html(', ', $ref_links); 600 598 } 601 599 602 600 if ($reverts_phids) { ··· 860 858 $drequest = $this->getDiffusionRequest(); 861 859 $repository = $drequest->getRepository(); 862 860 863 - $vcs = $repository->getVersionControlSystem(); 864 - switch ($vcs) { 865 - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 866 - // These aren't supported under SVN. 867 - return null; 868 - } 869 - 870 - $limit = 50; 871 - 872 - $merges = $this->callConduitWithDiffusionRequest( 873 - 'diffusion.mergedcommitsquery', 874 - array( 875 - 'commit' => $drequest->getCommit(), 876 - 'limit' => $limit + 1, 877 - )); 878 - 861 + $merges = $this->getCommitMerges(); 879 862 if (!$merges) { 880 863 return null; 881 864 } 882 - $merges = DiffusionPathChange::newFromConduit($merges); 865 + 866 + $limit = $this->getMergeDisplayLimit(); 883 867 884 868 $caption = null; 885 869 if (count($merges) > $limit) { ··· 961 945 return $actions; 962 946 } 963 947 964 - private function buildRefs(DiffusionRequest $request) { 965 - // this is git-only, so save a conduit round trip and just get out of 966 - // here if the repository isn't git 967 - $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; 968 - $repository = $request->getRepository(); 969 - if ($repository->getVersionControlSystem() != $type_git) { 970 - return null; 971 - } 972 - 973 - $results = $this->callConduitWithDiffusionRequest( 974 - 'diffusion.refsquery', 975 - array('commit' => $request->getCommit())); 976 - $ref_links = array(); 977 - foreach ($results as $ref_data) { 978 - $ref_links[] = phutil_tag('a', 979 - array('href' => $ref_data['href']), 980 - $ref_data['ref']); 981 - } 982 - return phutil_implode_html(', ', $ref_links); 983 - } 984 - 985 948 private function buildRawDiffResponse(DiffusionRequest $drequest) { 986 949 $raw_diff = $this->callConduitWithDiffusionRequest( 987 950 'diffusion.rawdiffquery', ··· 1136 1099 1137 1100 return $toc_view; 1138 1101 } 1102 + 1103 + private function loadCommitState() { 1104 + $viewer = $this->getViewer(); 1105 + $drequest = $this->getDiffusionRequest(); 1106 + $repository = $drequest->getRepository(); 1107 + $commit = $drequest->getCommit(); 1108 + 1109 + // TODO: We could use futures here and resolve these calls in parallel. 1110 + 1111 + $exceptions = array(); 1112 + 1113 + try { 1114 + $parent_refs = $this->callConduitWithDiffusionRequest( 1115 + 'diffusion.commitparentsquery', 1116 + array( 1117 + 'commit' => $commit, 1118 + )); 1119 + 1120 + if ($parent_refs) { 1121 + $parents = id(new DiffusionCommitQuery()) 1122 + ->setViewer($viewer) 1123 + ->withRepository($repository) 1124 + ->withIdentifiers($parent_refs) 1125 + ->execute(); 1126 + } else { 1127 + $parents = array(); 1128 + } 1129 + 1130 + $this->commitParents = $parents; 1131 + } catch (Exception $ex) { 1132 + $this->commitParents = false; 1133 + $exceptions[] = $ex; 1134 + } 1135 + 1136 + $merge_limit = $this->getMergeDisplayLimit(); 1137 + 1138 + try { 1139 + $merges = $this->callConduitWithDiffusionRequest( 1140 + 'diffusion.mergedcommitsquery', 1141 + array( 1142 + 'commit' => $commit, 1143 + 'limit' => $merge_limit + 1, 1144 + )); 1145 + 1146 + $this->commitMerges = DiffusionPathChange::newFromConduit($merges); 1147 + } catch (Exception $ex) { 1148 + $this->commitMerges = false; 1149 + $exceptions[] = $ex; 1150 + } 1151 + 1152 + 1153 + try { 1154 + if ($repository->isGit()) { 1155 + $refs = $this->callConduitWithDiffusionRequest( 1156 + 'diffusion.refsquery', 1157 + array( 1158 + 'commit' => $commit, 1159 + )); 1160 + } else { 1161 + $refs = array(); 1162 + } 1163 + 1164 + $this->commitRefs = $refs; 1165 + } catch (Exception $ex) { 1166 + $this->commitRefs = false; 1167 + $exceptions[] = $ex; 1168 + } 1169 + 1170 + if ($exceptions) { 1171 + $exists = $this->callConduitWithDiffusionRequest( 1172 + 'diffusion.existsquery', 1173 + array( 1174 + 'commit' => $commit, 1175 + )); 1176 + 1177 + if ($exists) { 1178 + $this->commitExists = true; 1179 + foreach ($exceptions as $exception) { 1180 + $this->commitErrors[] = $exception->getMessage(); 1181 + } 1182 + } else { 1183 + $this->commitExists = false; 1184 + $this->commitErrors[] = pht( 1185 + 'This commit no longer exists in the repository. It may have '. 1186 + 'been part of a branch which was deleted.'); 1187 + } 1188 + } else { 1189 + $this->commitExists = true; 1190 + $this->commitErrors = array(); 1191 + } 1192 + } 1193 + 1194 + private function getMergeDisplayLimit() { 1195 + return 50; 1196 + } 1197 + 1198 + private function getCommitExists() { 1199 + if ($this->commitExists === null) { 1200 + $this->loadCommitState(); 1201 + } 1202 + 1203 + return $this->commitExists; 1204 + } 1205 + 1206 + private function getCommitParents() { 1207 + if ($this->commitParents === null) { 1208 + $this->loadCommitState(); 1209 + } 1210 + 1211 + return $this->commitParents; 1212 + } 1213 + 1214 + private function getCommitRefs() { 1215 + if ($this->commitRefs === null) { 1216 + $this->loadCommitState(); 1217 + } 1218 + 1219 + return $this->commitRefs; 1220 + } 1221 + 1222 + private function getCommitMerges() { 1223 + if ($this->commitMerges === null) { 1224 + $this->loadCommitState(); 1225 + } 1226 + 1227 + return $this->commitMerges; 1228 + } 1229 + 1230 + private function getCommitErrors() { 1231 + if ($this->commitErrors === null) { 1232 + $this->loadCommitState(); 1233 + } 1234 + 1235 + return $this->commitErrors; 1236 + } 1237 + 1139 1238 1140 1239 }