@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
at upstream/main 261 lines 6.8 kB view raw
1<?php 2 3/** 4 * Utility class which encapsulates some shared behavior between different 5 * applications which render diffs. 6 * 7 * @task config Configuring the Engine 8 * @task diff Generating Diffs 9 */ 10final class PhabricatorDifferenceEngine extends Phobject { 11 12 13 private $oldName; 14 private $newName; 15 private $normalize; 16 17 18/* -( Configuring the Engine )--------------------------------------------- */ 19 20 21 /** 22 * Set the name to identify the old file with. Primarily cosmetic. 23 * 24 * @param string $old_name Old file name. 25 * @return $this 26 * @task config 27 */ 28 public function setOldName($old_name) { 29 $this->oldName = $old_name; 30 return $this; 31 } 32 33 34 /** 35 * Set the name to identify the new file with. Primarily cosmetic. 36 * 37 * @param string $new_name New file name. 38 * @return $this 39 * @task config 40 */ 41 public function setNewName($new_name) { 42 $this->newName = $new_name; 43 return $this; 44 } 45 46 47 public function setNormalize($normalize) { 48 $this->normalize = $normalize; 49 return $this; 50 } 51 52 public function getNormalize() { 53 return $this->normalize; 54 } 55 56 57/* -( Generating Diffs )--------------------------------------------------- */ 58 59 60 /** 61 * Generate a raw diff from two raw files. This is a lower-level API than 62 * @{method:generateChangesetFromFileContent}, but may be useful if you need 63 * to use a custom parser configuration, as with Diffusion. 64 * 65 * @param string $old Entire previous file content. 66 * @param string $new Entire current file content. 67 * @return string Raw diff between the two files. 68 * @task diff 69 */ 70 public function generateRawDiffFromFileContent($old, $new) { 71 72 $options = array(); 73 74 // Generate diffs with full context. 75 $options[] = '-U65535'; 76 77 $old_name = nonempty($this->oldName, '/dev/universe').' 9999-99-99'; 78 $new_name = nonempty($this->newName, '/dev/universe').' 9999-99-99'; 79 80 $options[] = '-L'; 81 $options[] = $old_name; 82 $options[] = '-L'; 83 $options[] = $new_name; 84 85 $normalize = $this->getNormalize(); 86 if ($normalize) { 87 $old = $this->normalizeFile($old); 88 $new = $this->normalizeFile($new); 89 } 90 91 $old_tmp = new TempFile(); 92 $new_tmp = new TempFile(); 93 94 Filesystem::writeFile($old_tmp, $old); 95 Filesystem::writeFile($new_tmp, $new); 96 list($err, $diff) = exec_manual( 97 'diff %Ls %s %s', 98 $options, 99 $old_tmp, 100 $new_tmp); 101 102 if (!$err) { 103 // This indicates that the two files are the same. Build a synthetic, 104 // changeless diff so that we can still render the raw, unchanged file 105 // instead of being forced to just say "this file didn't change" since we 106 // don't have the content. 107 108 $entire_file = explode("\n", $old); 109 foreach ($entire_file as $k => $line) { 110 $entire_file[$k] = ' '.$line; 111 } 112 113 $len = count($entire_file); 114 $entire_file = implode("\n", $entire_file); 115 116 // TODO: If both files were identical but missing newlines, we probably 117 // get this wrong. Unclear if it ever matters. 118 119 // This is a bit hacky but the diff parser can handle it. 120 $diff = "--- {$old_name}\n". 121 "+++ {$new_name}\n". 122 "@@ -1,{$len} +1,{$len} @@\n". 123 $entire_file."\n"; 124 } 125 126 return $diff; 127 } 128 129 130 /** 131 * Generate an @{class:DifferentialChangeset} from two raw files. This is 132 * principally useful because you can feed the output to 133 * @{class:DifferentialChangesetParser} in order to render it. 134 * 135 * @param string $old Entire previous file content. 136 * @param string $new Entire current file content. 137 * @return DifferentialChangeset Synthetic changeset. 138 * @task diff 139 */ 140 public function generateChangesetFromFileContent($old, $new) { 141 $diff = $this->generateRawDiffFromFileContent($old, $new); 142 143 $changes = id(new ArcanistDiffParser())->parseDiff($diff); 144 $diff = DifferentialDiff::newEphemeralFromRawChanges( 145 $changes); 146 return head($diff->getChangesets()); 147 } 148 149 private function normalizeFile($corpus) { 150 // We can freely apply any other transformations we want to here: we have 151 // no constraints on what we need to preserve. If we normalize every line 152 // to "cat", the diff will still work, the alignment of the "-" / "+" 153 // lines will just be very hard to read. 154 155 // In general, we'll make the diff better if we normalize two lines that 156 // humans think are the same. 157 158 // We'll make the diff worse if we normalize two lines that humans think 159 // are different. 160 161 162 // Strip all whitespace present anywhere in the diff, since humans never 163 // consider whitespace changes to alter the line into a "different line" 164 // even when they're semantic (e.g., in a string constant). This covers 165 // indentation changes, trailing whitepspace, and formatting changes 166 // like "+/-". 167 $corpus = preg_replace('/[ \t]/', '', $corpus); 168 169 return $corpus; 170 } 171 172 public static function applyIntralineDiff($str, $intra_stack) { 173 $buf = ''; 174 $p = $s = $e = 0; // position, start, end 175 $highlight = $tag = $ent = false; 176 $highlight_o = '<span class="bright">'; 177 $highlight_c = '</span>'; 178 179 $depth_in = '<span class="depth-in">'; 180 $depth_out = '<span class="depth-out">'; 181 182 $is_html = false; 183 if ($str instanceof PhutilSafeHTML) { 184 $is_html = true; 185 $str = $str->getHTMLContent(); 186 } 187 188 $n = strlen($str); 189 for ($i = 0; $i < $n; $i++) { 190 191 if ($p == $e) { 192 do { 193 if (empty($intra_stack)) { 194 $buf .= substr($str, $i); 195 break 2; 196 } 197 $stack = array_shift($intra_stack); 198 $s = $e; 199 $e += $stack[1]; 200 } while ($stack[0] === 0); 201 202 switch ($stack[0]) { 203 case '>': 204 $open_tag = $depth_in; 205 break; 206 case '<': 207 $open_tag = $depth_out; 208 break; 209 default: 210 $open_tag = $highlight_o; 211 break; 212 } 213 } 214 215 if (!$highlight && !$tag && !$ent && $p == $s) { 216 $buf .= $open_tag; 217 $highlight = true; 218 } 219 220 if ($str[$i] == '<') { 221 $tag = true; 222 if ($highlight) { 223 $buf .= $highlight_c; 224 } 225 } 226 227 if (!$tag) { 228 if ($str[$i] == '&') { 229 $ent = true; 230 } 231 if ($ent && $str[$i] == ';') { 232 $ent = false; 233 } 234 if (!$ent) { 235 $p++; 236 } 237 } 238 239 $buf .= $str[$i]; 240 241 if ($tag && $str[$i] == '>') { 242 $tag = false; 243 if ($highlight) { 244 $buf .= $open_tag; 245 } 246 } 247 248 if ($highlight && ($p == $e || $i == $n - 1)) { 249 $buf .= $highlight_c; 250 $highlight = false; 251 } 252 } 253 254 if ($is_html) { 255 return phutil_safe_html($buf); 256 } 257 258 return $buf; 259 } 260 261}