protected final function updateCommitData($author, $message) { $commit = $this->commit; $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { $data = new PhabricatorRepositoryCommitData(); } $data->setCommitID($commit->getID()); $data->setAuthorName($author); $data->setCommitMessage($message); $repository = $this->repository; $detail_parser = $repository->getDetail('detail-parser', 'PhabricatorRepositoryDefaultCommitMessageDetailParser'); if ($detail_parser) { PhutilSymbolLoader::loadClass($detail_parser); $parser_obj = newv($detail_parser, array($commit, $data)); $parser_obj->parseCommitDetails(); } $data->save(); $revision_id = $data->getCommitDetail('differential.revisionID'); if ($revision_id) { $revision = id(new DifferentialRevision())->load($revision_id); if ($revision) { queryfx($revision->establishConnection('w'), 'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)', DifferentialRevision::TABLE_COMMIT, $revision->getID(), $commit->getPHID()); if ($revision->getStatus() != DifferentialRevisionStatus::COMMITTED) { $editor = new DifferentialCommentEditor($revision, $revision->getAuthorPHID(), DifferentialAction::ACTION_COMMIT); $editor->save(); } } } }
protected final function updateCommitData($author, $message) { $commit = $this->commit; $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { $data = new PhabricatorRepositoryCommitData(); } $data->setCommitID($commit->getID()); $data->setAuthorName($author); $data->setCommitMessage($message); $repository = $this->repository; $detail_parser = $repository->getDetail('detail-parser', 'PhabricatorRepositoryDefaultCommitMessageDetailParser'); if ($detail_parser) { PhutilSymbolLoader::loadClass($detail_parser); $parser_obj = newv($detail_parser, array($commit, $data)); $parser_obj->parseCommitDetails(); } $data->save(); $conn_w = id(new DifferentialRevision())->establishConnection('w'); // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, // preventing more than one revision from being associated with a commit. // Generally this is good and desirable, but with the advent of hash // tracking we may end up in a situation where we match several different // revisions. We just kind of ignore this and pick one, we might want to // revisit this and do something differently. (If we match several revisions // someone probably did something very silly, though.) $revision_id = $data->getCommitDetail('differential.revisionID'); if (!$revision_id) { $hashes = $this->getCommitHashes($this->repository, $this->commit); if ($hashes) { $query = new DifferentialRevisionQuery(); $query->withCommitHashes($hashes); $revisions = $query->execute(); if (!empty($revisions)) { $revision = $this->identifyBestRevision($revisions); $revision_id = $revision->getID(); } } } if ($revision_id) { $revision = id(new DifferentialRevision())->load($revision_id); if ($revision) { queryfx($conn_w, 'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)', DifferentialRevision::TABLE_COMMIT, $revision->getID(), $commit->getPHID()); if ($revision->getStatus() != DifferentialRevisionStatus::COMMITTED) { $message = null; $committer = $data->getCommitDetail('authorPHID'); if (!$committer) { $committer = $revision->getAuthorPHID(); $message = 'Change committed by ' . $data->getAuthorName() . '.'; } $editor = new DifferentialCommentEditor($revision, $committer, DifferentialAction::ACTION_COMMIT); $editor->setMessage($message)->save(); } } } }
private function recordCommit(PhabricatorRepository $repository, $commit_identifier, $epoch, $close_immediately, array $parents) { $commit = new PhabricatorRepositoryCommit(); $conn_w = $repository->establishConnection('w'); // First, try to revive an existing unreachable commit (if one exists) by // removing the "unreachable" flag. If we succeed, we don't need to do // anything else: we already discovered this commit some time ago. queryfx($conn_w, 'UPDATE %T SET importStatus = (importStatus & ~%d) WHERE repositoryID = %d AND commitIdentifier = %s', $commit->getTableName(), PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, $repository->getID(), $commit_identifier); if ($conn_w->getAffectedRows()) { $commit = $commit->loadOneWhere('repositoryID = %d AND commitIdentifier = %s', $repository->getID(), $commit_identifier); // After reviving a commit, schedule new daemons for it. $this->didDiscoverCommit($repository, $commit, $epoch); return; } $commit->setRepositoryID($repository->getID()); $commit->setCommitIdentifier($commit_identifier); $commit->setEpoch($epoch); if ($close_immediately) { $commit->setImportStatus(PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE); } $data = new PhabricatorRepositoryCommitData(); try { // If this commit has parents, look up their IDs. The parent commits // should always exist already. $parent_ids = array(); if ($parents) { $parent_rows = queryfx_all($conn_w, 'SELECT id, commitIdentifier FROM %T WHERE commitIdentifier IN (%Ls) AND repositoryID = %d', $commit->getTableName(), $parents, $repository->getID()); $parent_map = ipull($parent_rows, 'id', 'commitIdentifier'); foreach ($parents as $parent) { if (empty($parent_map[$parent])) { throw new Exception(pht('Unable to identify parent "%s"!', $parent)); } $parent_ids[] = $parent_map[$parent]; } } else { // Write an explicit 0 so we can distinguish between "really no // parents" and "data not available". if (!$repository->isSVN()) { $parent_ids = array(0); } } $commit->openTransaction(); $commit->save(); $data->setCommitID($commit->getID()); $data->save(); foreach ($parent_ids as $parent_id) { queryfx($conn_w, 'INSERT IGNORE INTO %T (childCommitID, parentCommitID) VALUES (%d, %d)', PhabricatorRepository::TABLE_PARENTS, $commit->getID(), $parent_id); } $commit->saveTransaction(); $this->didDiscoverCommit($repository, $commit, $epoch); if ($this->repairMode) { // Normally, the query should throw a duplicate key exception. If we // reach this in repair mode, we've actually performed a repair. $this->log(pht('Repaired commit "%s".', $commit_identifier)); } PhutilEventEngine::dispatchEvent(new PhabricatorEvent(PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT, array('repository' => $repository, 'commit' => $commit))); } catch (AphrontDuplicateKeyQueryException $ex) { $commit->killTransaction(); // Ignore. This can happen because we discover the same new commit // more than once when looking at history, or because of races or // data inconsistency or cosmic radiation; in any case, we're still // in a good state if we ignore the failure. } }
private function recordCommit(PhabricatorRepository $repository, $commit_identifier, $epoch, $close_immediately, array $parents) { $commit = new PhabricatorRepositoryCommit(); $commit->setRepositoryID($repository->getID()); $commit->setCommitIdentifier($commit_identifier); $commit->setEpoch($epoch); if ($close_immediately) { $commit->setImportStatus(PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE); } $data = new PhabricatorRepositoryCommitData(); $conn_w = $repository->establishConnection('w'); try { // If this commit has parents, look up their IDs. The parent commits // should always exist already. $parent_ids = array(); if ($parents) { $parent_rows = queryfx_all($conn_w, 'SELECT id, commitIdentifier FROM %T WHERE commitIdentifier IN (%Ls) AND repositoryID = %d', $commit->getTableName(), $parents, $repository->getID()); $parent_map = ipull($parent_rows, 'id', 'commitIdentifier'); foreach ($parents as $parent) { if (empty($parent_map[$parent])) { throw new Exception(pht('Unable to identify parent "%s"!', $parent)); } $parent_ids[] = $parent_map[$parent]; } } else { // Write an explicit 0 so we can distinguish between "really no // parents" and "data not available". if (!$repository->isSVN()) { $parent_ids = array(0); } } $commit->openTransaction(); $commit->save(); $data->setCommitID($commit->getID()); $data->save(); foreach ($parent_ids as $parent_id) { queryfx($conn_w, 'INSERT IGNORE INTO %T (childCommitID, parentCommitID) VALUES (%d, %d)', PhabricatorRepository::TABLE_PARENTS, $commit->getID(), $parent_id); } $commit->saveTransaction(); $this->insertTask($repository, $commit); queryfx($conn_w, 'INSERT INTO %T (repositoryID, size, lastCommitID, epoch) VALUES (%d, 1, %d, %d) ON DUPLICATE KEY UPDATE size = size + 1, lastCommitID = IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID), epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)', PhabricatorRepository::TABLE_SUMMARY, $repository->getID(), $commit->getID(), $epoch); if ($this->repairMode) { // Normally, the query should throw a duplicate key exception. If we // reach this in repair mode, we've actually performed a repair. $this->log(pht('Repaired commit "%s".', $commit_identifier)); } PhutilEventEngine::dispatchEvent(new PhabricatorEvent(PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT, array('repository' => $repository, 'commit' => $commit))); } catch (AphrontDuplicateKeyQueryException $ex) { $commit->killTransaction(); // Ignore. This can happen because we discover the same new commit // more than once when looking at history, or because of races or // data inconsistency or cosmic radiation; in any case, we're still // in a good state if we ignore the failure. } }
protected final function updateCommitData(DiffusionCommitRef $ref) { $commit = $this->commit; $author = $ref->getAuthor(); $message = $ref->getMessage(); $committer = $ref->getCommitter(); $hashes = $ref->getHashes(); $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { $data = new PhabricatorRepositoryCommitData(); } $data->setCommitID($commit->getID()); $data->setAuthorName(id(new PhutilUTF8StringTruncator())->setMaximumBytes(255)->truncateString((string) $author)); $data->setCommitDetail('authorName', $ref->getAuthorName()); $data->setCommitDetail('authorEmail', $ref->getAuthorEmail()); $data->setCommitDetail('authorPHID', $this->resolveUserPHID($commit, $author)); $data->setCommitMessage($message); if (strlen($committer)) { $data->setCommitDetail('committer', $committer); $data->setCommitDetail('committerName', $ref->getCommitterName()); $data->setCommitDetail('committerEmail', $ref->getCommitterEmail()); $data->setCommitDetail('committerPHID', $this->resolveUserPHID($commit, $committer)); } $repository = $this->repository; $author_phid = $data->getCommitDetail('authorPHID'); $committer_phid = $data->getCommitDetail('committerPHID'); $user = new PhabricatorUser(); if ($author_phid) { $user = $user->loadOneWhere('phid = %s', $author_phid); } $differential_app = 'PhabricatorDifferentialApplication'; $revision_id = null; $low_level_query = null; if (PhabricatorApplication::isClassInstalled($differential_app)) { $low_level_query = id(new DiffusionLowLevelCommitFieldsQuery())->setRepository($repository)->withCommitRef($ref); $field_values = $low_level_query->execute(); $revision_id = idx($field_values, 'revisionID'); if (!empty($field_values['reviewedByPHIDs'])) { $data->setCommitDetail('reviewerPHID', reset($field_values['reviewedByPHIDs'])); } $data->setCommitDetail('differential.revisionID', $revision_id); } if ($author_phid != $commit->getAuthorPHID()) { $commit->setAuthorPHID($author_phid); } $commit->setSummary($data->getSummary()); $commit->save(); // Figure out if we're going to try to "autoclose" related objects (e.g., // close linked tasks and related revisions) and, if not, record why we // aren't. Autoclose can be disabled for various reasons at the repository // or commit levels. $force_autoclose = idx($this->getTaskData(), 'forceAutoclose', false); if ($force_autoclose) { $autoclose_reason = PhabricatorRepository::BECAUSE_AUTOCLOSE_FORCED; } else { $autoclose_reason = $repository->shouldSkipAutocloseCommit($commit); } $data->setCommitDetail('autocloseReason', $autoclose_reason); $should_autoclose = $force_autoclose || $repository->shouldAutocloseCommit($commit); // When updating related objects, we'll act under an omnipotent user to // ensure we can see them, but take actions as either the committer or // author (if we recognize their accounts) or the Diffusion application // (if we do not). $actor = PhabricatorUser::getOmnipotentUser(); $acting_as_phid = nonempty($committer_phid, $author_phid, id(new PhabricatorDiffusionApplication())->getPHID()); $conn_w = id(new DifferentialRevision())->establishConnection('w'); // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, // preventing more than one revision from being associated with a commit. // Generally this is good and desirable, but with the advent of hash // tracking we may end up in a situation where we match several different // revisions. We just kind of ignore this and pick one, we might want to // revisit this and do something differently. (If we match several revisions // someone probably did something very silly, though.) $revision = null; if ($revision_id) { $revision_query = id(new DifferentialRevisionQuery())->withIDs(array($revision_id))->setViewer($actor)->needReviewerStatus(true)->needActiveDiffs(true); $revision = $revision_query->executeOne(); if ($revision) { if (!$data->getCommitDetail('precommitRevisionStatus')) { $data->setCommitDetail('precommitRevisionStatus', $revision->getStatus()); } $commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST; id(new PhabricatorEdgeEditor())->addEdge($commit->getPHID(), $commit_drev, $revision->getPHID())->save(); queryfx($conn_w, 'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)', DifferentialRevision::TABLE_COMMIT, $revision->getID(), $commit->getPHID()); $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; $should_close = $revision->getStatus() != $status_closed && $should_autoclose; if ($should_close) { $commit_close_xaction = id(new DifferentialTransaction())->setTransactionType(DifferentialTransaction::TYPE_ACTION)->setNewValue(DifferentialAction::ACTION_CLOSE)->setMetadataValue('isCommitClose', true); $commit_close_xaction->setMetadataValue('commitPHID', $commit->getPHID()); $commit_close_xaction->setMetadataValue('committerPHID', $committer_phid); $commit_close_xaction->setMetadataValue('committerName', $data->getCommitDetail('committer')); $commit_close_xaction->setMetadataValue('authorPHID', $author_phid); $commit_close_xaction->setMetadataValue('authorName', $data->getAuthorName()); if ($low_level_query) { $commit_close_xaction->setMetadataValue('revisionMatchData', $low_level_query->getRevisionMatchData()); $data->setCommitDetail('revisionMatchData', $low_level_query->getRevisionMatchData()); } $diff = $this->generateFinalDiff($revision, $acting_as_phid); $vs_diff = $this->loadChangedByCommit($revision, $diff); $changed_uri = null; if ($vs_diff) { $data->setCommitDetail('vsDiff', $vs_diff->getID()); $changed_uri = PhabricatorEnv::getProductionURI('/D' . $revision->getID() . '?vs=' . $vs_diff->getID() . '&id=' . $diff->getID() . '#toc'); } $xactions = array(); $xactions[] = id(new DifferentialTransaction())->setTransactionType(DifferentialTransaction::TYPE_UPDATE)->setIgnoreOnNoEffect(true)->setNewValue($diff->getPHID())->setMetadataValue('isCommitUpdate', true); $xactions[] = $commit_close_xaction; $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_DAEMON, array()); $editor = id(new DifferentialTransactionEditor())->setActor($actor)->setActingAsPHID($acting_as_phid)->setContinueOnMissingFields(true)->setContentSource($content_source)->setChangedPriorToCommitURI($changed_uri)->setIsCloseByCommit(true); try { $editor->applyTransactions($revision, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { // NOTE: We've marked transactions other than the CLOSE transaction // as ignored when they don't have an effect, so this means that we // lost a race to close the revision. That's perfectly fine, we can // just continue normally. } } } } if ($should_autoclose) { $this->closeTasks($actor, $acting_as_phid, $repository, $commit, $message); } $data->save(); $commit->writeImportStatusFlag(PhabricatorRepositoryCommit::IMPORTED_MESSAGE); }
private function lookupSvnCommits(PhabricatorRepository $repository, array $commits) { if (!$commits) { return array(); } $commit_table = new PhabricatorRepositoryCommit(); $commit_data = queryfx_all($commit_table->establishConnection('w'), 'SELECT id, commitIdentifier FROM %T WHERE repositoryID = %d AND commitIdentifier in (%Ls)', $commit_table->getTableName(), $repository->getID(), $commits); $commit_map = ipull($commit_data, 'id', 'commitIdentifier'); $need = array(); foreach ($commits as $commit) { if (empty($commit_map[$commit])) { $need[] = $commit; } } // If we are parsing a Subversion repository and have been configured to // import only some subdirectory of it, we may find commits which reference // other foreign commits outside of the directory (for instance, because of // a move or copy). Rather than trying to execute full parses on them, just // create stub commits and identify the stubs as foreign commits. if ($need) { $subpath = $repository->getDetail('svn-subpath'); if (!$subpath) { $commits = implode(', ', $need); throw new Exception("Missing commits ({$need}) in a SVN repository which is not " . "configured for subdirectory-only parsing!"); } foreach ($need as $foreign_commit) { $commit = new PhabricatorRepositoryCommit(); $commit->setRepositoryID($repository->getID()); $commit->setCommitIdentifier($foreign_commit); $commit->setEpoch(0); // Mark this commit as imported so it doesn't prevent the repository // from transitioning into the "Imported" state. $commit->setImportStatus(PhabricatorRepositoryCommit::IMPORTED_ALL); $commit->save(); $data = new PhabricatorRepositoryCommitData(); $data->setCommitID($commit->getID()); $data->setAuthorName(''); $data->setCommitMessage(''); $data->setCommitDetails(array('foreign-svn-stub' => true, 'svn-subpath' => $subpath)); $data->save(); $commit_map[$foreign_commit] = $commit->getID(); } } return $commit_map; }
private function updateCommit(PhabricatorRepository $repository, $commit_identifier, $branch) { $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere('repositoryID = %s AND commitIdentifier = %s', $repository->getID(), $commit_identifier); if (!$commit) { // This can happen if the phabricator DB doesn't have the commit info, // or the commit is so big that phabricator couldn't parse it. In this // case we just ignore it. return; } $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { $data = new PhabricatorRepositoryCommitData(); $data->setCommitID($commit->getID()); } $branches = $data->getCommitDetail('seenOnBranches', array()); $branches[] = $branch; $data->setCommitDetail('seenOnBranches', $branches); $data->save(); $this->insertTask($repository, $commit, array('only' => true)); }