@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 789 lines 20 kB view raw
1<?php 2 3final class DifferentialChangeset 4 extends DifferentialDAO 5 implements 6 PhabricatorPolicyInterface, 7 PhabricatorDestructibleInterface, 8 PhabricatorConduitResultInterface { 9 10 protected $diffID; 11 protected $oldFile; 12 protected $filename; 13 protected $awayPaths; 14 protected $changeType; 15 protected $fileType; 16 protected $metadata = array(); 17 protected $oldProperties; 18 protected $newProperties; 19 protected $addLines; 20 protected $delLines; 21 22 private $unsavedHunks = array(); 23 private $hunks = self::ATTACHABLE; 24 private $diff = self::ATTACHABLE; 25 26 private $authorityPackages; 27 private $changesetPackages; 28 29 private $newFileObject = self::ATTACHABLE; 30 private $oldFileObject = self::ATTACHABLE; 31 32 private $hasOldState; 33 private $hasNewState; 34 private $oldStateMetadata; 35 private $newStateMetadata; 36 private $oldFileType; 37 private $newFileType; 38 39 const TABLE_CACHE = 'differential_changeset_parse_cache'; 40 41 const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted'; 42 const METADATA_UNTRUSTED_ATTRIBUTES = 'attributes.untrusted'; 43 const METADATA_EFFECT_HASH = 'hash.effect'; 44 45 const ATTRIBUTE_GENERATED = 'generated'; 46 47 protected function getConfiguration() { 48 return array( 49 self::CONFIG_AUX_PHID => true, 50 self::CONFIG_SERIALIZATION => array( 51 'metadata' => self::SERIALIZATION_JSON, 52 'oldProperties' => self::SERIALIZATION_JSON, 53 'newProperties' => self::SERIALIZATION_JSON, 54 'awayPaths' => self::SERIALIZATION_JSON, 55 ), 56 self::CONFIG_COLUMN_SCHEMA => array( 57 'oldFile' => 'bytes?', 58 'filename' => 'bytes', 59 'changeType' => 'uint32', 60 'fileType' => 'uint32', 61 'addLines' => 'uint32', 62 'delLines' => 'uint32', 63 64 // T6203/NULLABILITY 65 // These should all be non-nullable, and store reasonable default 66 // JSON values if empty. 67 'awayPaths' => 'text?', 68 'metadata' => 'text?', 69 'oldProperties' => 'text?', 70 'newProperties' => 'text?', 71 ), 72 self::CONFIG_KEY_SCHEMA => array( 73 'diffID' => array( 74 'columns' => array('diffID'), 75 ), 76 ), 77 ) + parent::getConfiguration(); 78 } 79 80 public function getPHIDType() { 81 return DifferentialChangesetPHIDType::TYPECONST; 82 } 83 84 public function getAffectedLineCount() { 85 return $this->getAddLines() + $this->getDelLines(); 86 } 87 88 /** 89 * @param array<DifferentialHunk> $hunks 90 */ 91 public function attachHunks(array $hunks) { 92 assert_instances_of($hunks, DifferentialHunk::class); 93 $this->hunks = $hunks; 94 return $this; 95 } 96 97 public function getHunks() { 98 return $this->assertAttached($this->hunks); 99 } 100 101 public function getDisplayFilename() { 102 $name = $this->getFilename(); 103 if ($this->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { 104 $name .= '/'; 105 } 106 return $name; 107 } 108 109 public function getOwnersFilename() { 110 // TODO: For Subversion, we should adjust these paths to be relative to 111 // the repository root where possible. 112 113 $path = $this->getFilename(); 114 115 if (!isset($path[0])) { 116 return '/'; 117 } 118 119 if ($path[0] != '/') { 120 $path = '/'.$path; 121 } 122 123 return $path; 124 } 125 126 public function addUnsavedHunk(DifferentialHunk $hunk) { 127 if ($this->hunks === self::ATTACHABLE) { 128 $this->hunks = array(); 129 } 130 $this->hunks[] = $hunk; 131 $this->unsavedHunks[] = $hunk; 132 return $this; 133 } 134 135 public function setAuthorityPackages(array $authority_packages) { 136 $this->authorityPackages = mpull($authority_packages, null, 'getPHID'); 137 return $this; 138 } 139 140 public function getAuthorityPackages() { 141 return $this->authorityPackages; 142 } 143 144 public function setChangesetPackages($changeset_packages) { 145 $this->changesetPackages = mpull($changeset_packages, null, 'getPHID'); 146 return $this; 147 } 148 149 public function getChangesetPackages() { 150 return $this->changesetPackages; 151 } 152 153 public function setHasOldState($has_old_state) { 154 $this->hasOldState = $has_old_state; 155 return $this; 156 } 157 158 public function setHasNewState($has_new_state) { 159 $this->hasNewState = $has_new_state; 160 return $this; 161 } 162 163 public function hasOldState() { 164 if ($this->hasOldState !== null) { 165 return $this->hasOldState; 166 } 167 168 $change_type = $this->getChangeType(); 169 return !DifferentialChangeType::isCreateChangeType($change_type); 170 } 171 172 public function hasNewState() { 173 if ($this->hasNewState !== null) { 174 return $this->hasNewState; 175 } 176 177 $change_type = $this->getChangeType(); 178 return !DifferentialChangeType::isDeleteChangeType($change_type); 179 } 180 181 public function save() { 182 $this->openTransaction(); 183 $ret = parent::save(); 184 foreach ($this->unsavedHunks as $hunk) { 185 $hunk->setChangesetID($this->getID()); 186 $hunk->save(); 187 } 188 $this->saveTransaction(); 189 return $ret; 190 } 191 192 public function delete() { 193 $this->openTransaction(); 194 195 $hunks = id(new DifferentialHunk())->loadAllWhere( 196 'changesetID = %d', 197 $this->getID()); 198 foreach ($hunks as $hunk) { 199 $hunk->delete(); 200 } 201 202 $this->unsavedHunks = array(); 203 204 queryfx( 205 $this->establishConnection('w'), 206 'DELETE FROM %T WHERE id = %d', 207 self::TABLE_CACHE, 208 $this->getID()); 209 210 $ret = parent::delete(); 211 $this->saveTransaction(); 212 return $ret; 213 } 214 215 /** 216 * Test if this changeset and some other changeset put the affected file in 217 * the same state. 218 * 219 * @param DifferentialChangeset $other Changeset to compare against. 220 * @return bool True if the two changesets have the same effect. 221 */ 222 public function hasSameEffectAs(DifferentialChangeset $other) { 223 if ($this->getFilename() !== $other->getFilename()) { 224 return false; 225 } 226 227 $hash_key = self::METADATA_EFFECT_HASH; 228 229 $u_hash = $this->getChangesetMetadata($hash_key); 230 if ($u_hash === null) { 231 return false; 232 } 233 234 $v_hash = $other->getChangesetMetadata($hash_key); 235 if ($v_hash === null) { 236 return false; 237 } 238 239 if ($u_hash !== $v_hash) { 240 return false; 241 } 242 243 // Make sure the final states for the file properties (like the "+x" 244 // executable bit) match one another. 245 $u_props = $this->getNewProperties(); 246 $v_props = $other->getNewProperties(); 247 ksort($u_props); 248 ksort($v_props); 249 250 if ($u_props !== $v_props) { 251 return false; 252 } 253 254 return true; 255 } 256 257 public function getSortKey() { 258 $sort_key = $this->getFilename(); 259 // Sort files with ".h" in them first, so headers (.h, .hpp) come before 260 // implementations (.c, .cpp, .cs). 261 $sort_key = str_replace('.h', '.!h', $sort_key); 262 return $sort_key; 263 } 264 265 public function makeNewFile() { 266 $file = mpull($this->getHunks(), 'makeNewFile'); 267 return implode('', $file); 268 } 269 270 public function makeOldFile() { 271 $file = mpull($this->getHunks(), 'makeOldFile'); 272 return implode('', $file); 273 } 274 275 public function makeChangesWithContext($num_lines = 3) { 276 $with_context = array(); 277 foreach ($this->getHunks() as $hunk) { 278 $context = array(); 279 $changes = explode("\n", $hunk->getChanges()); 280 foreach ($changes as $l => $line) { 281 $type = substr($line, 0, 1); 282 if ($type == '+' || $type == '-') { 283 $context += array_fill($l - $num_lines, 2 * $num_lines + 1, true); 284 } 285 } 286 $with_context[] = array_intersect_key($changes, $context); 287 } 288 return array_mergev($with_context); 289 } 290 291 public function getAnchorName() { 292 return 'change-'.PhabricatorHash::digestForAnchor($this->getFilename()); 293 } 294 295 public function getAbsoluteRepositoryPath( 296 ?PhabricatorRepository $repository = null, 297 ?DifferentialDiff $diff = null) { 298 299 $base = '/'; 300 if ($diff && $diff->getSourceControlPath()) { 301 $base = id(new PhutilURI($diff->getSourceControlPath()))->getPath(); 302 } 303 304 $path = $this->getFilename(); 305 $path = rtrim($base, '/').'/'.ltrim($path, '/'); 306 307 $svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN; 308 if ($repository && $repository->getVersionControlSystem() == $svn) { 309 $prefix = $repository->getDetail('remote-uri'); 310 $prefix = id(new PhutilURI($prefix))->getPath(); 311 if (!strncmp($path, $prefix, strlen($prefix))) { 312 $path = substr($path, strlen($prefix)); 313 } 314 $path = '/'.ltrim($path, '/'); 315 } 316 317 return $path; 318 } 319 320 public function attachDiff(DifferentialDiff $diff) { 321 $this->diff = $diff; 322 return $this; 323 } 324 325 public function getDiff() { 326 return $this->assertAttached($this->diff); 327 } 328 329 public function getOldStatePathVector() { 330 $path = $this->getOldFile(); 331 if (!phutil_nonempty_string($path)) { 332 $path = $this->getFilename(); 333 } 334 335 if (!phutil_nonempty_string($path)) { 336 return null; 337 } 338 339 $path = trim($path, '/'); 340 return explode('/', $path); 341 } 342 343 public function getNewStatePathVector() { 344 if (!$this->hasNewState()) { 345 return null; 346 } 347 348 $path = $this->getFilename(); 349 $path = trim($path, '/'); 350 $path = explode('/', $path); 351 352 return $path; 353 } 354 355 public function newFileTreeIcon() { 356 $icon = $this->getPathIconIcon(); 357 $color = $this->getPathIconColor(); 358 359 return id(new PHUIIconView()) 360 ->setIcon("{$icon} {$color}"); 361 } 362 363 public function getIsOwnedChangeset() { 364 $authority_packages = $this->getAuthorityPackages(); 365 $changeset_packages = $this->getChangesetPackages(); 366 367 if (!$authority_packages || !$changeset_packages) { 368 return false; 369 } 370 371 return (bool)array_intersect_key($authority_packages, $changeset_packages); 372 } 373 374 public function getIsLowImportanceChangeset() { 375 if (!$this->hasNewState()) { 376 return true; 377 } 378 379 if ($this->isGeneratedChangeset()) { 380 return true; 381 } 382 383 return false; 384 } 385 386 public function getPathIconIcon() { 387 return idx($this->getPathIconDetails(), 'icon'); 388 } 389 390 public function getPathIconColor() { 391 return idx($this->getPathIconDetails(), 'color'); 392 } 393 394 private function getPathIconDetails() { 395 $change_icons = array( 396 DifferentialChangeType::TYPE_DELETE => array( 397 'icon' => 'fa-times', 398 'color' => 'delete-color', 399 ), 400 DifferentialChangeType::TYPE_ADD => array( 401 'icon' => 'fa-plus', 402 'color' => 'create-color', 403 ), 404 DifferentialChangeType::TYPE_MOVE_AWAY => array( 405 'icon' => 'fa-circle-o', 406 'color' => 'grey', 407 ), 408 DifferentialChangeType::TYPE_MULTICOPY => array( 409 'icon' => 'fa-circle-o', 410 'color' => 'grey', 411 ), 412 DifferentialChangeType::TYPE_MOVE_HERE => array( 413 'icon' => 'fa-plus-circle', 414 'color' => 'create-color', 415 ), 416 DifferentialChangeType::TYPE_COPY_HERE => array( 417 'icon' => 'fa-plus-circle', 418 'color' => 'create-color', 419 ), 420 ); 421 422 $change_type = $this->getChangeType(); 423 if (isset($change_icons[$change_type])) { 424 return $change_icons[$change_type]; 425 } 426 427 if ($this->isGeneratedChangeset()) { 428 return array( 429 'icon' => 'fa-cogs', 430 'color' => 'grey', 431 ); 432 } 433 434 $file_type = $this->getFileType(); 435 $icon = DifferentialChangeType::getIconForFileType($file_type); 436 437 return array( 438 'icon' => $icon, 439 'color' => 'bluetext', 440 ); 441 } 442 443 public function setChangesetMetadata($key, $value) { 444 if (!is_array($this->metadata)) { 445 $this->metadata = array(); 446 } 447 448 $this->metadata[$key] = $value; 449 450 return $this; 451 } 452 453 public function getChangesetMetadata($key, $default = null) { 454 if (!is_array($this->metadata)) { 455 return $default; 456 } 457 458 return idx($this->metadata, $key, $default); 459 } 460 461 private function setInternalChangesetAttribute($trusted, $key, $value) { 462 if ($trusted) { 463 $meta_key = self::METADATA_TRUSTED_ATTRIBUTES; 464 } else { 465 $meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES; 466 } 467 468 $attributes = $this->getChangesetMetadata($meta_key, array()); 469 $attributes[$key] = $value; 470 $this->setChangesetMetadata($meta_key, $attributes); 471 472 return $this; 473 } 474 475 private function getInternalChangesetAttributes($trusted) { 476 if ($trusted) { 477 $meta_key = self::METADATA_TRUSTED_ATTRIBUTES; 478 } else { 479 $meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES; 480 } 481 482 return $this->getChangesetMetadata($meta_key, array()); 483 } 484 485 public function setTrustedChangesetAttribute($key, $value) { 486 return $this->setInternalChangesetAttribute(true, $key, $value); 487 } 488 489 public function getTrustedChangesetAttributes() { 490 return $this->getInternalChangesetAttributes(true); 491 } 492 493 public function getTrustedChangesetAttribute($key, $default = null) { 494 $map = $this->getTrustedChangesetAttributes(); 495 return idx($map, $key, $default); 496 } 497 498 public function setUntrustedChangesetAttribute($key, $value) { 499 return $this->setInternalChangesetAttribute(false, $key, $value); 500 } 501 502 public function getUntrustedChangesetAttributes() { 503 return $this->getInternalChangesetAttributes(false); 504 } 505 506 public function getUntrustedChangesetAttribute($key, $default = null) { 507 $map = $this->getUntrustedChangesetAttributes(); 508 return idx($map, $key, $default); 509 } 510 511 public function getChangesetAttributes() { 512 // Prefer trusted values over untrusted values when both exist. 513 return 514 $this->getTrustedChangesetAttributes() + 515 $this->getUntrustedChangesetAttributes(); 516 } 517 518 public function getChangesetAttribute($key, $default = null) { 519 $map = $this->getChangesetAttributes(); 520 return idx($map, $key, $default); 521 } 522 523 public function isGeneratedChangeset() { 524 return $this->getChangesetAttribute(self::ATTRIBUTE_GENERATED); 525 } 526 527 public function getNewFileObjectPHID() { 528 $metadata = $this->getMetadata(); 529 return idx($metadata, 'new:binary-phid'); 530 } 531 532 public function getOldFileObjectPHID() { 533 $metadata = $this->getMetadata(); 534 return idx($metadata, 'old:binary-phid'); 535 } 536 537 public function attachNewFileObject(PhabricatorFile $file) { 538 $this->newFileObject = $file; 539 return $this; 540 } 541 542 public function getNewFileObject() { 543 return $this->assertAttached($this->newFileObject); 544 } 545 546 public function attachOldFileObject(PhabricatorFile $file) { 547 $this->oldFileObject = $file; 548 return $this; 549 } 550 551 public function getOldFileObject() { 552 return $this->assertAttached($this->oldFileObject); 553 } 554 555 public function newComparisonChangeset( 556 ?DifferentialChangeset $against = null) { 557 558 $left = $this; 559 $right = $against; 560 561 $left_data = $left->makeNewFile(); 562 $left_properties = $left->getNewProperties(); 563 $left_metadata = $left->getNewStateMetadata(); 564 $left_state = $left->hasNewState(); 565 $shared_metadata = $left->getMetadata(); 566 $left_type = $left->getNewFileType(); 567 if ($right) { 568 $right_data = $right->makeNewFile(); 569 $right_properties = $right->getNewProperties(); 570 $right_metadata = $right->getNewStateMetadata(); 571 $right_state = $right->hasNewState(); 572 $shared_metadata = $right->getMetadata(); 573 $right_type = $right->getNewFileType(); 574 575 $file_name = $right->getFilename(); 576 } else { 577 $right_data = $left->makeOldFile(); 578 $right_properties = $left->getOldProperties(); 579 $right_metadata = $left->getOldStateMetadata(); 580 $right_state = $left->hasOldState(); 581 $right_type = $left->getOldFileType(); 582 583 $file_name = $left->getFilename(); 584 } 585 586 $engine = new PhabricatorDifferenceEngine(); 587 588 $synthetic = $engine->generateChangesetFromFileContent( 589 $left_data, 590 $right_data); 591 592 $comparison = id(new self()) 593 ->makeEphemeral() 594 ->attachDiff($left->getDiff()) 595 ->setOldFile($left->getFilename()) 596 ->setFilename($file_name); 597 598 // TODO: Change type? 599 // TODO: Away paths? 600 // TODO: View state key? 601 602 $comparison->attachHunks($synthetic->getHunks()); 603 604 $comparison->setOldProperties($left_properties); 605 $comparison->setNewProperties($right_properties); 606 607 $comparison 608 ->setOldStateMetadata($left_metadata) 609 ->setNewStateMetadata($right_metadata) 610 ->setHasOldState($left_state) 611 ->setHasNewState($right_state) 612 ->setOldFileType($left_type) 613 ->setNewFileType($right_type); 614 615 // NOTE: Some metadata is not stored statefully, like the "generated" 616 // flag. For now, use the rightmost "new state" metadata to fill in these 617 // values. 618 619 $metadata = $comparison->getMetadata(); 620 $metadata = $metadata + $shared_metadata; 621 $comparison->setMetadata($metadata); 622 623 return $comparison; 624 } 625 626 627 public function setNewFileType($new_file_type) { 628 $this->newFileType = $new_file_type; 629 return $this; 630 } 631 632 public function getNewFileType() { 633 if ($this->newFileType !== null) { 634 return $this->newFileType; 635 } 636 637 return $this->getFiletype(); 638 } 639 640 public function setOldFileType($old_file_type) { 641 $this->oldFileType = $old_file_type; 642 return $this; 643 } 644 645 public function getOldFileType() { 646 if ($this->oldFileType !== null) { 647 return $this->oldFileType; 648 } 649 650 return $this->getFileType(); 651 } 652 653 public function hasSourceTextBody() { 654 $type_map = array( 655 DifferentialChangeType::FILE_TEXT => true, 656 DifferentialChangeType::FILE_SYMLINK => true, 657 ); 658 659 $old_body = isset($type_map[$this->getOldFileType()]); 660 $new_body = isset($type_map[$this->getNewFileType()]); 661 662 return ($old_body || $new_body); 663 } 664 665 public function getNewStateMetadata() { 666 return $this->getMetadataWithPrefix('new:'); 667 } 668 669 public function setNewStateMetadata(array $metadata) { 670 return $this->setMetadataWithPrefix($metadata, 'new:'); 671 } 672 673 public function getOldStateMetadata() { 674 return $this->getMetadataWithPrefix('old:'); 675 } 676 677 public function setOldStateMetadata(array $metadata) { 678 return $this->setMetadataWithPrefix($metadata, 'old:'); 679 } 680 681 private function getMetadataWithPrefix($prefix) { 682 $length = strlen($prefix); 683 684 $result = array(); 685 foreach ($this->getMetadata() as $key => $value) { 686 if (strncmp($key, $prefix, $length)) { 687 continue; 688 } 689 690 $key = substr($key, $length); 691 $result[$key] = $value; 692 } 693 694 return $result; 695 } 696 697 private function setMetadataWithPrefix(array $metadata, $prefix) { 698 foreach ($metadata as $key => $value) { 699 $key = $prefix.$key; 700 $this->metadata[$key] = $value; 701 } 702 703 return $this; 704 } 705 706 707/* -( PhabricatorPolicyInterface )----------------------------------------- */ 708 709 710 public function getCapabilities() { 711 return array( 712 PhabricatorPolicyCapability::CAN_VIEW, 713 ); 714 } 715 716 public function getPolicy($capability) { 717 return $this->getDiff()->getPolicy($capability); 718 } 719 720 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 721 return $this->getDiff()->hasAutomaticCapability($capability, $viewer); 722 } 723 724 725/* -( PhabricatorDestructibleInterface )----------------------------------- */ 726 727 728 public function destroyObjectPermanently( 729 PhabricatorDestructionEngine $engine) { 730 $this->openTransaction(); 731 732 $hunks = id(new DifferentialHunk())->loadAllWhere( 733 'changesetID = %d', 734 $this->getID()); 735 foreach ($hunks as $hunk) { 736 $engine->destroyObject($hunk); 737 } 738 739 $this->delete(); 740 741 $this->saveTransaction(); 742 } 743 744/* -( PhabricatorConduitResultInterface )---------------------------------- */ 745 746 public function getFieldSpecificationsForConduit() { 747 return array( 748 id(new PhabricatorConduitSearchFieldSpecification()) 749 ->setKey('diffPHID') 750 ->setType('phid') 751 ->setDescription(pht('The diff the changeset is attached to.')), 752 ); 753 } 754 755 public function getFieldValuesForConduit() { 756 $diff = $this->getDiff(); 757 758 $repository = null; 759 if ($diff) { 760 $revision = $diff->getRevision(); 761 if ($revision) { 762 $repository = $revision->getRepository(); 763 } 764 } 765 766 $absolute_path = $this->getAbsoluteRepositoryPath($repository, $diff); 767 if (strlen($absolute_path)) { 768 $absolute_path = base64_encode($absolute_path); 769 } else { 770 $absolute_path = null; 771 } 772 773 $display_path = $this->getDisplayFilename(); 774 775 return array( 776 'diffPHID' => $diff->getPHID(), 777 'path' => array( 778 'displayPath' => $display_path, 779 'absolutePath.base64' => $absolute_path, 780 ), 781 ); 782 } 783 784 public function getConduitSearchAttachments() { 785 return array(); 786 } 787 788 789}