[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * This DifferentialFieldSpecification exists for two reason: 5 * 6 * 1: To parse "Releeph: picks RQ<nn>" headers in commits created by 7 * arc-releeph so that RQs committed by arc-releeph have real 8 * PhabricatorRepositoryCommits associated with them (instaed of just the SHA 9 * of the commit, as seen by the pusher). 10 * 11 * 2: If requestors want to commit directly to their release branch, they can 12 * use this header to (i) indicate on a differential revision that this 13 * differential revision is for the release branch, and (ii) when they land 14 * their diff on to the release branch manually, the ReleephRequest is 15 * automatically updated (instead of having to use the "Mark Manually Picked" 16 * button.) 17 * 18 */ 19 final class DifferentialReleephRequestFieldSpecification { 20 21 // TODO: This class is essentially dead right now, see T2222. 22 23 const ACTION_PICKS = 'picks'; 24 const ACTION_REVERTS = 'reverts'; 25 26 private $releephAction; 27 private $releephPHIDs = array(); 28 29 public function getStorageKey() { 30 return 'releeph:actions'; 31 } 32 33 public function getValueForStorage() { 34 return json_encode(array( 35 'releephAction' => $this->releephAction, 36 'releephPHIDs' => $this->releephPHIDs, 37 )); 38 } 39 40 public function setValueFromStorage($json) { 41 if ($json) { 42 $dict = json_decode($json, true); 43 $this->releephAction = idx($dict, 'releephAction'); 44 $this->releephPHIDs = idx($dict, 'releephPHIDs'); 45 } 46 return $this; 47 } 48 49 public function shouldAppearOnRevisionView() { 50 return true; 51 } 52 53 public function renderLabelForRevisionView() { 54 return 'Releeph'; 55 } 56 57 public function getRequiredHandlePHIDs() { 58 return mpull($this->loadReleephRequests(), 'getPHID'); 59 } 60 61 public function renderValueForRevisionView() { 62 static $tense = array( 63 self::ACTION_PICKS => array( 64 'future' => 'Will pick', 65 'past' => 'Picked', 66 ), 67 self::ACTION_REVERTS => array( 68 'future' => 'Will revert', 69 'past' => 'Reverted', 70 ), 71 ); 72 73 $releeph_requests = $this->loadReleephRequests(); 74 if (!$releeph_requests) { 75 return null; 76 } 77 78 $status = $this->getRevision()->getStatus(); 79 if ($status == ArcanistDifferentialRevisionStatus::CLOSED) { 80 $verb = $tense[$this->releephAction]['past']; 81 } else { 82 $verb = $tense[$this->releephAction]['future']; 83 } 84 85 $parts = hsprintf('%s...', $verb); 86 foreach ($releeph_requests as $releeph_request) { 87 $parts->appendHTML(phutil_tag('br')); 88 $parts->appendHTML( 89 $this->getHandle($releeph_request->getPHID())->renderLink()); 90 } 91 92 return $parts; 93 } 94 95 public function shouldAppearOnCommitMessage() { 96 return true; 97 } 98 99 public function getCommitMessageKey() { 100 return 'releephActions'; 101 } 102 103 public function setValueFromParsedCommitMessage($dict) { 104 $this->releephAction = $dict['releephAction']; 105 $this->releephPHIDs = $dict['releephPHIDs']; 106 return $this; 107 } 108 109 public function renderValueForCommitMessage($is_edit) { 110 $releeph_requests = $this->loadReleephRequests(); 111 if (!$releeph_requests) { 112 return null; 113 } 114 115 $parts = array($this->releephAction); 116 foreach ($releeph_requests as $releeph_request) { 117 $parts[] = 'RQ'.$releeph_request->getID(); 118 } 119 120 return implode(' ', $parts); 121 } 122 123 /** 124 * Releeph fields should look like: 125 * 126 * Releeph: picks RQ1 RQ2, RQ3 127 * Releeph: reverts RQ1 128 */ 129 public function parseValueFromCommitMessage($value) { 130 /** 131 * Releeph commit messages look like this (but with more blank lines, 132 * omitted here): 133 * 134 * Make CaptainHaddock more reasonable 135 * Releeph: picks RQ1 136 * Requested By: edward 137 * Approved By: edward (requestor) 138 * Request Reason: x 139 * Summary: Make the Haddock implementation more reasonable. 140 * Test Plan: none 141 * Reviewers: user1 142 * 143 * Some of these fields are recognized by Differential (e.g. "Requested 144 * By"). They are folded up into the "Releeph" field, parsed by this 145 * class. As such $value includes more than just the first-line: 146 * 147 * "picks RQ1\n\nRequested By: edward\n\nApproved By: edward (requestor)" 148 * 149 * To hack around this, just consider the first line of $value when 150 * determining what Releeph actions the parsed commit is performing. 151 */ 152 $first_line = head(array_filter(explode("\n", $value))); 153 154 $tokens = preg_split('/\s*,?\s+/', $first_line); 155 $raw_action = array_shift($tokens); 156 $action = strtolower($raw_action); 157 158 if (!$action) { 159 return null; 160 } 161 162 switch ($action) { 163 case self::ACTION_REVERTS: 164 case self::ACTION_PICKS: 165 break; 166 167 default: 168 throw new DifferentialFieldParseException( 169 "Commit message contains unknown Releeph action '{$raw_action}'!"); 170 break; 171 } 172 173 $releeph_requests = array(); 174 foreach ($tokens as $token) { 175 $match = array(); 176 if (!preg_match('/^(?:RQ)?(\d+)$/i', $token, $match)) { 177 $label = $this->renderLabelForCommitMessage(); 178 throw new DifferentialFieldParseException( 179 "Commit message contains unparseable ". 180 "Releeph request token '{$token}'!"); 181 } 182 183 $id = (int) $match[1]; 184 $releeph_request = id(new ReleephRequest())->load($id); 185 186 if (!$releeph_request) { 187 throw new DifferentialFieldParseException( 188 "Commit message references non existent releeph request: {$value}!"); 189 } 190 191 $releeph_requests[] = $releeph_request; 192 } 193 194 if (count($releeph_requests) > 1) { 195 $rqs_seen = array(); 196 $groups = array(); 197 foreach ($releeph_requests as $releeph_request) { 198 $releeph_branch = $releeph_request->getBranch(); 199 $branch_name = $releeph_branch->getName(); 200 $rq_id = 'RQ'.$releeph_request->getID(); 201 202 if (idx($rqs_seen, $rq_id)) { 203 throw new DifferentialFieldParseException( 204 "Commit message refers to {$rq_id} multiple times!"); 205 } 206 $rqs_seen[$rq_id] = true; 207 208 if (!isset($groups[$branch_name])) { 209 $groups[$branch_name] = array(); 210 } 211 $groups[$branch_name][] = $rq_id; 212 } 213 214 if (count($groups) > 1) { 215 $lists = array(); 216 foreach ($groups as $branch_name => $rq_ids) { 217 $lists[] = implode(', ', $rq_ids).' in '.$branch_name; 218 } 219 throw new DifferentialFieldParseException( 220 'Commit message references multiple Releeph requests, '. 221 'but the requests are in different branches: '. 222 implode('; ', $lists)); 223 } 224 } 225 226 $phids = mpull($releeph_requests, 'getPHID'); 227 228 $data = array( 229 'releephAction' => $action, 230 'releephPHIDs' => $phids, 231 ); 232 return $data; 233 } 234 235 public function renderLabelForCommitMessage() { 236 return 'Releeph'; 237 } 238 239 public function shouldAppearOnCommitMessageTemplate() { 240 return false; 241 } 242 243 public function didParseCommit(PhabricatorRepository $repo, 244 PhabricatorRepositoryCommit $commit, 245 PhabricatorRepositoryCommitData $data) { 246 247 // NOTE: This is currently dead code. See T2222. 248 249 $releeph_requests = $this->loadReleephRequests(); 250 251 if (!$releeph_requests) { 252 return; 253 } 254 255 $releeph_branch = head($releeph_requests)->getBranch(); 256 if (!$this->isCommitOnBranch($repo, $commit, $releeph_branch)) { 257 return; 258 } 259 260 foreach ($releeph_requests as $releeph_request) { 261 if ($this->releephAction === self::ACTION_PICKS) { 262 $action = 'pick'; 263 } else { 264 $action = 'revert'; 265 } 266 267 $actor_phid = coalesce( 268 $data->getCommitDetail('committerPHID'), 269 $data->getCommitDetail('authorPHID')); 270 271 $actor = id(new PhabricatorUser()) 272 ->loadOneWhere('phid = %s', $actor_phid); 273 274 $xactions = array(); 275 276 $xactions[] = id(new ReleephRequestTransaction()) 277 ->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY) 278 ->setMetadataValue('action', $action) 279 ->setMetadataValue('authorPHID', 280 $data->getCommitDetail('authorPHID')) 281 ->setMetadataValue('committerPHID', 282 $data->getCommitDetail('committerPHID')) 283 ->setNewValue($commit->getPHID()); 284 285 $editor = id(new ReleephRequestTransactionalEditor()) 286 ->setActor($actor) 287 ->setContinueOnNoEffect(true) 288 ->setContentSource( 289 PhabricatorContentSource::newForSource( 290 PhabricatorContentSource::SOURCE_UNKNOWN, 291 array())); 292 293 $editor->applyTransactions($releeph_request, $xactions); 294 } 295 } 296 297 private function loadReleephRequests() { 298 if (!$this->releephPHIDs) { 299 return array(); 300 } 301 302 return id(new ReleephRequestQuery()) 303 ->setViewer($this->getViewer()) 304 ->withPHIDs($this->releephPHIDs) 305 ->execute(); 306 } 307 308 private function isCommitOnBranch(PhabricatorRepository $repo, 309 PhabricatorRepositoryCommit $commit, 310 ReleephBranch $releeph_branch) { 311 312 switch ($repo->getVersionControlSystem()) { 313 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 314 list($output) = $repo->execxLocalCommand( 315 'branch --all --no-color --contains %s', 316 $commit->getCommitIdentifier()); 317 318 $remote_prefix = 'remotes/origin/'; 319 $branches = array(); 320 foreach (array_filter(explode("\n", $output)) as $line) { 321 $tokens = explode(' ', $line); 322 $ref = last($tokens); 323 if (strncmp($ref, $remote_prefix, strlen($remote_prefix)) === 0) { 324 $branch = substr($ref, strlen($remote_prefix)); 325 $branches[$branch] = $branch; 326 } 327 } 328 329 return idx($branches, $releeph_branch->getName()); 330 break; 331 332 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 333 $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( 334 DiffusionRequest::newFromDictionary(array( 335 'user' => $this->getUser(), 336 'repository' => $repo, 337 'commit' => $commit->getCommitIdentifier(), 338 ))); 339 $path_changes = $change_query->loadChanges(); 340 $commit_paths = mpull($path_changes, 'getPath'); 341 342 $branch_path = $releeph_branch->getName(); 343 344 $in_branch = array(); 345 $ex_branch = array(); 346 foreach ($commit_paths as $path) { 347 if (strncmp($path, $branch_path, strlen($branch_path)) === 0) { 348 $in_branch[] = $path; 349 } else { 350 $ex_branch[] = $path; 351 } 352 } 353 354 if ($in_branch && $ex_branch) { 355 $error = sprintf( 356 'CONFUSION: commit %s in %s contains %d path change(s) that were '. 357 'part of a Releeph branch, but also has %d path change(s) not '. 358 'part of a Releeph branch!', 359 $commit->getCommitIdentifier(), 360 $repo->getCallsign(), 361 count($in_branch), 362 count($ex_branch)); 363 phlog($error); 364 } 365 366 return !empty($in_branch); 367 break; 368 } 369 } 370 371 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |