Path: blob/master/src/applications/differential/storage/DifferentialDiff.php
12256 views
<?php12final class DifferentialDiff3extends DifferentialDAO4implements5PhabricatorPolicyInterface,6PhabricatorExtendedPolicyInterface,7HarbormasterBuildableInterface,8HarbormasterCircleCIBuildableInterface,9HarbormasterBuildkiteBuildableInterface,10PhabricatorApplicationTransactionInterface,11PhabricatorDestructibleInterface,12PhabricatorConduitResultInterface {1314protected $revisionID;15protected $authorPHID;16protected $repositoryPHID;17protected $commitPHID;1819protected $sourceMachine;20protected $sourcePath;2122protected $sourceControlSystem;23protected $sourceControlBaseRevision;24protected $sourceControlPath;2526protected $lintStatus;27protected $unitStatus;2829protected $lineCount;3031protected $branch;32protected $bookmark;3334protected $creationMethod;35protected $repositoryUUID;3637protected $description;3839protected $viewPolicy;4041private $unsavedChangesets = array();42private $changesets = self::ATTACHABLE;43private $revision = self::ATTACHABLE;44private $properties = self::ATTACHABLE;45private $buildable = self::ATTACHABLE;4647private $unitMessages = self::ATTACHABLE;4849protected function getConfiguration() {50return array(51self::CONFIG_AUX_PHID => true,52self::CONFIG_COLUMN_SCHEMA => array(53'revisionID' => 'id?',54'authorPHID' => 'phid?',55'repositoryPHID' => 'phid?',56'sourceMachine' => 'text255?',57'sourcePath' => 'text255?',58'sourceControlSystem' => 'text64?',59'sourceControlBaseRevision' => 'text255?',60'sourceControlPath' => 'text255?',61'lintStatus' => 'uint32',62'unitStatus' => 'uint32',63'lineCount' => 'uint32',64'branch' => 'text255?',65'bookmark' => 'text255?',66'repositoryUUID' => 'text64?',67'commitPHID' => 'phid?',6869// T6203/NULLABILITY70// These should be non-null; all diffs should have a creation method71// and the description should just be empty.72'creationMethod' => 'text255?',73'description' => 'text255?',74),75self::CONFIG_KEY_SCHEMA => array(76'revisionID' => array(77'columns' => array('revisionID'),78),79'key_commit' => array(80'columns' => array('commitPHID'),81),82),83) + parent::getConfiguration();84}8586public function generatePHID() {87return PhabricatorPHID::generateNewPHID(88DifferentialDiffPHIDType::TYPECONST);89}9091public function addUnsavedChangeset(DifferentialChangeset $changeset) {92if ($this->changesets === null) {93$this->changesets = array();94}95$this->unsavedChangesets[] = $changeset;96$this->changesets[] = $changeset;97return $this;98}99100public function attachChangesets(array $changesets) {101assert_instances_of($changesets, 'DifferentialChangeset');102$this->changesets = $changesets;103return $this;104}105106public function getChangesets() {107return $this->assertAttached($this->changesets);108}109110public function loadChangesets() {111if (!$this->getID()) {112return array();113}114$changesets = id(new DifferentialChangeset())->loadAllWhere(115'diffID = %d',116$this->getID());117118foreach ($changesets as $changeset) {119$changeset->attachDiff($this);120}121122return $changesets;123}124125public function save() {126$this->openTransaction();127$ret = parent::save();128foreach ($this->unsavedChangesets as $changeset) {129$changeset->setDiffID($this->getID());130$changeset->save();131}132$this->saveTransaction();133return $ret;134}135136public static function initializeNewDiff(PhabricatorUser $actor) {137$app = id(new PhabricatorApplicationQuery())138->setViewer($actor)139->withClasses(array('PhabricatorDifferentialApplication'))140->executeOne();141$view_policy = $app->getPolicy(142DifferentialDefaultViewCapability::CAPABILITY);143144$diff = id(new DifferentialDiff())145->setViewPolicy($view_policy);146147return $diff;148}149150public static function newFromRawChanges(151PhabricatorUser $actor,152array $changes) {153154assert_instances_of($changes, 'ArcanistDiffChange');155156$diff = self::initializeNewDiff($actor);157return self::buildChangesetsFromRawChanges($diff, $changes);158}159160public static function newEphemeralFromRawChanges(array $changes) {161assert_instances_of($changes, 'ArcanistDiffChange');162163$diff = id(new DifferentialDiff())->makeEphemeral();164return self::buildChangesetsFromRawChanges($diff, $changes);165}166167private static function buildChangesetsFromRawChanges(168DifferentialDiff $diff,169array $changes) {170171// There may not be any changes; initialize the changesets list so that172// we don't throw later when accessing it.173$diff->attachChangesets(array());174175$lines = 0;176foreach ($changes as $change) {177if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) {178// If a user pastes a diff into Differential which includes a commit179// message (e.g., they ran `git show` to generate it), discard that180// change when constructing a DifferentialDiff.181continue;182}183184$changeset = new DifferentialChangeset();185$add_lines = 0;186$del_lines = 0;187$first_line = PHP_INT_MAX;188$hunks = $change->getHunks();189if ($hunks) {190foreach ($hunks as $hunk) {191$dhunk = new DifferentialHunk();192$dhunk->setOldOffset($hunk->getOldOffset());193$dhunk->setOldLen($hunk->getOldLength());194$dhunk->setNewOffset($hunk->getNewOffset());195$dhunk->setNewLen($hunk->getNewLength());196$dhunk->setChanges($hunk->getCorpus());197$changeset->addUnsavedHunk($dhunk);198$add_lines += $hunk->getAddLines();199$del_lines += $hunk->getDelLines();200$added_lines = $hunk->getChangedLines('new');201if ($added_lines) {202$first_line = min($first_line, head_key($added_lines));203}204}205$lines += $add_lines + $del_lines;206} else {207// This happens when you add empty files.208$changeset->attachHunks(array());209}210211$metadata = $change->getAllMetadata();212if ($first_line != PHP_INT_MAX) {213$metadata['line:first'] = $first_line;214}215216$changeset->setOldFile($change->getOldPath());217$changeset->setFilename($change->getCurrentPath());218$changeset->setChangeType($change->getType());219220$changeset->setFileType($change->getFileType());221$changeset->setMetadata($metadata);222$changeset->setOldProperties($change->getOldProperties());223$changeset->setNewProperties($change->getNewProperties());224$changeset->setAwayPaths($change->getAwayPaths());225$changeset->setAddLines($add_lines);226$changeset->setDelLines($del_lines);227228$diff->addUnsavedChangeset($changeset);229}230$diff->setLineCount($lines);231232$changesets = $diff->getChangesets();233234// TODO: This is "safe", but it would be better to propagate a real user235// down the stack.236$viewer = PhabricatorUser::getOmnipotentUser();237238id(new DifferentialChangesetEngine())239->setViewer($viewer)240->rebuildChangesets($changesets);241242return $diff;243}244245public function getDiffDict() {246$dict = array(247'id' => $this->getID(),248'revisionID' => $this->getRevisionID(),249'dateCreated' => $this->getDateCreated(),250'dateModified' => $this->getDateModified(),251'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(),252'sourceControlPath' => $this->getSourceControlPath(),253'sourceControlSystem' => $this->getSourceControlSystem(),254'branch' => $this->getBranch(),255'bookmark' => $this->getBookmark(),256'creationMethod' => $this->getCreationMethod(),257'description' => $this->getDescription(),258'unitStatus' => $this->getUnitStatus(),259'lintStatus' => $this->getLintStatus(),260'changes' => array(),261);262263$dict['changes'] = $this->buildChangesList();264265return $dict + $this->getDiffAuthorshipDict();266}267268public function getDiffAuthorshipDict() {269$dict = array('properties' => array());270271$properties = id(new DifferentialDiffProperty())->loadAllWhere(272'diffID = %d',273$this->getID());274foreach ($properties as $property) {275$dict['properties'][$property->getName()] = $property->getData();276277if ($property->getName() == 'local:commits') {278foreach ($property->getData() as $commit) {279$dict['authorName'] = $commit['author'];280$dict['authorEmail'] = idx($commit, 'authorEmail');281break;282}283}284}285286return $dict;287}288289public function buildChangesList() {290$changes = array();291foreach ($this->getChangesets() as $changeset) {292$hunks = array();293foreach ($changeset->getHunks() as $hunk) {294$hunks[] = array(295'oldOffset' => $hunk->getOldOffset(),296'newOffset' => $hunk->getNewOffset(),297'oldLength' => $hunk->getOldLen(),298'newLength' => $hunk->getNewLen(),299'addLines' => null,300'delLines' => null,301'isMissingOldNewline' => null,302'isMissingNewNewline' => null,303'corpus' => $hunk->getChanges(),304);305}306$change = array(307'id' => $changeset->getID(),308'metadata' => $changeset->getMetadata(),309'oldPath' => $changeset->getOldFile(),310'currentPath' => $changeset->getFilename(),311'awayPaths' => $changeset->getAwayPaths(),312'oldProperties' => $changeset->getOldProperties(),313'newProperties' => $changeset->getNewProperties(),314'type' => $changeset->getChangeType(),315'fileType' => $changeset->getFileType(),316'commitHash' => null,317'addLines' => $changeset->getAddLines(),318'delLines' => $changeset->getDelLines(),319'hunks' => $hunks,320);321$changes[] = $change;322}323return $changes;324}325326public function hasRevision() {327return $this->revision !== self::ATTACHABLE;328}329330public function getRevision() {331return $this->assertAttached($this->revision);332}333334public function attachRevision(DifferentialRevision $revision = null) {335$this->revision = $revision;336return $this;337}338339public function attachProperty($key, $value) {340if (!is_array($this->properties)) {341$this->properties = array();342}343$this->properties[$key] = $value;344return $this;345}346347public function getProperty($key) {348return $this->assertAttachedKey($this->properties, $key);349}350351public function hasDiffProperty($key) {352$properties = $this->getDiffProperties();353return array_key_exists($key, $properties);354}355356public function attachDiffProperties(array $properties) {357$this->properties = $properties;358return $this;359}360361public function getDiffProperties() {362return $this->assertAttached($this->properties);363}364365public function attachBuildable(HarbormasterBuildable $buildable = null) {366$this->buildable = $buildable;367return $this;368}369370public function getBuildable() {371return $this->assertAttached($this->buildable);372}373374public function getBuildTargetPHIDs() {375$buildable = $this->getBuildable();376377if (!$buildable) {378return array();379}380381$target_phids = array();382foreach ($buildable->getBuilds() as $build) {383foreach ($build->getBuildTargets() as $target) {384$target_phids[] = $target->getPHID();385}386}387388return $target_phids;389}390391public function loadCoverageMap(PhabricatorUser $viewer) {392$target_phids = $this->getBuildTargetPHIDs();393if (!$target_phids) {394return array();395}396397$unit = id(new HarbormasterBuildUnitMessageQuery())398->setViewer($viewer)399->withBuildTargetPHIDs($target_phids)400->execute();401402$map = array();403foreach ($unit as $message) {404$coverage = $message->getProperty('coverage', array());405foreach ($coverage as $path => $coverage_data) {406$map[$path][] = $coverage_data;407}408}409410foreach ($map as $path => $coverage_items) {411$map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items);412}413414return $map;415}416417public function getURI() {418$id = $this->getID();419return "/differential/diff/{$id}/";420}421422423public function attachUnitMessages(array $unit_messages) {424$this->unitMessages = $unit_messages;425return $this;426}427428429public function getUnitMessages() {430return $this->assertAttached($this->unitMessages);431}432433434/* -( PhabricatorPolicyInterface )----------------------------------------- */435436437public function getCapabilities() {438return array(439PhabricatorPolicyCapability::CAN_VIEW,440);441}442443public function getPolicy($capability) {444if ($this->hasRevision()) {445return PhabricatorPolicies::getMostOpenPolicy();446}447448return $this->viewPolicy;449}450451public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {452if ($this->hasRevision()) {453return $this->getRevision()->hasAutomaticCapability($capability, $viewer);454}455456return ($this->getAuthorPHID() == $viewer->getPHID());457}458459public function describeAutomaticCapability($capability) {460if ($this->hasRevision()) {461return pht(462'This diff is attached to a revision, and inherits its policies.');463}464465return pht('The author of a diff can see it.');466}467468469/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */470471472public function getExtendedPolicy($capability, PhabricatorUser $viewer) {473$extended = array();474475switch ($capability) {476case PhabricatorPolicyCapability::CAN_VIEW:477if ($this->hasRevision()) {478$extended[] = array(479$this->getRevision(),480PhabricatorPolicyCapability::CAN_VIEW,481);482} else if ($this->getRepositoryPHID()) {483$extended[] = array(484$this->getRepositoryPHID(),485PhabricatorPolicyCapability::CAN_VIEW,486);487}488break;489}490491return $extended;492}493494495/* -( HarbormasterBuildableInterface )------------------------------------- */496497498public function getHarbormasterBuildableDisplayPHID() {499$container_phid = $this->getHarbormasterContainerPHID();500if ($container_phid) {501return $container_phid;502}503504return $this->getHarbormasterBuildablePHID();505}506507public function getHarbormasterBuildablePHID() {508return $this->getPHID();509}510511public function getHarbormasterContainerPHID() {512if ($this->getRevisionID()) {513$revision = id(new DifferentialRevision())->load($this->getRevisionID());514if ($revision) {515return $revision->getPHID();516}517}518519return null;520}521522public function getBuildVariables() {523$results = array();524525$results['buildable.diff'] = $this->getID();526if ($this->revisionID) {527$revision = $this->getRevision();528$results['buildable.revision'] = $revision->getID();529$repo = $revision->getRepository();530531if ($repo) {532$results['repository.callsign'] = $repo->getCallsign();533$results['repository.phid'] = $repo->getPHID();534$results['repository.vcs'] = $repo->getVersionControlSystem();535$results['repository.uri'] = $repo->getPublicCloneURI();536537$results['repository.staging.uri'] = $repo->getStagingURI();538$results['repository.staging.ref'] = $this->getStagingRef();539}540}541542return $results;543}544545public function getAvailableBuildVariables() {546return array(547'buildable.diff' =>548pht('The differential diff ID, if applicable.'),549'buildable.revision' =>550pht('The differential revision ID, if applicable.'),551'repository.callsign' =>552pht('The callsign of the repository.'),553'repository.phid' =>554pht('The PHID of the repository.'),555'repository.vcs' =>556pht('The version control system, either "svn", "hg" or "git".'),557'repository.uri' =>558pht('The URI to clone or checkout the repository from.'),559'repository.staging.uri' =>560pht('The URI of the staging repository.'),561'repository.staging.ref' =>562pht('The ref name for this change in the staging repository.'),563);564}565566public function newBuildableEngine() {567return new DifferentialBuildableEngine();568}569570571/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */572573574public function getCircleCIGitHubRepositoryURI() {575$diff_phid = $this->getPHID();576$repository_phid = $this->getRepositoryPHID();577if (!$repository_phid) {578throw new Exception(579pht(580'This diff ("%s") is not associated with a repository. A diff '.581'must belong to a tracked repository to be built by CircleCI.',582$diff_phid));583}584585$repository = id(new PhabricatorRepositoryQuery())586->setViewer(PhabricatorUser::getOmnipotentUser())587->withPHIDs(array($repository_phid))588->executeOne();589if (!$repository) {590throw new Exception(591pht(592'This diff ("%s") is associated with a repository ("%s") which '.593'could not be loaded.',594$diff_phid,595$repository_phid));596}597598$staging_uri = $repository->getStagingURI();599if (!$staging_uri) {600throw new Exception(601pht(602'This diff ("%s") is associated with a repository ("%s") that '.603'does not have a Staging Area configured. You must configure a '.604'Staging Area to use CircleCI integration.',605$diff_phid,606$repository_phid));607}608609$path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath(610$staging_uri);611if (!$path) {612throw new Exception(613pht(614'This diff ("%s") is associated with a repository ("%s") that '.615'does not have a Staging Area ("%s") that is hosted on GitHub. '.616'CircleCI can only build from GitHub, so the Staging Area for '.617'the repository must be hosted there.',618$diff_phid,619$repository_phid,620$staging_uri));621}622623return $staging_uri;624}625626public function getCircleCIBuildIdentifierType() {627return 'tag';628}629630public function getCircleCIBuildIdentifier() {631$ref = $this->getStagingRef();632$ref = preg_replace('(^refs/tags/)', '', $ref);633return $ref;634}635636637/* -( HarbormasterBuildkiteBuildableInterface )---------------------------- */638639public function getBuildkiteBranch() {640$ref = $this->getStagingRef();641642// NOTE: Circa late January 2017, Buildkite fails with the error message643// "Tags have been disabled for this project" if we pass the "refs/tags/"644// prefix via the API and the project doesn't have GitHub tag builds645// enabled, even if GitHub builds are disabled. The tag builds fine646// without this prefix.647$ref = preg_replace('(^refs/tags/)', '', $ref);648649return $ref;650}651652public function getBuildkiteCommit() {653return 'HEAD';654}655656657public function getStagingRef() {658// TODO: We're just hoping to get lucky. Instead, `arc` should store659// where it sent changes and we should only provide staging details660// if we reasonably believe they are accurate.661return 'refs/tags/phabricator/diff/'.$this->getID();662}663664public function loadTargetBranch() {665// TODO: This is sketchy, but just eat the query cost until this can get666// cleaned up.667668// For now, we're only returning a target if there's exactly one and it's669// a branch, since we don't support landing to more esoteric targets like670// tags yet.671672$property = id(new DifferentialDiffProperty())->loadOneWhere(673'diffID = %d AND name = %s',674$this->getID(),675'arc:onto');676if (!$property) {677return null;678}679680$data = $property->getData();681682if (!$data) {683return null;684}685686if (!is_array($data)) {687return null;688}689690if (count($data) != 1) {691return null;692}693694$onto = head($data);695if (!is_array($onto)) {696return null;697}698699$type = idx($onto, 'type');700if ($type != 'branch') {701return null;702}703704return idx($onto, 'name');705}706707708/* -( PhabricatorApplicationTransactionInterface )------------------------- */709710711public function getApplicationTransactionEditor() {712return new DifferentialDiffEditor();713}714715public function getApplicationTransactionTemplate() {716return new DifferentialDiffTransaction();717}718719720/* -( PhabricatorDestructibleInterface )----------------------------------- */721722723public function destroyObjectPermanently(724PhabricatorDestructionEngine $engine) {725726$viewer = $engine->getViewer();727728$this->openTransaction();729$this->delete();730731foreach ($this->loadChangesets() as $changeset) {732$engine->destroyObject($changeset);733}734735$properties = id(new DifferentialDiffProperty())->loadAllWhere(736'diffID = %d',737$this->getID());738foreach ($properties as $prop) {739$prop->delete();740}741742$viewstate_query = id(new DifferentialViewStateQuery())743->setViewer($viewer)744->withObjectPHIDs(array($this->getPHID()));745$viewstates = new PhabricatorQueryIterator($viewstate_query);746foreach ($viewstates as $viewstate) {747$viewstate->delete();748}749750$this->saveTransaction();751}752753754/* -( PhabricatorConduitResultInterface )---------------------------------- */755756757public function getFieldSpecificationsForConduit() {758return array(759id(new PhabricatorConduitSearchFieldSpecification())760->setKey('revisionPHID')761->setType('phid')762->setDescription(pht('Associated revision PHID.')),763id(new PhabricatorConduitSearchFieldSpecification())764->setKey('authorPHID')765->setType('phid')766->setDescription(pht('Revision author PHID.')),767id(new PhabricatorConduitSearchFieldSpecification())768->setKey('repositoryPHID')769->setType('phid')770->setDescription(pht('Associated repository PHID.')),771id(new PhabricatorConduitSearchFieldSpecification())772->setKey('refs')773->setType('map<string, wild>')774->setDescription(pht('List of related VCS references.')),775);776}777778public function getFieldValuesForConduit() {779$refs = array();780781$branch = $this->getBranch();782if (strlen($branch)) {783$refs[] = array(784'type' => 'branch',785'name' => $branch,786);787}788789$onto = $this->loadTargetBranch();790if (strlen($onto)) {791$refs[] = array(792'type' => 'onto',793'name' => $onto,794);795}796797$base = $this->getSourceControlBaseRevision();798if (strlen($base)) {799$refs[] = array(800'type' => 'base',801'identifier' => $base,802);803}804805$bookmark = $this->getBookmark();806if (strlen($bookmark)) {807$refs[] = array(808'type' => 'bookmark',809'name' => $bookmark,810);811}812813$revision_phid = null;814if ($this->getRevisionID()) {815$revision_phid = $this->getRevision()->getPHID();816}817818return array(819'revisionPHID' => $revision_phid,820'authorPHID' => $this->getAuthorPHID(),821'repositoryPHID' => $this->getRepositoryPHID(),822'refs' => $refs,823);824}825826public function getConduitSearchAttachments() {827return array(828id(new DifferentialCommitsSearchEngineAttachment())829->setAttachmentKey('commits'),830);831}832833}834835836