[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Update the ref cursors for a repository, which track the positions of 5 * branches, bookmarks, and tags. 6 */ 7 final class PhabricatorRepositoryRefEngine 8 extends PhabricatorRepositoryEngine { 9 10 private $newRefs = array(); 11 private $deadRefs = array(); 12 private $closeCommits = array(); 13 private $hasNoCursors; 14 15 public function updateRefs() { 16 $this->newRefs = array(); 17 $this->deadRefs = array(); 18 $this->closeCommits = array(); 19 20 $repository = $this->getRepository(); 21 22 $vcs = $repository->getVersionControlSystem(); 23 switch ($vcs) { 24 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 25 // No meaningful refs of any type in Subversion. 26 $branches = array(); 27 $bookmarks = array(); 28 $tags = array(); 29 break; 30 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 31 $branches = $this->loadMercurialBranchPositions($repository); 32 $bookmarks = $this->loadMercurialBookmarkPositions($repository); 33 $tags = array(); 34 break; 35 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 36 $branches = $this->loadGitBranchPositions($repository); 37 $bookmarks = array(); 38 $tags = $this->loadGitTagPositions($repository); 39 break; 40 default: 41 throw new Exception(pht('Unknown VCS "%s"!', $vcs)); 42 } 43 44 $maps = array( 45 PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches, 46 PhabricatorRepositoryRefCursor::TYPE_TAG => $tags, 47 PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks, 48 ); 49 50 $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) 51 ->setViewer(PhabricatorUser::getOmnipotentUser()) 52 ->withRepositoryPHIDs(array($repository->getPHID())) 53 ->execute(); 54 $cursor_groups = mgroup($all_cursors, 'getRefType'); 55 56 $this->hasNoCursors = (!$all_cursors); 57 58 // Find all the heads of closing refs. 59 $all_closing_heads = array(); 60 foreach ($all_cursors as $cursor) { 61 if ($this->shouldCloseRef($cursor->getRefType(), $cursor->getRefName())) { 62 $all_closing_heads[] = $cursor->getCommitIdentifier(); 63 } 64 } 65 $all_closing_heads = array_unique($all_closing_heads); 66 $all_closing_heads = $this->removeMissingCommits($all_closing_heads); 67 68 foreach ($maps as $type => $refs) { 69 $cursor_group = idx($cursor_groups, $type, array()); 70 $this->updateCursors($cursor_group, $refs, $type, $all_closing_heads); 71 } 72 73 if ($this->closeCommits) { 74 $this->setCloseFlagOnCommits($this->closeCommits); 75 } 76 77 if ($this->newRefs || $this->deadRefs) { 78 $repository->openTransaction(); 79 foreach ($this->newRefs as $ref) { 80 $ref->save(); 81 } 82 foreach ($this->deadRefs as $ref) { 83 $ref->delete(); 84 } 85 $repository->saveTransaction(); 86 87 $this->newRefs = array(); 88 $this->deadRefs = array(); 89 } 90 } 91 92 private function markRefNew(PhabricatorRepositoryRefCursor $cursor) { 93 $this->newRefs[] = $cursor; 94 return $this; 95 } 96 97 private function markRefDead(PhabricatorRepositoryRefCursor $cursor) { 98 $this->deadRefs[] = $cursor; 99 return $this; 100 } 101 102 private function markCloseCommits(array $identifiers) { 103 foreach ($identifiers as $identifier) { 104 $this->closeCommits[$identifier] = $identifier; 105 } 106 return $this; 107 } 108 109 /** 110 * Remove commits which no longer exist in the repository from a list. 111 * 112 * After a force push and garbage collection, we may have branch cursors which 113 * point at commits which no longer exist. This can make commands issued later 114 * fail. See T5839 for discussion. 115 * 116 * @param list<string> List of commit identifiers. 117 * @return list<string> List with nonexistent identifiers removed. 118 */ 119 private function removeMissingCommits(array $identifiers) { 120 if (!$identifiers) { 121 return array(); 122 } 123 124 $resolved = id(new DiffusionLowLevelResolveRefsQuery()) 125 ->setRepository($this->getRepository()) 126 ->withRefs($identifiers) 127 ->execute(); 128 129 foreach ($identifiers as $key => $identifier) { 130 if (empty($resolved[$identifier])) { 131 unset($identifiers[$key]); 132 } 133 } 134 135 return $identifiers; 136 } 137 138 private function updateCursors( 139 array $cursors, 140 array $new_refs, 141 $ref_type, 142 array $all_closing_heads) { 143 $repository = $this->getRepository(); 144 145 // NOTE: Mercurial branches may have multiple branch heads; this logic 146 // is complex primarily to account for that. 147 148 // Group all the cursors by their ref name, like "master". Since Mercurial 149 // branches may have multiple heads, there could be several cursors with 150 // the same name. 151 $cursor_groups = mgroup($cursors, 'getRefNameRaw'); 152 153 // Group all the new ref values by their name. As above, these groups may 154 // have multiple members in Mercurial. 155 $ref_groups = mgroup($new_refs, 'getShortName'); 156 157 foreach ($ref_groups as $name => $refs) { 158 $new_commits = mpull($refs, 'getCommitIdentifier', 'getCommitIdentifier'); 159 160 $ref_cursors = idx($cursor_groups, $name, array()); 161 $old_commits = mpull($ref_cursors, null, 'getCommitIdentifier'); 162 163 // We're going to delete all the cursors pointing at commits which are 164 // no longer associated with the refs. This primarily makes the Mercurial 165 // multiple head case easier, and means that when we update a ref we 166 // delete the old one and write a new one. 167 foreach ($ref_cursors as $cursor) { 168 if (isset($new_commits[$cursor->getCommitIdentifier()])) { 169 // This ref previously pointed at this commit, and still does. 170 $this->log( 171 pht( 172 'Ref %s "%s" still points at %s.', 173 $ref_type, 174 $name, 175 $cursor->getCommitIdentifier())); 176 } else { 177 // This ref previously pointed at this commit, but no longer does. 178 $this->log( 179 pht( 180 'Ref %s "%s" no longer points at %s.', 181 $ref_type, 182 $name, 183 $cursor->getCommitIdentifier())); 184 185 // Nuke the obsolete cursor. 186 $this->markRefDead($cursor); 187 } 188 } 189 190 // Now, we're going to insert new cursors for all the commits which are 191 // associated with this ref that don't currently have cursors. 192 $added_commits = array_diff_key($new_commits, $old_commits); 193 foreach ($added_commits as $identifier) { 194 $this->log( 195 pht( 196 'Ref %s "%s" now points at %s.', 197 $ref_type, 198 $name, 199 $identifier)); 200 $this->markRefNew( 201 id(new PhabricatorRepositoryRefCursor()) 202 ->setRepositoryPHID($repository->getPHID()) 203 ->setRefType($ref_type) 204 ->setRefName($name) 205 ->setCommitIdentifier($identifier)); 206 } 207 208 if ($this->shouldCloseRef($ref_type, $name)) { 209 foreach ($added_commits as $identifier) { 210 $new_identifiers = $this->loadNewCommitIdentifiers( 211 $identifier, 212 $all_closing_heads); 213 214 $this->markCloseCommits($new_identifiers); 215 } 216 } 217 } 218 219 // Find any cursors for refs which no longer exist. This happens when a 220 // branch, tag or bookmark is deleted. 221 222 foreach ($cursor_groups as $name => $cursor_group) { 223 if (idx($ref_groups, $name) === null) { 224 foreach ($cursor_group as $cursor) { 225 $this->log( 226 pht( 227 'Ref %s "%s" no longer exists.', 228 $cursor->getRefType(), 229 $cursor->getRefName())); 230 $this->markRefDead($cursor); 231 } 232 } 233 } 234 } 235 236 private function shouldCloseRef($ref_type, $ref_name) { 237 if ($ref_type !== PhabricatorRepositoryRefCursor::TYPE_BRANCH) { 238 return false; 239 } 240 241 if ($this->hasNoCursors) { 242 // If we don't have any cursors, don't close things. Particularly, this 243 // corresponds to the case where you've just updated to this code on an 244 // existing repository: we don't want to requeue message steps for every 245 // commit on a closeable ref. 246 return false; 247 } 248 249 return $this->getRepository()->shouldAutocloseBranch($ref_name); 250 } 251 252 /** 253 * Find all ancestors of a new closing branch head which are not ancestors 254 * of any old closing branch head. 255 */ 256 private function loadNewCommitIdentifiers( 257 $new_head, 258 array $all_closing_heads) { 259 260 $repository = $this->getRepository(); 261 $vcs = $repository->getVersionControlSystem(); 262 switch ($vcs) { 263 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 264 if ($all_closing_heads) { 265 $escheads = array(); 266 foreach ($all_closing_heads as $head) { 267 $escheads[] = hgsprintf('%s', $head); 268 } 269 $escheads = implode(' or ', $escheads); 270 list($stdout) = $this->getRepository()->execxLocalCommand( 271 'log --template %s --rev %s', 272 '{node}\n', 273 hgsprintf('%s', $new_head).' - ('.$escheads.')'); 274 } else { 275 list($stdout) = $this->getRepository()->execxLocalCommand( 276 'log --template %s --rev %s', 277 '{node}\n', 278 hgsprintf('%s', $new_head)); 279 } 280 281 $stdout = trim($stdout); 282 if (!strlen($stdout)) { 283 return array(); 284 } 285 return phutil_split_lines($stdout, $retain_newlines = false); 286 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 287 if ($all_closing_heads) { 288 list($stdout) = $this->getRepository()->execxLocalCommand( 289 'log --format=%s %s --not %Ls', 290 '%H', 291 $new_head, 292 $all_closing_heads); 293 } else { 294 list($stdout) = $this->getRepository()->execxLocalCommand( 295 'log --format=%s %s', 296 '%H', 297 $new_head); 298 } 299 300 $stdout = trim($stdout); 301 if (!strlen($stdout)) { 302 return array(); 303 } 304 return phutil_split_lines($stdout, $retain_newlines = false); 305 default: 306 throw new Exception(pht('Unsupported VCS "%s"!', $vcs)); 307 } 308 } 309 310 /** 311 * Mark a list of commits as closeable, and queue workers for those commits 312 * which don't already have the flag. 313 */ 314 private function setCloseFlagOnCommits(array $identifiers) { 315 $repository = $this->getRepository(); 316 $commit_table = new PhabricatorRepositoryCommit(); 317 $conn_w = $commit_table->establishConnection('w'); 318 319 $vcs = $repository->getVersionControlSystem(); 320 switch ($vcs) { 321 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 322 $class = 'PhabricatorRepositoryGitCommitMessageParserWorker'; 323 break; 324 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 325 $class = 'PhabricatorRepositorySvnCommitMessageParserWorker'; 326 break; 327 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 328 $class = 'PhabricatorRepositoryMercurialCommitMessageParserWorker'; 329 break; 330 default: 331 throw new Exception("Unknown repository type '{$vcs}'!"); 332 } 333 334 $all_commits = queryfx_all( 335 $conn_w, 336 'SELECT id, commitIdentifier, importStatus FROM %T 337 WHERE repositoryID = %d AND commitIdentifier IN (%Ls)', 338 $commit_table->getTableName(), 339 $repository->getID(), 340 $identifiers); 341 342 $closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE; 343 344 $all_commits = ipull($all_commits, null, 'commitIdentifier'); 345 foreach ($identifiers as $identifier) { 346 $row = idx($all_commits, $identifier); 347 348 if (!$row) { 349 throw new Exception( 350 pht( 351 'Commit "%s" has not been discovered yet! Run discovery before '. 352 'updating refs.', 353 $identifier)); 354 } 355 356 if (!($row['importStatus'] & $closeable_flag)) { 357 queryfx( 358 $conn_w, 359 'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d', 360 $commit_table->getTableName(), 361 $closeable_flag, 362 $row['id']); 363 364 $data = array( 365 'commitID' => $row['id'], 366 'only' => true, 367 ); 368 369 PhabricatorWorker::scheduleTask($class, $data); 370 } 371 } 372 } 373 374 375 /* -( Updating Git Refs )-------------------------------------------------- */ 376 377 378 /** 379 * @task git 380 */ 381 private function loadGitBranchPositions(PhabricatorRepository $repository) { 382 return id(new DiffusionLowLevelGitRefQuery()) 383 ->setRepository($repository) 384 ->withIsOriginBranch(true) 385 ->execute(); 386 } 387 388 389 /** 390 * @task git 391 */ 392 private function loadGitTagPositions(PhabricatorRepository $repository) { 393 return id(new DiffusionLowLevelGitRefQuery()) 394 ->setRepository($repository) 395 ->withIsTag(true) 396 ->execute(); 397 } 398 399 400 /* -( Updating Mercurial Refs )-------------------------------------------- */ 401 402 403 /** 404 * @task hg 405 */ 406 private function loadMercurialBranchPositions( 407 PhabricatorRepository $repository) { 408 return id(new DiffusionLowLevelMercurialBranchesQuery()) 409 ->setRepository($repository) 410 ->execute(); 411 } 412 413 414 /** 415 * @task hg 416 */ 417 private function loadMercurialBookmarkPositions( 418 PhabricatorRepository $repository) { 419 // TODO: Implement support for Mercurial bookmarks. 420 return array(); 421 } 422 423 }
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 |