Differential D18614 Diff 44694 src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
| <?php | <?php | ||||
| /** | /** | ||||
| * Update the ref cursors for a repository, which track the positions of | * Update the ref cursors for a repository, which track the positions of | ||||
| * branches, bookmarks, and tags. | * branches, bookmarks, and tags. | ||||
| */ | */ | ||||
| final class PhabricatorRepositoryRefEngine | final class PhabricatorRepositoryRefEngine | ||||
| extends PhabricatorRepositoryEngine { | extends PhabricatorRepositoryEngine { | ||||
| private $newRefs = array(); | private $newPositions = array(); | ||||
| private $deadRefs = array(); | private $deadPositions = array(); | ||||
| private $closeCommits = array(); | private $closeCommits = array(); | ||||
| private $hasNoCursors; | private $hasNoCursors; | ||||
| public function updateRefs() { | public function updateRefs() { | ||||
| $this->newRefs = array(); | $this->newPositions = array(); | ||||
| $this->deadRefs = array(); | $this->deadPositions = array(); | ||||
| $this->closeCommits = array(); | $this->closeCommits = array(); | ||||
| $repository = $this->getRepository(); | $repository = $this->getRepository(); | ||||
| $viewer = $this->getViewer(); | |||||
| $branches_may_close = false; | $branches_may_close = false; | ||||
| $vcs = $repository->getVersionControlSystem(); | $vcs = $repository->getVersionControlSystem(); | ||||
| switch ($vcs) { | switch ($vcs) { | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | ||||
| // No meaningful refs of any type in Subversion. | // No meaningful refs of any type in Subversion. | ||||
| $maps = array(); | $maps = array(); | ||||
| Show All 19 Lines | public function updateRefs() { | ||||
| $maps = $maps + array( | $maps = $maps + array( | ||||
| PhabricatorRepositoryRefCursor::TYPE_BRANCH => array(), | PhabricatorRepositoryRefCursor::TYPE_BRANCH => array(), | ||||
| PhabricatorRepositoryRefCursor::TYPE_TAG => array(), | PhabricatorRepositoryRefCursor::TYPE_TAG => array(), | ||||
| PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => array(), | PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => array(), | ||||
| PhabricatorRepositoryRefCursor::TYPE_REF => array(), | PhabricatorRepositoryRefCursor::TYPE_REF => array(), | ||||
| ); | ); | ||||
| $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) | $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) | ||||
| ->setViewer(PhabricatorUser::getOmnipotentUser()) | ->setViewer($viewer) | ||||
| ->withRepositoryPHIDs(array($repository->getPHID())) | ->withRepositoryPHIDs(array($repository->getPHID())) | ||||
| ->needPositions(true) | |||||
| ->execute(); | ->execute(); | ||||
| $cursor_groups = mgroup($all_cursors, 'getRefType'); | $cursor_groups = mgroup($all_cursors, 'getRefType'); | ||||
| $this->hasNoCursors = (!$all_cursors); | $this->hasNoCursors = (!$all_cursors); | ||||
| // Find all the heads of closing refs. | // Find all the heads of closing refs. | ||||
| $all_closing_heads = array(); | $all_closing_heads = array(); | ||||
| foreach ($all_cursors as $cursor) { | foreach ($all_cursors as $cursor) { | ||||
| if ($this->shouldCloseRef($cursor->getRefType(), $cursor->getRefName())) { | $should_close = $this->shouldCloseRef( | ||||
| $all_closing_heads[] = $cursor->getCommitIdentifier(); | $cursor->getRefType(), | ||||
| $cursor->getRefName()); | |||||
| if (!$should_close) { | |||||
| continue; | |||||
| } | |||||
| foreach ($cursor->getPositionIdentifiers() as $identifier) { | |||||
| $all_closing_heads[] = $identifier; | |||||
| } | } | ||||
| } | } | ||||
| $all_closing_heads = array_unique($all_closing_heads); | $all_closing_heads = array_unique($all_closing_heads); | ||||
| $all_closing_heads = $this->removeMissingCommits($all_closing_heads); | $all_closing_heads = $this->removeMissingCommits($all_closing_heads); | ||||
| foreach ($maps as $type => $refs) { | foreach ($maps as $type => $refs) { | ||||
| $cursor_group = idx($cursor_groups, $type, array()); | $cursor_group = idx($cursor_groups, $type, array()); | ||||
| $this->updateCursors($cursor_group, $refs, $type, $all_closing_heads); | $this->updateCursors($cursor_group, $refs, $type, $all_closing_heads); | ||||
| } | } | ||||
| if ($this->closeCommits) { | if ($this->closeCommits) { | ||||
| $this->setCloseFlagOnCommits($this->closeCommits); | $this->setCloseFlagOnCommits($this->closeCommits); | ||||
| } | } | ||||
| if ($this->newRefs || $this->deadRefs) { | if ($this->newPositions || $this->deadPositions) { | ||||
| $repository->openTransaction(); | $repository->openTransaction(); | ||||
| foreach ($this->newRefs as $ref) { | |||||
| $ref->save(); | |||||
| } | |||||
| foreach ($this->deadRefs as $ref) { | |||||
| // Shove this ref into the old refs table so the discovery engine | |||||
| // can check if any commits have been rendered unreachable. | |||||
| id(new PhabricatorRepositoryOldRef()) | |||||
| ->setRepositoryPHID($repository->getPHID()) | |||||
| ->setCommitIdentifier($ref->getCommitIdentifier()) | |||||
| ->save(); | |||||
| $ref->delete(); | $this->saveNewPositions(); | ||||
| } | $this->deleteDeadPositions(); | ||||
| $repository->saveTransaction(); | |||||
| $this->newRefs = array(); | $repository->saveTransaction(); | ||||
| $this->deadRefs = array(); | |||||
| } | } | ||||
| $branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH]; | $branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH]; | ||||
| if ($branches && $branches_may_close) { | if ($branches && $branches_may_close) { | ||||
| $this->updateBranchStates($repository, $branches); | $this->updateBranchStates($repository, $branches); | ||||
| } | } | ||||
| } | } | ||||
| private function updateBranchStates( | private function updateBranchStates( | ||||
| PhabricatorRepository $repository, | PhabricatorRepository $repository, | ||||
| array $branches) { | array $branches) { | ||||
| assert_instances_of($branches, 'DiffusionRepositoryRef'); | assert_instances_of($branches, 'DiffusionRepositoryRef'); | ||||
| $viewer = $this->getViewer(); | |||||
| $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) | $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) | ||||
| ->setViewer(PhabricatorUser::getOmnipotentUser()) | ->setViewer($viewer) | ||||
| ->withRepositoryPHIDs(array($repository->getPHID())) | ->withRepositoryPHIDs(array($repository->getPHID())) | ||||
| ->needPositions(true) | |||||
| ->execute(); | ->execute(); | ||||
| $state_map = array(); | $state_map = array(); | ||||
| $type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH; | $type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH; | ||||
| foreach ($all_cursors as $cursor) { | foreach ($all_cursors as $cursor) { | ||||
| if ($cursor->getRefType() !== $type_branch) { | if ($cursor->getRefType() !== $type_branch) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| $raw_name = $cursor->getRefNameRaw(); | $raw_name = $cursor->getRefNameRaw(); | ||||
| $hash = $cursor->getCommitIdentifier(); | |||||
| $state_map[$raw_name][$hash] = $cursor; | foreach ($cursor->getPositions() as $position) { | ||||
| $hash = $position->getCommitIdentifier(); | |||||
| $state_map[$raw_name][$hash] = $position; | |||||
| } | |||||
| } | } | ||||
| $updates = array(); | |||||
| foreach ($branches as $branch) { | foreach ($branches as $branch) { | ||||
| $cursor = idx($state_map, $branch->getShortName(), array()); | $position = idx($state_map, $branch->getShortName(), array()); | ||||
| $cursor = idx($cursor, $branch->getCommitIdentifier()); | $position = idx($position, $branch->getCommitIdentifier()); | ||||
| if (!$cursor) { | if (!$position) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| $fields = $branch->getRawFields(); | $fields = $branch->getRawFields(); | ||||
| $cursor_state = (bool)$cursor->getIsClosed(); | $position_state = (bool)$position->getIsClosed(); | ||||
| $branch_state = (bool)idx($fields, 'closed'); | $branch_state = (bool)idx($fields, 'closed'); | ||||
| if ($cursor_state != $branch_state) { | if ($position_state != $branch_state) { | ||||
| $cursor->setIsClosed((int)$branch_state)->save(); | $updates[$position->getID()] = (int)$branch_state; | ||||
| } | |||||
| } | |||||
| if ($updates) { | |||||
| $position_table = id(new PhabricatorRepositoryRefPosition()); | |||||
| $conn = $position_table->establishConnection('w'); | |||||
| $position_table->openTransaction(); | |||||
| foreach ($updates as $position_id => $branch_state) { | |||||
| queryfx( | |||||
| $conn, | |||||
| 'UPDATE %T SET isClosed = %d WHERE id = %d', | |||||
| $position_table->getTableName(), | |||||
| $branch_state, | |||||
| $position_id); | |||||
| } | } | ||||
| $position_table->saveTransaction(); | |||||
| } | } | ||||
| } | } | ||||
| private function markRefNew(PhabricatorRepositoryRefCursor $cursor) { | private function markPositionNew( | ||||
| $this->newRefs[] = $cursor; | PhabricatorRepositoryRefPosition $position) { | ||||
| $this->newPositions[] = $position; | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| private function markRefDead(PhabricatorRepositoryRefCursor $cursor) { | private function markPositionDead( | ||||
| $this->deadRefs[] = $cursor; | PhabricatorRepositoryRefPosition $position) { | ||||
| $this->deadPositions[] = $position; | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| private function markCloseCommits(array $identifiers) { | private function markCloseCommits(array $identifiers) { | ||||
| foreach ($identifiers as $identifier) { | foreach ($identifiers as $identifier) { | ||||
| $this->closeCommits[$identifier] = $identifier; | $this->closeCommits[$identifier] = $identifier; | ||||
| } | } | ||||
| return $this; | return $this; | ||||
| Show All 33 Lines | private function updateCursors( | ||||
| array $new_refs, | array $new_refs, | ||||
| $ref_type, | $ref_type, | ||||
| array $all_closing_heads) { | array $all_closing_heads) { | ||||
| $repository = $this->getRepository(); | $repository = $this->getRepository(); | ||||
| // NOTE: Mercurial branches may have multiple branch heads; this logic | // NOTE: Mercurial branches may have multiple branch heads; this logic | ||||
| // is complex primarily to account for that. | // is complex primarily to account for that. | ||||
| // Group all the cursors by their ref name, like "master". Since Mercurial | $cursors = mpull($cursors, null, 'getRefNameRaw'); | ||||
| // branches may have multiple heads, there could be several cursors with | |||||
| // the same name. | |||||
| $cursor_groups = mgroup($cursors, 'getRefNameRaw'); | |||||
| // Group all the new ref values by their name. As above, these groups may | // Group all the new ref values by their name. As above, these groups may | ||||
| // have multiple members in Mercurial. | // have multiple members in Mercurial. | ||||
| $ref_groups = mgroup($new_refs, 'getShortName'); | $ref_groups = mgroup($new_refs, 'getShortName'); | ||||
| foreach ($ref_groups as $name => $refs) { | foreach ($ref_groups as $name => $refs) { | ||||
| $new_commits = mpull($refs, 'getCommitIdentifier', 'getCommitIdentifier'); | $new_commits = mpull($refs, 'getCommitIdentifier', 'getCommitIdentifier'); | ||||
| $ref_cursors = idx($cursor_groups, $name, array()); | $ref_cursor = idx($cursors, $name); | ||||
| $old_commits = mpull($ref_cursors, null, 'getCommitIdentifier'); | if ($ref_cursor) { | ||||
| $old_positions = $ref_cursor->getPositions(); | |||||
| } else { | |||||
| $old_positions = array(); | |||||
| } | |||||
| // We're going to delete all the cursors pointing at commits which are | // We're going to delete all the cursors pointing at commits which are | ||||
| // no longer associated with the refs. This primarily makes the Mercurial | // no longer associated with the refs. This primarily makes the Mercurial | ||||
| // multiple head case easier, and means that when we update a ref we | // multiple head case easier, and means that when we update a ref we | ||||
| // delete the old one and write a new one. | // delete the old one and write a new one. | ||||
| foreach ($ref_cursors as $cursor) { | foreach ($old_positions as $old_position) { | ||||
| if (isset($new_commits[$cursor->getCommitIdentifier()])) { | $hash = $old_position->getCommitIdentifier(); | ||||
| if (isset($new_commits[$hash])) { | |||||
| // This ref previously pointed at this commit, and still does. | // This ref previously pointed at this commit, and still does. | ||||
| $this->log( | $this->log( | ||||
| pht( | pht( | ||||
| 'Ref %s "%s" still points at %s.', | 'Ref %s "%s" still points at %s.', | ||||
| $ref_type, | $ref_type, | ||||
| $name, | $name, | ||||
| $cursor->getCommitIdentifier())); | $hash)); | ||||
| } else { | continue; | ||||
| } | |||||
| // This ref previously pointed at this commit, but no longer does. | // This ref previously pointed at this commit, but no longer does. | ||||
| $this->log( | $this->log( | ||||
| pht( | pht( | ||||
| 'Ref %s "%s" no longer points at %s.', | 'Ref %s "%s" no longer points at %s.', | ||||
| $ref_type, | $ref_type, | ||||
| $name, | $name, | ||||
| $cursor->getCommitIdentifier())); | $hash)); | ||||
| // Nuke the obsolete cursor. | // Nuke the obsolete cursor. | ||||
| $this->markRefDead($cursor); | $this->markPositionDead($old_position); | ||||
| } | |||||
| } | } | ||||
| // Now, we're going to insert new cursors for all the commits which are | // Now, we're going to insert new cursors for all the commits which are | ||||
| // associated with this ref that don't currently have cursors. | // associated with this ref that don't currently have cursors. | ||||
| $old_commits = mpull($old_positions, 'getCommitIdentifier'); | |||||
| $old_commits = array_fuse($old_commits); | |||||
| $added_commits = array_diff_key($new_commits, $old_commits); | $added_commits = array_diff_key($new_commits, $old_commits); | ||||
| foreach ($added_commits as $identifier) { | foreach ($added_commits as $identifier) { | ||||
| $this->log( | $this->log( | ||||
| pht( | pht( | ||||
| 'Ref %s "%s" now points at %s.', | 'Ref %s "%s" now points at %s.', | ||||
| $ref_type, | $ref_type, | ||||
| $name, | $name, | ||||
| $identifier)); | $identifier)); | ||||
| $this->markRefNew( | |||||
| id(new PhabricatorRepositoryRefCursor()) | if (!$ref_cursor) { | ||||
| ->setRepositoryPHID($repository->getPHID()) | // If this is the first time we've seen a particular ref (for | ||||
| ->setRefType($ref_type) | // example, a new branch) we need to insert a RefCursor record | ||||
| ->setRefName($name) | // for it before we can insert a RefPosition. | ||||
| ->setCommitIdentifier($identifier)); | |||||
| $ref_cursor = $this->newRefCursor( | |||||
| $repository, | |||||
| $ref_type, | |||||
| $name); | |||||
| } | |||||
| $new_position = id(new PhabricatorRepositoryRefPosition()) | |||||
| ->setCursorID($ref_cursor->getID()) | |||||
| ->setCommitIdentifier($identifier) | |||||
| ->setIsClosed(0); | |||||
| $this->markPositionNew($new_position); | |||||
| } | } | ||||
| if ($this->shouldCloseRef($ref_type, $name)) { | if ($this->shouldCloseRef($ref_type, $name)) { | ||||
| foreach ($added_commits as $identifier) { | foreach ($added_commits as $identifier) { | ||||
| $new_identifiers = $this->loadNewCommitIdentifiers( | $new_identifiers = $this->loadNewCommitIdentifiers( | ||||
| $identifier, | $identifier, | ||||
| $all_closing_heads); | $all_closing_heads); | ||||
| $this->markCloseCommits($new_identifiers); | $this->markCloseCommits($new_identifiers); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| // Find any cursors for refs which no longer exist. This happens when a | // Find any cursors for refs which no longer exist. This happens when a | ||||
| // branch, tag or bookmark is deleted. | // branch, tag or bookmark is deleted. | ||||
| foreach ($cursor_groups as $name => $cursor_group) { | foreach ($cursors as $name => $cursor) { | ||||
| if (idx($ref_groups, $name) === null) { | if (!empty($ref_groups[$name])) { | ||||
| foreach ($cursor_group as $cursor) { | // This ref still has some positions, so we don't need to wipe it | ||||
| // out. Try the next one. | |||||
| continue; | |||||
| } | |||||
| foreach ($cursor->getPositions() as $position) { | |||||
| $this->log( | $this->log( | ||||
| pht( | pht( | ||||
| 'Ref %s "%s" no longer exists.', | 'Ref %s "%s" no longer exists.', | ||||
| $cursor->getRefType(), | $cursor->getRefType(), | ||||
| $cursor->getRefName())); | $cursor->getRefName())); | ||||
| $this->markRefDead($cursor); | |||||
| } | $this->markPositionDead($position); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private function shouldCloseRef($ref_type, $ref_name) { | private function shouldCloseRef($ref_type, $ref_name) { | ||||
| if ($ref_type !== PhabricatorRepositoryRefCursor::TYPE_BRANCH) { | if ($ref_type !== PhabricatorRepositoryRefCursor::TYPE_BRANCH) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 149 Lines • ▼ Show 20 Lines | foreach ($identifiers as $identifier) { | ||||
| PhabricatorWorker::scheduleTask($class, $data); | PhabricatorWorker::scheduleTask($class, $data); | ||||
| } | } | ||||
| } | } | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| private function newRefCursor( | |||||
| PhabricatorRepository $repository, | |||||
| $ref_type, | |||||
| $ref_name) { | |||||
| $cursor = id(new PhabricatorRepositoryRefCursor()) | |||||
| ->setRepositoryPHID($repository->getPHID()) | |||||
| ->setRefType($ref_type) | |||||
| ->setRefName($ref_name); | |||||
| try { | |||||
| return $cursor->save(); | |||||
| } catch (AphrontDuplicateKeyQueryException $ex) { | |||||
| // If we raced another daemon to create this position and lost the race, | |||||
| // load the cursor the other daemon created instead. | |||||
| } | |||||
| $viewer = $this->getViewer(); | |||||
| $cursor = id(new PhabricatorRepositoryRefCursorQuery()) | |||||
| ->setViewer($viewer) | |||||
| ->withRepositoryPHIDs(array($repository->getPHID())) | |||||
| ->withRefTypes(array($ref_type)) | |||||
| ->withRefNames(array($ref_name)) | |||||
| ->needPositions(true) | |||||
| ->executeOne(); | |||||
| if (!$cursor) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Failed to create a new ref cursor (for "%s", of type "%s", in '. | |||||
| 'repository "%s") because it collided with an existing cursor, '. | |||||
| 'but then failed to load that cursor.', | |||||
| $ref_name, | |||||
| $ref_type, | |||||
| $repository->getDisplayName())); | |||||
| } | |||||
| return $cursor; | |||||
| } | |||||
| private function saveNewPositions() { | |||||
| $positions = $this->newPositions; | |||||
| foreach ($positions as $position) { | |||||
| try { | |||||
| $position->save(); | |||||
| } catch (AphrontDuplicateKeyQueryException $ex) { | |||||
| // We may race another daemon to create this position. If we do, and | |||||
| // we lose the race, that's fine: the other daemon did our work for | |||||
| // us and we can continue. | |||||
| } | |||||
| } | |||||
| $this->newPositions = array(); | |||||
| } | |||||
| private function deleteDeadPositions() { | |||||
| $positions = $this->deadPositions; | |||||
| $repository = $this->getRepository(); | |||||
| foreach ($positions as $position) { | |||||
| // Shove this ref into the old refs table so the discovery engine | |||||
| // can check if any commits have been rendered unreachable. | |||||
| id(new PhabricatorRepositoryOldRef()) | |||||
| ->setRepositoryPHID($repository->getPHID()) | |||||
| ->setCommitIdentifier($position->getCommitIdentifier()) | |||||
| ->save(); | |||||
| $position->delete(); | |||||
| } | |||||
| $this->deadPositions = array(); | |||||
| } | |||||
| /* -( Updating Git Refs )-------------------------------------------------- */ | /* -( Updating Git Refs )-------------------------------------------------- */ | ||||
| /** | /** | ||||
| * @task git | * @task git | ||||
| */ | */ | ||||
| private function loadGitRefPositions(PhabricatorRepository $repository) { | private function loadGitRefPositions(PhabricatorRepository $repository) { | ||||
| Show All 32 Lines | |||||