diff --git a/resources/sql/autopatches/20170109.diff.01.commit.sql b/resources/sql/autopatches/20170109.diff.01.commit.sql new file mode 100644 index 0000000000..2a28900272 --- /dev/null +++ b/resources/sql/autopatches/20170109.diff.01.commit.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_diff + ADD commitPHID VARBINARY(64); diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index 05e19bbe71..952c76c63f 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -1,291 +1,305 @@ viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setAuthorPHID($author_phid) { $this->authorPHID = $author_phid; return $this; } public function getAuthorPHID() { return $this->authorPHID; } public function newDiffFromCommit(PhabricatorRepositoryCommit $commit) { $viewer = $this->getViewer(); + // If we already have an unattached diff for this commit, just reuse it. + // This stops us from repeatedly generating diffs if something goes wrong + // later in the process. See T10968 for context. + $existing_diffs = id(new DifferentialDiffQuery()) + ->setViewer($viewer) + ->withCommitPHIDs(array($commit->getPHID())) + ->withHasRevision(false) + ->needChangesets(true) + ->execute(); + if ($existing_diffs) { + return head($existing_diffs); + } + $repository = $commit->getRepository(); $identifier = $commit->getCommitIdentifier(); $monogram = $commit->getMonogram(); $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $viewer, 'repository' => $repository, )); $diff_info = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, $drequest, 'diffusion.rawdiffquery', array( 'commit' => $identifier, )); $file_phid = $diff_info['filePHID']; $diff_file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($file_phid)) ->executeOne(); if (!$diff_file) { throw new Exception( pht( 'Failed to load file ("%s") returned by "%s".', $file_phid, 'diffusion.rawdiffquery')); } $raw_diff = $diff_file->loadFileData(); // TODO: Support adds, deletes and moves under SVN. if (strlen($raw_diff)) { $changes = id(new ArcanistDiffParser())->parseDiff($raw_diff); } else { // This is an empty diff, maybe made with `git commit --allow-empty`. // NOTE: These diffs have the same tree hash as their ancestors, so // they may attach to revisions in an unexpected way. Just let this // happen for now, although it might make sense to special case it // eventually. $changes = array(); } $diff = DifferentialDiff::newFromRawChanges($viewer, $changes) ->setRepositoryPHID($repository->getPHID()) + ->setCommitPHID($commit->getPHID()) ->setCreationMethod('commit') ->setSourceControlSystem($repository->getVersionControlSystem()) ->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP) ->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP) ->setDateCreated($commit->getEpoch()) ->setDescription($monogram); $author_phid = $this->getAuthorPHID(); if ($author_phid !== null) { $diff->setAuthorPHID($author_phid); } $parents = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, $drequest, 'diffusion.commitparentsquery', array( 'commit' => $identifier, )); if ($parents) { $diff->setSourceControlBaseRevision(head($parents)); } // TODO: Attach binary files. return $diff->save(); } public function isDiffChangedBeforeCommit( PhabricatorRepositoryCommit $commit, DifferentialDiff $old, DifferentialDiff $new) { $viewer = $this->getViewer(); $repository = $commit->getRepository(); $identifier = $commit->getCommitIdentifier(); $vs_changesets = array(); foreach ($old->getChangesets() as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $old); $path = ltrim($path, '/'); $vs_changesets[$path] = $changeset; } $changesets = array(); foreach ($new->getChangesets() as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $new); $path = ltrim($path, '/'); $changesets[$path] = $changeset; } if (array_fill_keys(array_keys($changesets), true) != array_fill_keys(array_keys($vs_changesets), true)) { return true; } $file_phids = array(); foreach ($vs_changesets as $changeset) { $metadata = $changeset->getMetadata(); $file_phid = idx($metadata, 'new:binary-phid'); if ($file_phid) { $file_phids[$file_phid] = $file_phid; } } $files = array(); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); } foreach ($changesets as $path => $changeset) { $vs_changeset = $vs_changesets[$path]; $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); if ($file_phid) { if (!isset($files[$file_phid])) { return true; } $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $viewer, 'repository' => $repository, )); $response = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, $drequest, 'diffusion.filecontentquery', array( 'commit' => $identifier, 'path' => $path, )); $new_file_phid = $response['filePHID']; if (!$new_file_phid) { return true; } $new_file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($new_file_phid)) ->executeOne(); if (!$new_file) { return true; } if ($files[$file_phid]->loadFileData() != $new_file->loadFileData()) { return true; } } else { $context = implode("\n", $changeset->makeChangesWithContext()); $vs_context = implode("\n", $vs_changeset->makeChangesWithContext()); // We couldn't just compare $context and $vs_context because following // diffs will be considered different: // // -(empty line) // -echo 'test'; // (empty line) // // (empty line) // -echo "test"; // -(empty line) $hunk = id(new DifferentialModernHunk())->setChanges($context); $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { return true; } } } return false; } public function updateRevisionWithCommit( DifferentialRevision $revision, PhabricatorRepositoryCommit $commit, array $more_xactions, PhabricatorContentSource $content_source) { $viewer = $this->getViewer(); $result_data = array(); $new_diff = $this->newDiffFromCommit($commit); $old_diff = $revision->getActiveDiff(); $changed_uri = null; if ($old_diff) { $old_diff = id(new DifferentialDiffQuery()) ->setViewer($viewer) ->withIDs(array($old_diff->getID())) ->needChangesets(true) ->executeOne(); if ($old_diff) { $has_changed = $this->isDiffChangedBeforeCommit( $commit, $old_diff, $new_diff); if ($has_changed) { $result_data['vsDiff'] = $old_diff->getID(); $revision_monogram = $revision->getMonogram(); $old_id = $old_diff->getID(); $new_id = $new_diff->getID(); $changed_uri = "/{$revision_monogram}?vs={$old_id}&id={$new_id}#toc"; $changed_uri = PhabricatorEnv::getProductionURI($changed_uri); } } } $xactions = array(); $xactions[] = id(new DifferentialTransaction()) ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) ->setIgnoreOnNoEffect(true) ->setNewValue($new_diff->getPHID()) ->setMetadataValue('isCommitUpdate', true); foreach ($more_xactions as $more_xaction) { $xactions[] = $more_xaction; } $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) ->setContinueOnMissingFields(true) ->setContentSource($content_source) ->setChangedPriorToCommitURI($changed_uri) ->setIsCloseByCommit(true); $author_phid = $this->getAuthorPHID(); if ($author_phid !== null) { $editor->setActingAsPHID($author_phid); } 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. } return $result_data; } } diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php index 23c016446c..f779e40c26 100644 --- a/src/applications/differential/query/DifferentialDiffQuery.php +++ b/src/applications/differential/query/DifferentialDiffQuery.php @@ -1,139 +1,170 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withRevisionIDs(array $revision_ids) { $this->revisionIDs = $revision_ids; return $this; } + public function withCommitPHIDs(array $phids) { + $this->commitPHIDs = $phids; + return $this; + } + + public function withHasRevision($has_revision) { + $this->hasRevision = $has_revision; + return $this; + } + public function needChangesets($bool) { $this->needChangesets = $bool; return $this; } public function needProperties($need_properties) { $this->needProperties = $need_properties; return $this; } public function newResultObject() { return new DifferentialDiff(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $diffs) { $revision_ids = array_filter(mpull($diffs, 'getRevisionID')); $revisions = array(); if ($revision_ids) { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($this->getViewer()) ->withIDs($revision_ids) ->execute(); } foreach ($diffs as $key => $diff) { if (!$diff->getRevisionID()) { continue; } $revision = idx($revisions, $diff->getRevisionID()); if ($revision) { $diff->attachRevision($revision); continue; } unset($diffs[$key]); } if ($diffs && $this->needChangesets) { $diffs = $this->loadChangesets($diffs); } return $diffs; } protected function didFilterPage(array $diffs) { if ($this->needProperties) { $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID IN (%Ld)', mpull($diffs, 'getID')); $properties = mgroup($properties, 'getDiffID'); foreach ($diffs as $diff) { $map = idx($properties, $diff->getID(), array()); $map = mpull($map, 'getData', 'getName'); $diff->attachDiffProperties($map); } } return $diffs; } private function loadChangesets(array $diffs) { id(new DifferentialChangesetQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) ->withDiffs($diffs) ->needAttachToDiffs(true) ->needHunks(true) ->execute(); return $diffs; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->revisionIDs) { + if ($this->revisionIDs !== null) { $where[] = qsprintf( $conn, 'revisionID IN (%Ld)', $this->revisionIDs); } + if ($this->commitPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'commitPHID IN (%Ls)', + $this->commitPHIDs); + } + + if ($this->hasRevision !== null) { + if ($this->hasRevision) { + $where[] = qsprintf( + $conn, + 'revisionID IS NOT NULL'); + } else { + $where[] = qsprintf( + $conn, + 'revisionID IS NULL'); + } + } + return $where; } public function getQueryApplicationClass() { return 'PhabricatorDifferentialApplication'; } } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 891af35982..d8c2ea9786 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -1,716 +1,721 @@ true, self::CONFIG_COLUMN_SCHEMA => array( 'revisionID' => 'id?', 'authorPHID' => 'phid?', 'repositoryPHID' => 'phid?', 'sourceMachine' => 'text255?', 'sourcePath' => 'text255?', 'sourceControlSystem' => 'text64?', 'sourceControlBaseRevision' => 'text255?', 'sourceControlPath' => 'text255?', 'lintStatus' => 'uint32', 'unitStatus' => 'uint32', 'lineCount' => 'uint32', 'branch' => 'text255?', 'bookmark' => 'text255?', 'repositoryUUID' => 'text64?', + 'commitPHID' => 'phid?', // T6203/NULLABILITY // These should be non-null; all diffs should have a creation method // and the description should just be empty. 'creationMethod' => 'text255?', 'description' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( 'revisionID' => array( 'columns' => array('revisionID'), ), + 'key_commit' => array( + 'columns' => array('commitPHID'), + ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( DifferentialDiffPHIDType::TYPECONST); } public function addUnsavedChangeset(DifferentialChangeset $changeset) { if ($this->changesets === null) { $this->changesets = array(); } $this->unsavedChangesets[] = $changeset; $this->changesets[] = $changeset; return $this; } public function attachChangesets(array $changesets) { assert_instances_of($changesets, 'DifferentialChangeset'); $this->changesets = $changesets; return $this; } public function getChangesets() { return $this->assertAttached($this->changesets); } public function loadChangesets() { if (!$this->getID()) { return array(); } $changesets = id(new DifferentialChangeset())->loadAllWhere( 'diffID = %d', $this->getID()); foreach ($changesets as $changeset) { $changeset->attachDiff($this); } return $changesets; } public function save() { $this->openTransaction(); $ret = parent::save(); foreach ($this->unsavedChangesets as $changeset) { $changeset->setDiffID($this->getID()); $changeset->save(); } $this->saveTransaction(); return $ret; } public static function initializeNewDiff(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorDifferentialApplication')) ->executeOne(); $view_policy = $app->getPolicy( DifferentialDefaultViewCapability::CAPABILITY); $diff = id(new DifferentialDiff()) ->setViewPolicy($view_policy); return $diff; } public static function newFromRawChanges( PhabricatorUser $actor, array $changes) { assert_instances_of($changes, 'ArcanistDiffChange'); $diff = self::initializeNewDiff($actor); return self::buildChangesetsFromRawChanges($diff, $changes); } public static function newEphemeralFromRawChanges(array $changes) { assert_instances_of($changes, 'ArcanistDiffChange'); $diff = id(new DifferentialDiff())->makeEphemeral(); return self::buildChangesetsFromRawChanges($diff, $changes); } private static function buildChangesetsFromRawChanges( DifferentialDiff $diff, array $changes) { // There may not be any changes; initialize the changesets list so that // we don't throw later when accessing it. $diff->attachChangesets(array()); $lines = 0; foreach ($changes as $change) { if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) { // If a user pastes a diff into Differential which includes a commit // message (e.g., they ran `git show` to generate it), discard that // change when constructing a DifferentialDiff. continue; } $changeset = new DifferentialChangeset(); $add_lines = 0; $del_lines = 0; $first_line = PHP_INT_MAX; $hunks = $change->getHunks(); if ($hunks) { foreach ($hunks as $hunk) { $dhunk = new DifferentialModernHunk(); $dhunk->setOldOffset($hunk->getOldOffset()); $dhunk->setOldLen($hunk->getOldLength()); $dhunk->setNewOffset($hunk->getNewOffset()); $dhunk->setNewLen($hunk->getNewLength()); $dhunk->setChanges($hunk->getCorpus()); $changeset->addUnsavedHunk($dhunk); $add_lines += $hunk->getAddLines(); $del_lines += $hunk->getDelLines(); $added_lines = $hunk->getChangedLines('new'); if ($added_lines) { $first_line = min($first_line, head_key($added_lines)); } } $lines += $add_lines + $del_lines; } else { // This happens when you add empty files. $changeset->attachHunks(array()); } $metadata = $change->getAllMetadata(); if ($first_line != PHP_INT_MAX) { $metadata['line:first'] = $first_line; } $changeset->setOldFile($change->getOldPath()); $changeset->setFilename($change->getCurrentPath()); $changeset->setChangeType($change->getType()); $changeset->setFileType($change->getFileType()); $changeset->setMetadata($metadata); $changeset->setOldProperties($change->getOldProperties()); $changeset->setNewProperties($change->getNewProperties()); $changeset->setAwayPaths($change->getAwayPaths()); $changeset->setAddLines($add_lines); $changeset->setDelLines($del_lines); $diff->addUnsavedChangeset($changeset); } $diff->setLineCount($lines); $parser = new DifferentialChangesetParser(); $changesets = $parser->detectCopiedCode( $diff->getChangesets(), $min_width = 30, $min_lines = 3); $diff->attachChangesets($changesets); return $diff; } public function getDiffDict() { $dict = array( 'id' => $this->getID(), 'revisionID' => $this->getRevisionID(), 'dateCreated' => $this->getDateCreated(), 'dateModified' => $this->getDateModified(), 'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(), 'sourceControlPath' => $this->getSourceControlPath(), 'sourceControlSystem' => $this->getSourceControlSystem(), 'branch' => $this->getBranch(), 'bookmark' => $this->getBookmark(), 'creationMethod' => $this->getCreationMethod(), 'description' => $this->getDescription(), 'unitStatus' => $this->getUnitStatus(), 'lintStatus' => $this->getLintStatus(), 'changes' => array(), ); $dict['changes'] = $this->buildChangesList(); return $dict + $this->getDiffAuthorshipDict(); } public function getDiffAuthorshipDict() { $dict = array('properties' => array()); $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $this->getID()); foreach ($properties as $property) { $dict['properties'][$property->getName()] = $property->getData(); if ($property->getName() == 'local:commits') { foreach ($property->getData() as $commit) { $dict['authorName'] = $commit['author']; $dict['authorEmail'] = idx($commit, 'authorEmail'); break; } } } return $dict; } public function buildChangesList() { $changes = array(); foreach ($this->getChangesets() as $changeset) { $hunks = array(); foreach ($changeset->getHunks() as $hunk) { $hunks[] = array( 'oldOffset' => $hunk->getOldOffset(), 'newOffset' => $hunk->getNewOffset(), 'oldLength' => $hunk->getOldLen(), 'newLength' => $hunk->getNewLen(), 'addLines' => null, 'delLines' => null, 'isMissingOldNewline' => null, 'isMissingNewNewline' => null, 'corpus' => $hunk->getChanges(), ); } $change = array( 'id' => $changeset->getID(), 'metadata' => $changeset->getMetadata(), 'oldPath' => $changeset->getOldFile(), 'currentPath' => $changeset->getFilename(), 'awayPaths' => $changeset->getAwayPaths(), 'oldProperties' => $changeset->getOldProperties(), 'newProperties' => $changeset->getNewProperties(), 'type' => $changeset->getChangeType(), 'fileType' => $changeset->getFileType(), 'commitHash' => null, 'addLines' => $changeset->getAddLines(), 'delLines' => $changeset->getDelLines(), 'hunks' => $hunks, ); $changes[] = $change; } return $changes; } public function hasRevision() { return $this->revision !== self::ATTACHABLE; } public function getRevision() { return $this->assertAttached($this->revision); } public function attachRevision(DifferentialRevision $revision = null) { $this->revision = $revision; return $this; } public function attachProperty($key, $value) { $this->properties[$key] = $value; return $this; } public function getProperty($key) { return $this->assertAttachedKey($this->properties, $key); } public function hasDiffProperty($key) { $properties = $this->getDiffProperties(); return array_key_exists($key, $properties); } public function attachDiffProperties(array $properties) { $this->properties = $properties; return $this; } public function getDiffProperties() { return $this->assertAttached($this->properties); } public function attachBuildable(HarbormasterBuildable $buildable = null) { $this->buildable = $buildable; return $this; } public function getBuildable() { return $this->assertAttached($this->buildable); } public function getBuildTargetPHIDs() { $buildable = $this->getBuildable(); if (!$buildable) { return array(); } $target_phids = array(); foreach ($buildable->getBuilds() as $build) { foreach ($build->getBuildTargets() as $target) { $target_phids[] = $target->getPHID(); } } return $target_phids; } public function loadCoverageMap(PhabricatorUser $viewer) { $target_phids = $this->getBuildTargetPHIDs(); if (!$target_phids) { return array(); } $unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere( 'buildTargetPHID IN (%Ls)', $target_phids); $map = array(); foreach ($unit as $message) { $coverage = $message->getProperty('coverage', array()); foreach ($coverage as $path => $coverage_data) { $map[$path][] = $coverage_data; } } foreach ($map as $path => $coverage_items) { $map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items); } return $map; } public function getURI() { $id = $this->getID(); return "/differential/diff/{$id}/"; } public function attachUnitMessages(array $unit_messages) { $this->unitMessages = $unit_messages; return $this; } public function getUnitMessages() { return $this->assertAttached($this->unitMessages); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { if ($this->hasRevision()) { return PhabricatorPolicies::getMostOpenPolicy(); } return $this->viewPolicy; } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { if ($this->hasRevision()) { return $this->getRevision()->hasAutomaticCapability($capability, $viewer); } return ($this->getAuthorPHID() == $viewer->getPHID()); } public function describeAutomaticCapability($capability) { if ($this->hasRevision()) { return pht( 'This diff is attached to a revision, and inherits its policies.'); } return pht('The author of a diff can see it.'); } /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ public function getExtendedPolicy($capability, PhabricatorUser $viewer) { $extended = array(); switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if ($this->hasRevision()) { $extended[] = array( $this->getRevision(), PhabricatorPolicyCapability::CAN_VIEW, ); } break; } return $extended; } /* -( HarbormasterBuildableInterface )------------------------------------- */ public function getHarbormasterBuildableDisplayPHID() { $container_phid = $this->getHarbormasterContainerPHID(); if ($container_phid) { return $container_phid; } return $this->getHarbormasterBuildablePHID(); } public function getHarbormasterBuildablePHID() { return $this->getPHID(); } public function getHarbormasterContainerPHID() { if ($this->getRevisionID()) { $revision = id(new DifferentialRevision())->load($this->getRevisionID()); if ($revision) { return $revision->getPHID(); } } return null; } public function getHarbormasterPublishablePHID() { return $this->getHarbormasterContainerPHID(); } public function getBuildVariables() { $results = array(); $results['buildable.diff'] = $this->getID(); if ($this->revisionID) { $revision = $this->getRevision(); $results['buildable.revision'] = $revision->getID(); $repo = $revision->getRepository(); if ($repo) { $results['repository.callsign'] = $repo->getCallsign(); $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); $results['repository.staging.uri'] = $repo->getStagingURI(); $results['repository.staging.ref'] = $this->getStagingRef(); } } return $results; } public function getAvailableBuildVariables() { return array( 'buildable.diff' => pht('The differential diff ID, if applicable.'), 'buildable.revision' => pht('The differential revision ID, if applicable.'), 'repository.callsign' => pht('The callsign of the repository in Phabricator.'), 'repository.phid' => pht('The PHID of the repository in Phabricator.'), 'repository.vcs' => pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => pht('The URI to clone or checkout the repository from.'), 'repository.staging.uri' => pht('The URI of the staging repository.'), 'repository.staging.ref' => pht('The ref name for this change in the staging repository.'), ); } /* -( HarbormasterCircleCIBuildableInterface )----------------------------- */ public function getCircleCIGitHubRepositoryURI() { $diff_phid = $this->getPHID(); $repository_phid = $this->getRepositoryPHID(); if (!$repository_phid) { throw new Exception( pht( 'This diff ("%s") is not associated with a repository. A diff '. 'must belong to a tracked repository to be built by CircleCI.', $diff_phid)); } $repository = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($repository_phid)) ->executeOne(); if (!$repository) { throw new Exception( pht( 'This diff ("%s") is associated with a repository ("%s") which '. 'could not be loaded.', $diff_phid, $repository_phid)); } $staging_uri = $repository->getStagingURI(); if (!$staging_uri) { throw new Exception( pht( 'This diff ("%s") is associated with a repository ("%s") that '. 'does not have a Staging Area configured. You must configure a '. 'Staging Area to use CircleCI integration.', $diff_phid, $repository_phid)); } $path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath( $staging_uri); if (!$path) { throw new Exception( pht( 'This diff ("%s") is associated with a repository ("%s") that '. 'does not have a Staging Area ("%s") that is hosted on GitHub. '. 'CircleCI can only build from GitHub, so the Staging Area for '. 'the repository must be hosted there.', $diff_phid, $repository_phid, $staging_uri)); } return $staging_uri; } public function getCircleCIBuildIdentifierType() { return 'tag'; } public function getCircleCIBuildIdentifier() { $ref = $this->getStagingRef(); $ref = preg_replace('(^refs/tags/)', '', $ref); return $ref; } public function getStagingRef() { // TODO: We're just hoping to get lucky. Instead, `arc` should store // where it sent changes and we should only provide staging details // if we reasonably believe they are accurate. return 'refs/tags/phabricator/diff/'.$this->getID(); } public function loadTargetBranch() { // TODO: This is sketchy, but just eat the query cost until this can get // cleaned up. // For now, we're only returning a target if there's exactly one and it's // a branch, since we don't support landing to more esoteric targets like // tags yet. $property = id(new DifferentialDiffProperty())->loadOneWhere( 'diffID = %d AND name = %s', $this->getID(), 'arc:onto'); if (!$property) { return null; } $data = $property->getData(); if (!$data) { return null; } if (!is_array($data)) { return null; } if (count($data) != 1) { return null; } $onto = head($data); if (!is_array($onto)) { return null; } $type = idx($onto, 'type'); if ($type != 'branch') { return null; } return idx($onto, 'name'); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new DifferentialDiffEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new DifferentialDiffTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); foreach ($this->loadChangesets() as $changeset) { $changeset->delete(); } $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $this->getID()); foreach ($properties as $prop) { $prop->delete(); } $this->saveTransaction(); } }