Path: blob/master/src/applications/differential/storage/DifferentialChangeset.php
12256 views
<?php12final class DifferentialChangeset3extends DifferentialDAO4implements5PhabricatorPolicyInterface,6PhabricatorDestructibleInterface,7PhabricatorConduitResultInterface {89protected $diffID;10protected $oldFile;11protected $filename;12protected $awayPaths;13protected $changeType;14protected $fileType;15protected $metadata = array();16protected $oldProperties;17protected $newProperties;18protected $addLines;19protected $delLines;2021private $unsavedHunks = array();22private $hunks = self::ATTACHABLE;23private $diff = self::ATTACHABLE;2425private $authorityPackages;26private $changesetPackages;2728private $newFileObject = self::ATTACHABLE;29private $oldFileObject = self::ATTACHABLE;3031private $hasOldState;32private $hasNewState;33private $oldStateMetadata;34private $newStateMetadata;35private $oldFileType;36private $newFileType;3738const TABLE_CACHE = 'differential_changeset_parse_cache';3940const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted';41const METADATA_UNTRUSTED_ATTRIBUTES = 'attributes.untrusted';42const METADATA_EFFECT_HASH = 'hash.effect';4344const ATTRIBUTE_GENERATED = 'generated';4546protected function getConfiguration() {47return array(48self::CONFIG_AUX_PHID => true,49self::CONFIG_SERIALIZATION => array(50'metadata' => self::SERIALIZATION_JSON,51'oldProperties' => self::SERIALIZATION_JSON,52'newProperties' => self::SERIALIZATION_JSON,53'awayPaths' => self::SERIALIZATION_JSON,54),55self::CONFIG_COLUMN_SCHEMA => array(56'oldFile' => 'bytes?',57'filename' => 'bytes',58'changeType' => 'uint32',59'fileType' => 'uint32',60'addLines' => 'uint32',61'delLines' => 'uint32',6263// T6203/NULLABILITY64// These should all be non-nullable, and store reasonable default65// JSON values if empty.66'awayPaths' => 'text?',67'metadata' => 'text?',68'oldProperties' => 'text?',69'newProperties' => 'text?',70),71self::CONFIG_KEY_SCHEMA => array(72'diffID' => array(73'columns' => array('diffID'),74),75),76) + parent::getConfiguration();77}7879public function getPHIDType() {80return DifferentialChangesetPHIDType::TYPECONST;81}8283public function getAffectedLineCount() {84return $this->getAddLines() + $this->getDelLines();85}8687public function attachHunks(array $hunks) {88assert_instances_of($hunks, 'DifferentialHunk');89$this->hunks = $hunks;90return $this;91}9293public function getHunks() {94return $this->assertAttached($this->hunks);95}9697public function getDisplayFilename() {98$name = $this->getFilename();99if ($this->getFileType() == DifferentialChangeType::FILE_DIRECTORY) {100$name .= '/';101}102return $name;103}104105public function getOwnersFilename() {106// TODO: For Subversion, we should adjust these paths to be relative to107// the repository root where possible.108109$path = $this->getFilename();110111if (!isset($path[0])) {112return '/';113}114115if ($path[0] != '/') {116$path = '/'.$path;117}118119return $path;120}121122public function addUnsavedHunk(DifferentialHunk $hunk) {123if ($this->hunks === self::ATTACHABLE) {124$this->hunks = array();125}126$this->hunks[] = $hunk;127$this->unsavedHunks[] = $hunk;128return $this;129}130131public function setAuthorityPackages(array $authority_packages) {132$this->authorityPackages = mpull($authority_packages, null, 'getPHID');133return $this;134}135136public function getAuthorityPackages() {137return $this->authorityPackages;138}139140public function setChangesetPackages($changeset_packages) {141$this->changesetPackages = mpull($changeset_packages, null, 'getPHID');142return $this;143}144145public function getChangesetPackages() {146return $this->changesetPackages;147}148149public function setHasOldState($has_old_state) {150$this->hasOldState = $has_old_state;151return $this;152}153154public function setHasNewState($has_new_state) {155$this->hasNewState = $has_new_state;156return $this;157}158159public function hasOldState() {160if ($this->hasOldState !== null) {161return $this->hasOldState;162}163164$change_type = $this->getChangeType();165return !DifferentialChangeType::isCreateChangeType($change_type);166}167168public function hasNewState() {169if ($this->hasNewState !== null) {170return $this->hasNewState;171}172173$change_type = $this->getChangeType();174return !DifferentialChangeType::isDeleteChangeType($change_type);175}176177public function save() {178$this->openTransaction();179$ret = parent::save();180foreach ($this->unsavedHunks as $hunk) {181$hunk->setChangesetID($this->getID());182$hunk->save();183}184$this->saveTransaction();185return $ret;186}187188public function delete() {189$this->openTransaction();190191$hunks = id(new DifferentialHunk())->loadAllWhere(192'changesetID = %d',193$this->getID());194foreach ($hunks as $hunk) {195$hunk->delete();196}197198$this->unsavedHunks = array();199200queryfx(201$this->establishConnection('w'),202'DELETE FROM %T WHERE id = %d',203self::TABLE_CACHE,204$this->getID());205206$ret = parent::delete();207$this->saveTransaction();208return $ret;209}210211/**212* Test if this changeset and some other changeset put the affected file in213* the same state.214*215* @param DifferentialChangeset Changeset to compare against.216* @return bool True if the two changesets have the same effect.217*/218public function hasSameEffectAs(DifferentialChangeset $other) {219if ($this->getFilename() !== $other->getFilename()) {220return false;221}222223$hash_key = self::METADATA_EFFECT_HASH;224225$u_hash = $this->getChangesetMetadata($hash_key);226if ($u_hash === null) {227return false;228}229230$v_hash = $other->getChangesetMetadata($hash_key);231if ($v_hash === null) {232return false;233}234235if ($u_hash !== $v_hash) {236return false;237}238239// Make sure the final states for the file properties (like the "+x"240// executable bit) match one another.241$u_props = $this->getNewProperties();242$v_props = $other->getNewProperties();243ksort($u_props);244ksort($v_props);245246if ($u_props !== $v_props) {247return false;248}249250return true;251}252253public function getSortKey() {254$sort_key = $this->getFilename();255// Sort files with ".h" in them first, so headers (.h, .hpp) come before256// implementations (.c, .cpp, .cs).257$sort_key = str_replace('.h', '.!h', $sort_key);258return $sort_key;259}260261public function makeNewFile() {262$file = mpull($this->getHunks(), 'makeNewFile');263return implode('', $file);264}265266public function makeOldFile() {267$file = mpull($this->getHunks(), 'makeOldFile');268return implode('', $file);269}270271public function makeChangesWithContext($num_lines = 3) {272$with_context = array();273foreach ($this->getHunks() as $hunk) {274$context = array();275$changes = explode("\n", $hunk->getChanges());276foreach ($changes as $l => $line) {277$type = substr($line, 0, 1);278if ($type == '+' || $type == '-') {279$context += array_fill($l - $num_lines, 2 * $num_lines + 1, true);280}281}282$with_context[] = array_intersect_key($changes, $context);283}284return array_mergev($with_context);285}286287public function getAnchorName() {288return 'change-'.PhabricatorHash::digestForAnchor($this->getFilename());289}290291public function getAbsoluteRepositoryPath(292PhabricatorRepository $repository = null,293DifferentialDiff $diff = null) {294295$base = '/';296if ($diff && $diff->getSourceControlPath()) {297$base = id(new PhutilURI($diff->getSourceControlPath()))->getPath();298}299300$path = $this->getFilename();301$path = rtrim($base, '/').'/'.ltrim($path, '/');302303$svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;304if ($repository && $repository->getVersionControlSystem() == $svn) {305$prefix = $repository->getDetail('remote-uri');306$prefix = id(new PhutilURI($prefix))->getPath();307if (!strncmp($path, $prefix, strlen($prefix))) {308$path = substr($path, strlen($prefix));309}310$path = '/'.ltrim($path, '/');311}312313return $path;314}315316public function attachDiff(DifferentialDiff $diff) {317$this->diff = $diff;318return $this;319}320321public function getDiff() {322return $this->assertAttached($this->diff);323}324325public function getOldStatePathVector() {326$path = $this->getOldFile();327if ($path === null || !strlen($path)) {328$path = $this->getFilename();329}330331$path = trim($path, '/');332$path = explode('/', $path);333334return $path;335}336337public function getNewStatePathVector() {338if (!$this->hasNewState()) {339return null;340}341342$path = $this->getFilename();343$path = trim($path, '/');344$path = explode('/', $path);345346return $path;347}348349public function newFileTreeIcon() {350$icon = $this->getPathIconIcon();351$color = $this->getPathIconColor();352353return id(new PHUIIconView())354->setIcon("{$icon} {$color}");355}356357public function getIsOwnedChangeset() {358$authority_packages = $this->getAuthorityPackages();359$changeset_packages = $this->getChangesetPackages();360361if (!$authority_packages || !$changeset_packages) {362return false;363}364365return (bool)array_intersect_key($authority_packages, $changeset_packages);366}367368public function getIsLowImportanceChangeset() {369if (!$this->hasNewState()) {370return true;371}372373if ($this->isGeneratedChangeset()) {374return true;375}376377return false;378}379380public function getPathIconIcon() {381return idx($this->getPathIconDetails(), 'icon');382}383384public function getPathIconColor() {385return idx($this->getPathIconDetails(), 'color');386}387388private function getPathIconDetails() {389$change_icons = array(390DifferentialChangeType::TYPE_DELETE => array(391'icon' => 'fa-times',392'color' => 'delete-color',393),394DifferentialChangeType::TYPE_ADD => array(395'icon' => 'fa-plus',396'color' => 'create-color',397),398DifferentialChangeType::TYPE_MOVE_AWAY => array(399'icon' => 'fa-circle-o',400'color' => 'grey',401),402DifferentialChangeType::TYPE_MULTICOPY => array(403'icon' => 'fa-circle-o',404'color' => 'grey',405),406DifferentialChangeType::TYPE_MOVE_HERE => array(407'icon' => 'fa-plus-circle',408'color' => 'create-color',409),410DifferentialChangeType::TYPE_COPY_HERE => array(411'icon' => 'fa-plus-circle',412'color' => 'create-color',413),414);415416$change_type = $this->getChangeType();417if (isset($change_icons[$change_type])) {418return $change_icons[$change_type];419}420421if ($this->isGeneratedChangeset()) {422return array(423'icon' => 'fa-cogs',424'color' => 'grey',425);426}427428$file_type = $this->getFileType();429$icon = DifferentialChangeType::getIconForFileType($file_type);430431return array(432'icon' => $icon,433'color' => 'bluetext',434);435}436437public function setChangesetMetadata($key, $value) {438if (!is_array($this->metadata)) {439$this->metadata = array();440}441442$this->metadata[$key] = $value;443444return $this;445}446447public function getChangesetMetadata($key, $default = null) {448if (!is_array($this->metadata)) {449return $default;450}451452return idx($this->metadata, $key, $default);453}454455private function setInternalChangesetAttribute($trusted, $key, $value) {456if ($trusted) {457$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;458} else {459$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;460}461462$attributes = $this->getChangesetMetadata($meta_key, array());463$attributes[$key] = $value;464$this->setChangesetMetadata($meta_key, $attributes);465466return $this;467}468469private function getInternalChangesetAttributes($trusted) {470if ($trusted) {471$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;472} else {473$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;474}475476return $this->getChangesetMetadata($meta_key, array());477}478479public function setTrustedChangesetAttribute($key, $value) {480return $this->setInternalChangesetAttribute(true, $key, $value);481}482483public function getTrustedChangesetAttributes() {484return $this->getInternalChangesetAttributes(true);485}486487public function getTrustedChangesetAttribute($key, $default = null) {488$map = $this->getTrustedChangesetAttributes();489return idx($map, $key, $default);490}491492public function setUntrustedChangesetAttribute($key, $value) {493return $this->setInternalChangesetAttribute(false, $key, $value);494}495496public function getUntrustedChangesetAttributes() {497return $this->getInternalChangesetAttributes(false);498}499500public function getUntrustedChangesetAttribute($key, $default = null) {501$map = $this->getUntrustedChangesetAttributes();502return idx($map, $key, $default);503}504505public function getChangesetAttributes() {506// Prefer trusted values over untrusted values when both exist.507return508$this->getTrustedChangesetAttributes() +509$this->getUntrustedChangesetAttributes();510}511512public function getChangesetAttribute($key, $default = null) {513$map = $this->getChangesetAttributes();514return idx($map, $key, $default);515}516517public function isGeneratedChangeset() {518return $this->getChangesetAttribute(self::ATTRIBUTE_GENERATED);519}520521public function getNewFileObjectPHID() {522$metadata = $this->getMetadata();523return idx($metadata, 'new:binary-phid');524}525526public function getOldFileObjectPHID() {527$metadata = $this->getMetadata();528return idx($metadata, 'old:binary-phid');529}530531public function attachNewFileObject(PhabricatorFile $file) {532$this->newFileObject = $file;533return $this;534}535536public function getNewFileObject() {537return $this->assertAttached($this->newFileObject);538}539540public function attachOldFileObject(PhabricatorFile $file) {541$this->oldFileObject = $file;542return $this;543}544545public function getOldFileObject() {546return $this->assertAttached($this->oldFileObject);547}548549public function newComparisonChangeset(550DifferentialChangeset $against = null) {551552$left = $this;553$right = $against;554555$left_data = $left->makeNewFile();556$left_properties = $left->getNewProperties();557$left_metadata = $left->getNewStateMetadata();558$left_state = $left->hasNewState();559$shared_metadata = $left->getMetadata();560$left_type = $left->getNewFileType();561if ($right) {562$right_data = $right->makeNewFile();563$right_properties = $right->getNewProperties();564$right_metadata = $right->getNewStateMetadata();565$right_state = $right->hasNewState();566$shared_metadata = $right->getMetadata();567$right_type = $right->getNewFileType();568569$file_name = $right->getFilename();570} else {571$right_data = $left->makeOldFile();572$right_properties = $left->getOldProperties();573$right_metadata = $left->getOldStateMetadata();574$right_state = $left->hasOldState();575$right_type = $left->getOldFileType();576577$file_name = $left->getFilename();578}579580$engine = new PhabricatorDifferenceEngine();581582$synthetic = $engine->generateChangesetFromFileContent(583$left_data,584$right_data);585586$comparison = id(new self())587->makeEphemeral(true)588->attachDiff($left->getDiff())589->setOldFile($left->getFilename())590->setFilename($file_name);591592// TODO: Change type?593// TODO: Away paths?594// TODO: View state key?595596$comparison->attachHunks($synthetic->getHunks());597598$comparison->setOldProperties($left_properties);599$comparison->setNewProperties($right_properties);600601$comparison602->setOldStateMetadata($left_metadata)603->setNewStateMetadata($right_metadata)604->setHasOldState($left_state)605->setHasNewState($right_state)606->setOldFileType($left_type)607->setNewFileType($right_type);608609// NOTE: Some metadata is not stored statefully, like the "generated"610// flag. For now, use the rightmost "new state" metadata to fill in these611// values.612613$metadata = $comparison->getMetadata();614$metadata = $metadata + $shared_metadata;615$comparison->setMetadata($metadata);616617return $comparison;618}619620621public function setNewFileType($new_file_type) {622$this->newFileType = $new_file_type;623return $this;624}625626public function getNewFileType() {627if ($this->newFileType !== null) {628return $this->newFileType;629}630631return $this->getFiletype();632}633634public function setOldFileType($old_file_type) {635$this->oldFileType = $old_file_type;636return $this;637}638639public function getOldFileType() {640if ($this->oldFileType !== null) {641return $this->oldFileType;642}643644return $this->getFileType();645}646647public function hasSourceTextBody() {648$type_map = array(649DifferentialChangeType::FILE_TEXT => true,650DifferentialChangeType::FILE_SYMLINK => true,651);652653$old_body = isset($type_map[$this->getOldFileType()]);654$new_body = isset($type_map[$this->getNewFileType()]);655656return ($old_body || $new_body);657}658659public function getNewStateMetadata() {660return $this->getMetadataWithPrefix('new:');661}662663public function setNewStateMetadata(array $metadata) {664return $this->setMetadataWithPrefix($metadata, 'new:');665}666667public function getOldStateMetadata() {668return $this->getMetadataWithPrefix('old:');669}670671public function setOldStateMetadata(array $metadata) {672return $this->setMetadataWithPrefix($metadata, 'old:');673}674675private function getMetadataWithPrefix($prefix) {676$length = strlen($prefix);677678$result = array();679foreach ($this->getMetadata() as $key => $value) {680if (strncmp($key, $prefix, $length)) {681continue;682}683684$key = substr($key, $length);685$result[$key] = $value;686}687688return $result;689}690691private function setMetadataWithPrefix(array $metadata, $prefix) {692foreach ($metadata as $key => $value) {693$key = $prefix.$key;694$this->metadata[$key] = $value;695}696697return $this;698}699700701/* -( PhabricatorPolicyInterface )----------------------------------------- */702703704public function getCapabilities() {705return array(706PhabricatorPolicyCapability::CAN_VIEW,707);708}709710public function getPolicy($capability) {711return $this->getDiff()->getPolicy($capability);712}713714public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {715return $this->getDiff()->hasAutomaticCapability($capability, $viewer);716}717718719/* -( PhabricatorDestructibleInterface )----------------------------------- */720721722public function destroyObjectPermanently(723PhabricatorDestructionEngine $engine) {724$this->openTransaction();725726$hunks = id(new DifferentialHunk())->loadAllWhere(727'changesetID = %d',728$this->getID());729foreach ($hunks as $hunk) {730$engine->destroyObject($hunk);731}732733$this->delete();734735$this->saveTransaction();736}737738/* -( PhabricatorConduitResultInterface )---------------------------------- */739740public function getFieldSpecificationsForConduit() {741return array(742id(new PhabricatorConduitSearchFieldSpecification())743->setKey('diffPHID')744->setType('phid')745->setDescription(pht('The diff the changeset is attached to.')),746);747}748749public function getFieldValuesForConduit() {750$diff = $this->getDiff();751752$repository = null;753if ($diff) {754$revision = $diff->getRevision();755if ($revision) {756$repository = $revision->getRepository();757}758}759760$absolute_path = $this->getAbsoluteRepositoryPath($repository, $diff);761if (strlen($absolute_path)) {762$absolute_path = base64_encode($absolute_path);763} else {764$absolute_path = null;765}766767$display_path = $this->getDisplayFilename();768769return array(770'diffPHID' => $diff->getPHID(),771'path' => array(772'displayPath' => $display_path,773'absolutePath.base64' => $absolute_path,774),775);776}777778public function getConduitSearchAttachments() {779return array();780}781782783}784785786