@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.

Improve the construction of synthetic "comparison/intradiff" changesets

Summary:
Ref T13523. Currently, when building a "comparison" changeset, metadata is taken from the left changeset. This is somewhat arbitrary.

This means that intradiffs of images don't work properly because the rendered changeset has only the left (usually "old") information.

Later, some of the code attempts to ignore the file data stored on the changeset and reconstruct the correct file data, which is how the result ends up not-completely-wrong.

Be more careful about building sensible-ish metadata, and then just use it directly later on. This fixes the "spooky" code referencing D955 + D6851.

There are some related issues, where "change type" and "file type" are selected arbitrarily and then used to determine whether the change has an "old/new" state or not (i.e., is the left side of the diff empty, since the change creates the file)?

In many cases, neither of the original changesets have a "change type" which will answer this question correctly. Separate this concept from "has state" from "change type", and make more of the code ask narrower questions about the specific conditions or states it cares about, rather than "change type".

Test Plan:
- Created a revision with Diff 1, Diff 2, and Diff 3. Diff 1 takes an image from "null -> A". Diff 2 takes the same image from "null -> B". Diff 3 takes the same image from "A -> B'.
- Intradiffed 1v2 and 1v3.
- Before patch:
- Left side usually missing, which is incorrect (should always be "A").
- Change properties are a mess ("null -> image/png" for MIME type, e.g.)
- Uninteresting/incorrect "unix:filemode" stuff.
- After patch;
- Left side shows state "A".
- Change properties only show size changes (which is correct).

{F7402012}

Maniphest Tasks: T13523

Differential Revision: https://secure.phabricator.com/D21180

+183 -92
+1 -22
src/applications/differential/controller/DifferentialChangesetViewController.php
··· 109 109 } 110 110 111 111 if ($left) { 112 - $left_data = $left->makeNewFile(); 113 - $left_properties = $left->getNewProperties(); 114 - if ($right) { 115 - $right_data = $right->makeNewFile(); 116 - $right_properties = $right->getNewProperties(); 117 - } else { 118 - $right_data = $left->makeOldFile(); 119 - $right_properties = $left->getOldProperties(); 120 - } 121 - 122 - $engine = new PhabricatorDifferenceEngine(); 123 - $synthetic = $engine->generateChangesetFromFileContent( 124 - $left_data, 125 - $right_data); 126 - 127 - $choice = clone nonempty($left, $right); 128 - $choice->attachHunks($synthetic->getHunks()); 129 - 130 - $choice->setOldProperties($left_properties); 131 - $choice->setNewProperties($right_properties); 132 - 133 - $changeset = $choice; 112 + $changeset = $left->newComparisonChangeset($right); 134 113 } 135 114 136 115 if ($left_new || $right_new) {
+26 -61
src/applications/differential/parser/DifferentialChangesetParser.php
··· 1694 1694 $changeset = $this->changeset; 1695 1695 $viewer = $this->getViewer(); 1696 1696 1697 - // TODO: This should probably be made non-optional in the future. 1698 - if (!$viewer) { 1699 - return null; 1700 - } 1701 - 1702 - $old_file = null; 1703 - $new_file = null; 1697 + list($old_file, $new_file) = $this->loadFileObjectsForChangeset(); 1704 1698 1705 - switch ($changeset->getFileType()) { 1706 - case DifferentialChangeType::FILE_IMAGE: 1707 - case DifferentialChangeType::FILE_BINARY: 1708 - list($old_file, $new_file) = $this->loadFileObjectsForChangeset(); 1709 - break; 1710 - } 1711 - 1712 - $type_delete = DifferentialChangeType::TYPE_DELETE; 1713 - $type_add = DifferentialChangeType::TYPE_ADD; 1714 - $change_type = $changeset->getChangeType(); 1715 - 1716 - $no_old = ($change_type == $type_add); 1717 - $no_new = ($change_type == $type_delete); 1699 + $no_old = !$changeset->hasOldState(); 1700 + $no_new = !$changeset->hasNewState(); 1718 1701 1719 1702 if ($no_old) { 1720 1703 $old_ref = null; ··· 1741 1724 $new_ref->setData($new_data); 1742 1725 } 1743 1726 } 1744 - 1745 1727 1746 1728 $old_engines = null; 1747 1729 if ($old_ref) { ··· 1813 1795 $changeset = $this->changeset; 1814 1796 $viewer = $this->getViewer(); 1815 1797 1798 + $old_phid = $changeset->getOldFileObjectPHID(); 1799 + $new_phid = $changeset->getNewFileObjectPHID(); 1800 + 1816 1801 $old_file = null; 1817 1802 $new_file = null; 1818 1803 1819 - // TODO: Improve the architectural issue as discussed in D955 1820 - // https://secure.phabricator.com/D955 1821 - $reference = $this->getRenderingReference(); 1822 - $parts = explode('/', $reference); 1823 - if (count($parts) == 2) { 1824 - list($id, $vs) = $parts; 1825 - } else { 1826 - $id = $parts[0]; 1827 - $vs = 0; 1828 - } 1829 - $id = (int)$id; 1830 - $vs = (int)$vs; 1831 - 1832 - if (!$vs) { 1833 - $metadata = $this->changeset->getMetadata(); 1834 - $old_phid = idx($metadata, 'old:binary-phid'); 1835 - $new_phid = idx($metadata, 'new:binary-phid'); 1836 - } else { 1837 - $vs_changeset = id(new DifferentialChangeset())->load($vs); 1838 - $old_phid = null; 1839 - $new_phid = null; 1840 - 1841 - // TODO: This is spooky, see D6851 1842 - if ($vs_changeset) { 1843 - $vs_metadata = $vs_changeset->getMetadata(); 1844 - $old_phid = idx($vs_metadata, 'new:binary-phid'); 1845 - } 1846 - 1847 - $changeset = id(new DifferentialChangeset())->load($id); 1848 - if ($changeset) { 1849 - $metadata = $changeset->getMetadata(); 1850 - $new_phid = idx($metadata, 'new:binary-phid'); 1851 - } 1852 - } 1853 - 1854 1804 if ($old_phid || $new_phid) { 1855 1805 $file_phids = array(); 1856 1806 if ($old_phid) { ··· 1864 1814 ->setViewer($viewer) 1865 1815 ->withPHIDs($file_phids) 1866 1816 ->execute(); 1817 + $files = mpull($files, null, 'getPHID'); 1867 1818 1868 - foreach ($files as $file) { 1869 - if ($file->getPHID() == $old_phid) { 1870 - $old_file = $file; 1871 - } else if ($file->getPHID() == $new_phid) { 1872 - $new_file = $file; 1819 + if ($old_phid) { 1820 + $old_file = idx($files, $old_phid); 1821 + if (!$old_file) { 1822 + throw new Exception( 1823 + pht( 1824 + 'Failed to load file data for changeset ("%s").', 1825 + $old_phid)); 1826 + } 1827 + $changeset->attachOldFileObject($old_file); 1828 + } 1829 + 1830 + if ($new_phid) { 1831 + $new_file = idx($files, $new_phid); 1832 + if (!$new_file) { 1833 + throw new Exception( 1834 + pht( 1835 + 'Failed to load file data for changeset ("%s").', 1836 + $new_phid)); 1873 1837 } 1838 + $changeset->attachNewFileObject($new_file); 1874 1839 } 1875 1840 } 1876 1841
+19 -9
src/applications/differential/render/DifferentialChangesetRenderer.php
··· 647 647 $old = $changeset->getOldProperties(); 648 648 $new = $changeset->getNewProperties(); 649 649 650 - // When adding files, don't show the uninteresting 644 filemode change. 651 - if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD && 652 - $new == array('unix:filemode' => '100644')) { 653 - unset($new['unix:filemode']); 654 - } 650 + // If a property has been changed, but is not present on one side of the 651 + // change and has an uninteresting default value on the other, remove it. 652 + // This most commonly happens when a change adds or removes a file: the 653 + // side of the change with the file has a "100644" filemode in Git. 655 654 656 - // Likewise when removing files. 657 - if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE && 658 - $old == array('unix:filemode' => '100644')) { 659 - unset($old['unix:filemode']); 655 + $defaults = array( 656 + 'unix:filemode' => '100644', 657 + ); 658 + 659 + foreach ($defaults as $default_key => $default_value) { 660 + $old_value = idx($old, $default_key, $default_value); 661 + $new_value = idx($new, $default_key, $default_value); 662 + 663 + $old_default = ($old_value === $default_value); 664 + $new_default = ($new_value === $default_value); 665 + 666 + if ($old_default && $new_default) { 667 + unset($old[$default_key]); 668 + unset($new[$default_key]); 669 + } 660 670 } 661 671 662 672 $metadata = $changeset->getMetadata();
+137
src/applications/differential/storage/DifferentialChangeset.php
··· 28 28 private $newFileObject = self::ATTACHABLE; 29 29 private $oldFileObject = self::ATTACHABLE; 30 30 31 + private $hasOldState; 32 + private $hasNewState; 33 + private $oldStateMetadata; 34 + private $newStateMetadata; 35 + 31 36 const TABLE_CACHE = 'differential_changeset_parse_cache'; 32 37 33 38 const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted'; ··· 132 137 133 138 public function getChangesetPackages() { 134 139 return $this->changesetPackages; 140 + } 141 + 142 + public function setHasOldState($has_old_state) { 143 + $this->hasOldState = $has_old_state; 144 + return $this; 145 + } 146 + 147 + public function setHasNewState($has_new_state) { 148 + $this->hasNewState = $has_new_state; 149 + return $this; 150 + } 151 + 152 + public function hasOldState() { 153 + if ($this->hasOldState !== null) { 154 + return $this->hasOldState; 155 + } 156 + 157 + $change_type = $this->getChangeType(); 158 + return !DifferentialChangeType::isCreateChangeType($change_type); 159 + } 160 + 161 + public function hasNewState() { 162 + if ($this->hasNewState !== null) { 163 + return $this->hasNewState; 164 + } 165 + 166 + $change_type = $this->getChangeType(); 167 + return !DifferentialChangeType::isDeleteChangeType($change_type); 135 168 } 136 169 137 170 public function save() { ··· 488 521 489 522 public function getOldFileObject() { 490 523 return $this->assertAttached($this->oldFileObject); 524 + } 525 + 526 + public function newComparisonChangeset( 527 + DifferentialChangeset $against = null) { 528 + 529 + $left = $this; 530 + $right = $against; 531 + 532 + $left_data = $left->makeNewFile(); 533 + $left_properties = $left->getNewProperties(); 534 + $left_metadata = $left->getNewStateMetadata(); 535 + $left_state = $left->hasNewState(); 536 + $shared_metadata = $left->getMetadata(); 537 + if ($right) { 538 + $right_data = $right->makeNewFile(); 539 + $right_properties = $right->getNewProperties(); 540 + $right_metadata = $right->getNewStateMetadata(); 541 + $right_state = $right->hasNewState(); 542 + $shared_metadata = $right->getMetadata(); 543 + } else { 544 + $right_data = $left->makeOldFile(); 545 + $right_properties = $left->getOldProperties(); 546 + $right_metadata = $left->getOldStateMetadata(); 547 + $right_state = $left->hasOldState(); 548 + } 549 + 550 + $engine = new PhabricatorDifferenceEngine(); 551 + 552 + $synthetic = $engine->generateChangesetFromFileContent( 553 + $left_data, 554 + $right_data); 555 + 556 + $comparison = id(new self()) 557 + ->makeEphemeral(true) 558 + ->attachDiff($left->getDiff()) 559 + ->setOldFile($left->getFilename()) 560 + ->setFilename($right->getFilename()); 561 + 562 + // TODO: Change type? 563 + // TODO: File type? 564 + // TODO: Away paths? 565 + // TODO: View state key? 566 + 567 + $comparison->attachHunks($synthetic->getHunks()); 568 + 569 + $comparison->setOldProperties($left_properties); 570 + $comparison->setNewProperties($right_properties); 571 + 572 + $comparison 573 + ->setOldStateMetadata($left_metadata) 574 + ->setNewStateMetadata($right_metadata) 575 + ->setHasOldState($left_state) 576 + ->setHasNewState($right_state); 577 + 578 + // NOTE: Some metadata is not stored statefully, like the "generated" 579 + // flag. For now, use the rightmost "new state" metadata to fill in these 580 + // values. 581 + 582 + $metadata = $comparison->getMetadata(); 583 + $metadata = $metadata + $shared_metadata; 584 + $comparison->setMetadata($metadata); 585 + 586 + return $comparison; 587 + } 588 + 589 + public function getNewStateMetadata() { 590 + return $this->getMetadataWithPrefix('new:'); 591 + } 592 + 593 + public function setNewStateMetadata(array $metadata) { 594 + return $this->setMetadataWithPrefix($metadata, 'new:'); 595 + } 596 + 597 + public function getOldStateMetadata() { 598 + return $this->getMetadataWithPrefix('old:'); 599 + } 600 + 601 + public function setOldStateMetadata(array $metadata) { 602 + return $this->setMetadataWithPrefix($metadata, 'old:'); 603 + } 604 + 605 + private function getMetadataWithPrefix($prefix) { 606 + $length = strlen($prefix); 607 + 608 + $result = array(); 609 + foreach ($this->getMetadata() as $key => $value) { 610 + if (strncmp($key, $prefix, $length)) { 611 + continue; 612 + } 613 + 614 + $key = substr($key, $length); 615 + $result[$key] = $value; 616 + } 617 + 618 + return $result; 619 + } 620 + 621 + private function setMetadataWithPrefix(array $metadata, $prefix) { 622 + foreach ($metadata as $key => $value) { 623 + $key = $prefix.$key; 624 + $this->metadata[$key] = $value; 625 + } 626 + 627 + return $this; 491 628 } 492 629 493 630