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

When showing a diff-of-diffs, hide files which didn't get any more changes and have no inlines

Summary:
Ref T13137. See that task for discussion.

When we show a diff-of-diffs, we often render stubs for files which didn't change between the diffs. These stubs usually aren't a big deal, but for certain types of changes (like refactors) they can create a lot of clutter.

Instead, hide these stubs and show a notice that we hid them.

Test Plan:
- Created a revision affecting 4 files.
- Updated it with a diff that changed only 1 of the 4 files.
- Added an inline comment to a different file.
- Viewed the diff of diffs.
- Before: 4 changesets with two "nothing changed" stubs.
- After: 2 changesets with the stubs hidden.

{F5621083}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13137

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

+153 -20
+81 -20
src/applications/differential/controller/DifferentialRevisionViewController.php
··· 5 5 6 6 private $revisionID; 7 7 private $changesetCount; 8 + private $hiddenChangesets; 8 9 9 10 public function shouldAllowPublic() { 10 11 return true; ··· 169 170 } 170 171 171 172 $handles = $this->loadViewerHandles($object_phids); 173 + $warnings = array(); 172 174 173 175 $request_uri = $request->getRequestURI(); 174 176 ··· 203 205 pht('Expand All Files'))), 204 206 ); 205 207 206 - $warning = id(new PHUIInfoView()) 208 + $warnings[] = id(new PHUIInfoView()) 207 209 ->setTitle(pht('Large Diff')) 208 210 ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 209 211 ->appendChild($message); 210 212 213 + $folded_changesets = $changesets; 214 + } else { 215 + $folded_changesets = array(); 216 + } 217 + 218 + // Don't hide or fold changesets which have inline comments. 219 + $hidden_changesets = $this->hiddenChangesets; 220 + if ($hidden_changesets || $folded_changesets) { 211 221 $old = array_select_keys($changesets, $old_ids); 212 222 $new = array_select_keys($changesets, $new_ids); 213 223 ··· 222 232 $new, 223 233 $revision); 224 234 225 - $visible_changesets = array(); 226 235 foreach ($inlines as $inline) { 227 236 $changeset_id = $inline->getChangesetID(); 228 - if (isset($changesets[$changeset_id])) { 229 - $visible_changesets[$changeset_id] = $changesets[$changeset_id]; 237 + if (!isset($changesets[$changeset_id])) { 238 + continue; 230 239 } 240 + 241 + unset($hidden_changesets[$changeset_id]); 242 + unset($folded_changesets[$changeset_id]); 231 243 } 232 - } else { 233 - $warning = null; 234 - $visible_changesets = $changesets; 244 + } 245 + 246 + // If we would hide only one changeset, don't hide anything. The notice 247 + // we'd render about it is about the same size as the changeset. 248 + if (count($hidden_changesets) < 2) { 249 + $hidden_changesets = array(); 250 + } 251 + 252 + // Update the set of hidden changesets, since we may have just un-hidden 253 + // some of them. 254 + if ($hidden_changesets) { 255 + $warnings[] = id(new PHUIInfoView()) 256 + ->setTitle(pht('Showing Only Differences')) 257 + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) 258 + ->appendChild( 259 + pht( 260 + 'This revision modifies %s more files that are hidden because '. 261 + 'they were not modified between selected diffs and they have no '. 262 + 'inline comments.', 263 + phutil_count($hidden_changesets))); 264 + } 265 + 266 + // Compute the unfolded changesets. By default, everything is unfolded. 267 + $unfolded_changesets = $changesets; 268 + foreach ($folded_changesets as $changeset_id => $changeset) { 269 + unset($unfolded_changesets[$changeset_id]); 270 + } 271 + 272 + // Throw away any hidden changesets. 273 + foreach ($hidden_changesets as $changeset_id => $changeset) { 274 + unset($changesets[$changeset_id]); 275 + unset($unfolded_changesets[$changeset_id]); 235 276 } 236 277 237 278 $commit_hashes = mpull($diffs, 'getSourceControlBaseRevision'); ··· 267 308 if ($repository) { 268 309 $symbol_indexes = $this->buildSymbolIndexes( 269 310 $repository, 270 - $visible_changesets); 311 + $unfolded_changesets); 271 312 } else { 272 313 $symbol_indexes = array(); 273 314 } ··· 328 369 } else { 329 370 $changeset_view = id(new DifferentialChangesetListView()) 330 371 ->setChangesets($changesets) 331 - ->setVisibleChangesets($visible_changesets) 372 + ->setVisibleChangesets($unfolded_changesets) 332 373 ->setStandaloneURI('/differential/changeset/') 333 374 ->setRawFileURIs( 334 375 '/differential/changeset/?view=old', ··· 405 446 406 447 $toc_view = $this->buildTableOfContents( 407 448 $changesets, 408 - $visible_changesets, 449 + $unfolded_changesets, 409 450 $target->loadCoverageMap($viewer)); 410 451 411 452 // Attach changesets to each reviewer so we can show which Owners package ··· 520 561 521 562 $footer[] = array( 522 563 $anchor, 523 - $warning, 564 + $warnings, 524 565 $tab_view, 525 566 $changeset_view, 526 567 ); ··· 807 848 DifferentialDiff $target, 808 849 DifferentialDiff $diff_vs = null, 809 850 PhabricatorRepository $repository = null) { 851 + $viewer = $this->getViewer(); 810 852 811 853 $load_diffs = array($target); 812 854 if ($diff_vs) { ··· 814 856 } 815 857 816 858 $raw_changesets = id(new DifferentialChangesetQuery()) 817 - ->setViewer($this->getRequest()->getUser()) 859 + ->setViewer($viewer) 818 860 ->withDiffs($load_diffs) 819 861 ->execute(); 820 862 $changeset_groups = mgroup($raw_changesets, 'getDiffID'); ··· 822 864 $changesets = idx($changeset_groups, $target->getID(), array()); 823 865 $changesets = mpull($changesets, null, 'getID'); 824 866 825 - $refs = array(); 826 - $vs_map = array(); 867 + $refs = array(); 868 + $vs_map = array(); 827 869 $vs_changesets = array(); 870 + $must_compare = array(); 828 871 if ($diff_vs) { 829 - $vs_id = $diff_vs->getID(); 872 + $vs_id = $diff_vs->getID(); 830 873 $vs_changesets_path_map = array(); 831 874 foreach (idx($changeset_groups, $vs_id, array()) as $changeset) { 832 875 $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs); 833 876 $vs_changesets_path_map[$path] = $changeset; 834 877 $vs_changesets[$changeset->getID()] = $changeset; 835 878 } 879 + 836 880 foreach ($changesets as $key => $changeset) { 837 881 $path = $changeset->getAbsoluteRepositoryPath($repository, $target); 838 882 if (isset($vs_changesets_path_map[$path])) { ··· 841 885 $refs[$changeset->getID()] = 842 886 $changeset->getID().'/'.$vs_changesets_path_map[$path]->getID(); 843 887 unset($vs_changesets_path_map[$path]); 888 + 889 + $must_compare[] = $changeset->getID(); 890 + 844 891 } else { 845 892 $refs[$changeset->getID()] = $changeset->getID(); 846 893 } 847 894 } 895 + 848 896 foreach ($vs_changesets_path_map as $path => $changeset) { 849 897 $changesets[$changeset->getID()] = $changeset; 850 - $vs_map[$changeset->getID()] = -1; 851 - $refs[$changeset->getID()] = $changeset->getID().'/-1'; 898 + $vs_map[$changeset->getID()] = -1; 899 + $refs[$changeset->getID()] = $changeset->getID().'/-1'; 852 900 } 901 + 853 902 } else { 854 903 foreach ($changesets as $changeset) { 855 904 $refs[$changeset->getID()] = $changeset->getID(); ··· 858 907 859 908 $changesets = msort($changesets, 'getSortKey'); 860 909 910 + // See T13137. When displaying the diff between two updates, hide any 911 + // changesets which haven't actually changed. 912 + $this->hiddenChangesets = array(); 913 + foreach ($must_compare as $changeset_id) { 914 + $changeset = $changesets[$changeset_id]; 915 + $vs_changeset = $vs_changesets[$vs_map[$changeset_id]]; 916 + 917 + if ($changeset->hasSameEffectAs($vs_changeset)) { 918 + $this->hiddenChangesets[$changeset_id] = $changesets[$changeset_id]; 919 + } 920 + } 921 + 861 922 return array($changesets, $vs_map, $vs_changesets, $refs); 862 923 } 863 924 864 925 private function buildSymbolIndexes( 865 926 PhabricatorRepository $repository, 866 - array $visible_changesets) { 867 - assert_instances_of($visible_changesets, 'DifferentialChangeset'); 927 + array $unfolded_changesets) { 928 + assert_instances_of($unfolded_changesets, 'DifferentialChangeset'); 868 929 869 930 $engine = PhabricatorSyntaxHighlighter::newEngine(); 870 931 ··· 889 950 $sources); 890 951 891 952 $indexed_langs = array_fill_keys($langs, true); 892 - foreach ($visible_changesets as $key => $changeset) { 953 + foreach ($unfolded_changesets as $key => $changeset) { 893 954 $lang = $engine->getLanguageFromFilename($changeset->getFilename()); 894 955 if (empty($indexed_langs) || isset($indexed_langs[$lang])) { 895 956 $symbol_indexes[$key] = array(
+25
src/applications/differential/engine/DifferentialChangesetEngine.php
··· 7 7 8 8 foreach ($changesets as $changeset) { 9 9 $this->detectGeneratedCode($changeset); 10 + $this->computeHashes($changeset); 10 11 } 11 12 12 13 $this->detectCopiedCode($changesets); ··· 56 57 } 57 58 58 59 return false; 60 + } 61 + 62 + 63 + /* -( Content Hashes )----------------------------------------------------- */ 64 + 65 + 66 + private function computeHashes(DifferentialChangeset $changeset) { 67 + 68 + $effect_key = DifferentialChangeset::METADATA_EFFECT_HASH; 69 + 70 + $effect_hash = $this->newEffectHash($changeset); 71 + if ($effect_hash !== null) { 72 + $changeset->setChangesetMetadata($effect_key, $effect_hash); 73 + } 74 + } 75 + 76 + private function newEffectHash(DifferentialChangeset $changeset) { 77 + 78 + if ($changeset->getHunks()) { 79 + $new_data = $changeset->makeNewFile(); 80 + return PhabricatorHash::digestForIndex($new_data); 81 + } 82 + 83 + return null; 59 84 } 60 85 61 86
+4
src/applications/differential/management/PhabricatorDifferentialRebuildChangesetsWorkflow.php
··· 82 82 id(new DifferentialChangesetEngine()) 83 83 ->rebuildChangesets($changesets); 84 84 85 + foreach ($changesets as $changeset) { 86 + $changeset->save(); 87 + } 88 + 85 89 echo tsprintf( 86 90 "%s\n", 87 91 pht('Done.'));
+43
src/applications/differential/storage/DifferentialChangeset.php
··· 26 26 27 27 const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted'; 28 28 const METADATA_UNTRUSTED_ATTRIBUTES = 'attributes.untrusted'; 29 + const METADATA_EFFECT_HASH = 'hash.effect'; 29 30 30 31 const ATTRIBUTE_GENERATED = 'generated'; 31 32 ··· 141 142 $ret = parent::delete(); 142 143 $this->saveTransaction(); 143 144 return $ret; 145 + } 146 + 147 + /** 148 + * Test if this changeset and some other changeset put the affected file in 149 + * the same state. 150 + * 151 + * @param DifferentialChangeset Changeset to compare against. 152 + * @return bool True if the two changesets have the same effect. 153 + */ 154 + public function hasSameEffectAs(DifferentialChangeset $other) { 155 + if ($this->getFilename() !== $other->getFilename()) { 156 + return false; 157 + } 158 + 159 + $hash_key = self::METADATA_EFFECT_HASH; 160 + 161 + $u_hash = $this->getChangesetMetadata($hash_key); 162 + if ($u_hash === null) { 163 + return false; 164 + } 165 + 166 + $v_hash = $other->getChangesetMetadata($hash_key); 167 + if ($v_hash === null) { 168 + return false; 169 + } 170 + 171 + if ($u_hash !== $v_hash) { 172 + return false; 173 + } 174 + 175 + // Make sure the final states for the file properties (like the "+x" 176 + // executable bit) match one another. 177 + $u_props = $this->getNewProperties(); 178 + $v_props = $other->getNewProperties(); 179 + ksort($u_props); 180 + ksort($v_props); 181 + 182 + if ($u_props !== $v_props) { 183 + return false; 184 + } 185 + 186 + return true; 144 187 } 145 188 146 189 public function getSortKey() {