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

Implement rough content-aware inline adjustment rules for ghosts

Summary:
Ref T7447. Fixes T7600. This likely needs significant adjustment, but implements content-aware comment porting for line changes.

Specifically, this moves lines around to adjust their position considering added and removed lines between the diffs and across rebases.

It does not try to do any actual content (line against line) matching.

Test Plan:
- Unit tests.
- Poking around in the web UI seems to generate mostly reasonable-ish results?
- This may be a huge step backward in some cases that I just haven't hit.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: yelirekim, epriestley

Maniphest Tasks: T7600, T7447

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

+953 -1
+4
src/__phutil_library_map__.php
··· 293 293 'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php', 294 294 'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php', 295 295 'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php', 296 + 'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php', 296 297 'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php', 297 298 'DifferentialApplyPatchField' => 'applications/differential/customfield/DifferentialApplyPatchField.php', 298 299 'DifferentialArcanistProjectField' => 'applications/differential/customfield/DifferentialArcanistProjectField.php', ··· 391 392 'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php', 392 393 'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php', 393 394 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', 395 + 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 394 396 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', 395 397 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', 396 398 'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php', ··· 3530 3532 'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand', 3531 3533 'DifferentialActionMenuEventListener' => 'PhabricatorEventListener', 3532 3534 'DifferentialAddCommentView' => 'AphrontView', 3535 + 'DifferentialAdjustmentMapTestCase' => 'ArcanistPhutilTestCase', 3533 3536 'DifferentialAffectedPath' => 'DifferentialDAO', 3534 3537 'DifferentialApplyPatchField' => 'DifferentialCustomField', 3535 3538 'DifferentialArcanistProjectField' => 'DifferentialCustomField', ··· 3632 3635 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', 3633 3636 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener', 3634 3637 'DifferentialLegacyHunk' => 'DifferentialHunk', 3638 + 'DifferentialLineAdjustmentMap' => 'Phobject', 3635 3639 'DifferentialLintField' => 'DifferentialCustomField', 3636 3640 'DifferentialLocalCommitsView' => 'AphrontView', 3637 3641 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
+376
src/applications/differential/parser/DifferentialLineAdjustmentMap.php
··· 1 + <?php 2 + 3 + /** 4 + * Datastructure which follows lines of code across source changes. 5 + * 6 + * This map is used to update the positions of inline comments after diff 7 + * updates. For example, if a inline comment appeared on line 30 of a diff 8 + * but the next update adds 15 more lines above it, the comment should move 9 + * down to line 45. 10 + * 11 + */ 12 + final class DifferentialLineAdjustmentMap extends Phobject { 13 + 14 + private $map; 15 + private $nearestMap; 16 + private $isInverse; 17 + private $finalOffset; 18 + private $nextMapInChain; 19 + 20 + /** 21 + * Get the raw adjustment map. 22 + */ 23 + public function getMap() { 24 + return $this->map; 25 + } 26 + 27 + public function getNearestMap() { 28 + if ($this->nearestMap === null) { 29 + $this->buildNearestMap(); 30 + } 31 + 32 + return $this->nearestMap; 33 + } 34 + 35 + public function getFinalOffset() { 36 + // Make sure we've built this map already. 37 + $this->getNearestMap(); 38 + return $this->finalOffset; 39 + } 40 + 41 + 42 + /** 43 + * Add a map to the end of the chain. 44 + * 45 + * When a line is mapped with @{method:mapLine}, it is mapped through all 46 + * maps in the chain. 47 + */ 48 + public function addMapToChain(DifferentialLineAdjustmentMap $map) { 49 + if ($this->nextMapInChain) { 50 + $this->nextMapInChain->addMapToChain($map); 51 + } else { 52 + $this->nextMapInChain = $map; 53 + } 54 + return $this; 55 + } 56 + 57 + 58 + /** 59 + * Map a line across a change, or a series of changes. 60 + * 61 + * @param int Line to map 62 + * @param bool True to map it as the end of a range. 63 + * @return wild Spooky magic. 64 + */ 65 + public function mapLine($line, $is_end) { 66 + $nmap = $this->getNearestMap(); 67 + 68 + $deleted = false; 69 + $offset = false; 70 + if (isset($nmap[$line])) { 71 + $line_range = $nmap[$line]; 72 + if ($is_end) { 73 + $to_line = end($line_range); 74 + } else { 75 + $to_line = reset($line_range); 76 + } 77 + if ($to_line <= 0) { 78 + // If we're tracing the first line and this block is collapsing, 79 + // compute the offset from the top of the block. 80 + if (!$is_end && $this->isInverse) { 81 + $offset = 0; 82 + $cursor = $line - 1; 83 + while (isset($nmap[$cursor])) { 84 + $prev = $nmap[$cursor]; 85 + $prev = reset($prev); 86 + if ($prev == $to_line) { 87 + $offset++; 88 + } else { 89 + break; 90 + } 91 + $cursor--; 92 + } 93 + } 94 + 95 + $to_line = -$to_line; 96 + if (!$this->isInverse) { 97 + $deleted = true; 98 + } 99 + } 100 + $line = $to_line; 101 + } else { 102 + $line = $line + $this->finalOffset; 103 + } 104 + 105 + if ($this->nextMapInChain) { 106 + $chain = $this->nextMapInChain->mapLine($line, $is_end); 107 + list($chain_deleted, $chain_offset, $line) = $chain; 108 + $deleted = ($deleted || $chain_deleted); 109 + if ($chain_offset !== false) { 110 + if ($offset === false) { 111 + $offset = 0; 112 + } 113 + $offset += $chain_offset; 114 + } 115 + } 116 + 117 + return array($deleted, $offset, $line); 118 + } 119 + 120 + 121 + /** 122 + * Build a derived map which maps deleted lines to the nearest valid line. 123 + * 124 + * This computes a "nearest line" map and a final-line offset. These 125 + * derived maps allow us to map deleted code to the previous (or next) line 126 + * which actually exists. 127 + */ 128 + private function buildNearestMap() { 129 + $map = $this->map; 130 + $nmap = array(); 131 + 132 + $nearest = 0; 133 + foreach ($map as $key => $value) { 134 + if ($value) { 135 + $nmap[$key] = $value; 136 + $nearest = end($value); 137 + } else { 138 + $nmap[$key][0] = -$nearest; 139 + } 140 + } 141 + 142 + if (isset($key)) { 143 + $this->finalOffset = ($nearest - $key); 144 + } else { 145 + $this->finalOffset = 0; 146 + } 147 + 148 + foreach (array_reverse($map, true) as $key => $value) { 149 + if ($value) { 150 + $nearest = reset($value); 151 + } else { 152 + $nmap[$key][1] = -$nearest; 153 + } 154 + } 155 + 156 + $this->nearestMap = $nmap; 157 + 158 + return $this; 159 + } 160 + 161 + public static function newFromHunks(array $hunks) { 162 + assert_instances_of($hunks, 'DifferentialHunk'); 163 + 164 + $map = array(); 165 + $o = 0; 166 + $n = 0; 167 + 168 + $hunks = msort($hunks, 'getOldOffset'); 169 + foreach ($hunks as $hunk) { 170 + 171 + // If the hunks are disjoint, add the implied missing lines where 172 + // nothing changed. 173 + $min = ($hunk->getOldOffset() - 1); 174 + while ($o < $min) { 175 + $o++; 176 + $n++; 177 + $map[$o][] = $n; 178 + } 179 + 180 + $lines = $hunk->getStructuredLines(); 181 + foreach ($lines as $line) { 182 + switch ($line['type']) { 183 + case '-': 184 + $o++; 185 + $map[$o] = array(); 186 + break; 187 + case '+': 188 + $n++; 189 + $map[$o][] = $n; 190 + break; 191 + case ' ': 192 + $o++; 193 + $n++; 194 + $map[$o][] = $n; 195 + break; 196 + default: 197 + break; 198 + } 199 + } 200 + } 201 + 202 + $map = self::reduceMapRanges($map); 203 + 204 + return self::newFromMap($map); 205 + } 206 + 207 + public static function newFromMap(array $map) { 208 + $obj = new DifferentialLineAdjustmentMap(); 209 + $obj->map = $map; 210 + return $obj; 211 + } 212 + 213 + public static function newInverseMap(DifferentialLineAdjustmentMap $map) { 214 + $old = $map->getMap(); 215 + $inv = array(); 216 + $last = 0; 217 + foreach ($old as $k => $v) { 218 + if (count($v) > 1) { 219 + $v = range(reset($v), end($v)); 220 + } 221 + if ($k == 0) { 222 + foreach ($v as $line) { 223 + $inv[$line] = array(); 224 + $last = $line; 225 + } 226 + } else if ($v) { 227 + $first = true; 228 + foreach ($v as $line) { 229 + if ($first) { 230 + $first = false; 231 + $inv[$line][] = $k; 232 + $last = $line; 233 + } else { 234 + $inv[$line] = array(); 235 + } 236 + } 237 + } else { 238 + $inv[$last][] = $k; 239 + } 240 + } 241 + 242 + $inv = self::reduceMapRanges($inv); 243 + 244 + $obj = new DifferentialLineAdjustmentMap(); 245 + $obj->map = $inv; 246 + $obj->isInverse = !$map->isInverse; 247 + return $obj; 248 + } 249 + 250 + private static function reduceMapRanges(array $map) { 251 + foreach ($map as $key => $values) { 252 + if (count($values) > 2) { 253 + $map[$key] = array(reset($values), end($values)); 254 + } 255 + } 256 + return $map; 257 + } 258 + 259 + 260 + public static function loadMaps(array $maps) { 261 + $keys = array(); 262 + foreach ($maps as $map) { 263 + list($u, $v) = $map; 264 + $keys[self::getCacheKey($u, $v)] = $map; 265 + } 266 + 267 + $cache = new PhabricatorKeyValueDatabaseCache(); 268 + $cache = new PhutilKeyValueCacheProfiler($cache); 269 + $cache->setProfiler(PhutilServiceProfiler::getInstance()); 270 + 271 + $results = array(); 272 + 273 + if ($keys) { 274 + $caches = $cache->getKeys(array_keys($keys)); 275 + foreach ($caches as $key => $value) { 276 + list($u, $v) = $keys[$key]; 277 + try { 278 + $results[$u][$v] = self::newFromMap( 279 + phutil_json_decode($value)); 280 + } catch (Exception $ex) { 281 + // Ignore, rebuild below. 282 + } 283 + unset($keys[$key]); 284 + } 285 + } 286 + 287 + if ($keys) { 288 + $built = self::buildMaps($maps); 289 + 290 + $write = array(); 291 + foreach ($built as $u => $list) { 292 + foreach ($list as $v => $map) { 293 + $write[self::getCacheKey($u, $v)] = json_encode($map->getMap()); 294 + $results[$u][$v] = $map; 295 + } 296 + } 297 + 298 + $cache->setKeys($write); 299 + } 300 + 301 + return $results; 302 + } 303 + 304 + private static function buildMaps(array $maps) { 305 + $need = array(); 306 + foreach ($maps as $map) { 307 + list($u, $v) = $map; 308 + $need[$u] = $u; 309 + $need[$v] = $v; 310 + } 311 + 312 + if ($need) { 313 + $changesets = id(new DifferentialChangesetQuery()) 314 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 315 + ->withIDs($need) 316 + ->needHunks(true) 317 + ->execute(); 318 + $changesets = mpull($changesets, null, 'getID'); 319 + } 320 + 321 + $results = array(); 322 + foreach ($maps as $map) { 323 + list($u, $v) = $map; 324 + $u_set = idx($changesets, $u); 325 + $v_set = idx($changesets, $v); 326 + 327 + if (!$u_set || !$v_set) { 328 + continue; 329 + } 330 + 331 + // This is the simple case. 332 + if ($u == $v) { 333 + $results[$u][$v] = self::newFromHunks( 334 + $u_set->getHunks()); 335 + continue; 336 + } 337 + 338 + $u_old = $u_set->makeOldFile(); 339 + $v_old = $v_set->makeOldFile(); 340 + 341 + // No difference between the two left sides. 342 + if ($u_old == $v_old) { 343 + $results[$u][$v] = self::newFromMap( 344 + array()); 345 + continue; 346 + } 347 + 348 + // If we're missing context, this won't currently work. We can 349 + // make this case work, but it's fairly rare. 350 + $u_hunks = $u_set->getHunks(); 351 + $v_hunks = $v_set->getHunks(); 352 + if (count($u_hunks) != 1 || 353 + count($v_hunks) != 1 || 354 + head($u_hunks)->getOldOffset() != 1 || 355 + head($u_hunks)->getNewOffset() != 1 || 356 + head($v_hunks)->getOldOffset() != 1 || 357 + head($v_hunks)->getNewOffset() != 1) { 358 + continue; 359 + } 360 + 361 + $changeset = id(new PhabricatorDifferenceEngine()) 362 + ->setIgnoreWhitespace(true) 363 + ->generateChangesetFromFileContent($u_old, $v_old); 364 + 365 + $results[$u][$v] = self::newFromHunks( 366 + $changeset->getHunks()); 367 + } 368 + 369 + return $results; 370 + } 371 + 372 + private static function getCacheKey($u, $v) { 373 + return 'diffadjust.v1('.$u.','.$v.')'; 374 + } 375 + 376 + }
+102
src/applications/differential/query/DifferentialInlineCommentQuery.php
··· 323 323 'new' => $is_new, 324 324 'reason' => $reason, 325 325 'href' => $href, 326 + 'originalID' => $changeset->getID(), 326 327 )); 327 328 328 329 $results[] = $inline; ··· 345 346 if (!isset($keep_map[$changeset_id][$is_new])) { 346 347 unset($results[$key]); 347 348 continue; 349 + } 350 + } 351 + 352 + // Adjust inline line numbers to account for content changes across 353 + // updates and rebases. 354 + $plan = array(); 355 + $need = array(); 356 + foreach ($results as $inline) { 357 + $ghost = $inline->getIsGhost(); 358 + if (!$ghost) { 359 + // If this isn't a "ghost" inline, ignore it. 360 + continue; 361 + } 362 + 363 + $src_id = $ghost['originalID']; 364 + $dst_id = $inline->getChangesetID(); 365 + 366 + $xforms = array(); 367 + 368 + // If the comment is on the right, transform it through the inverse map 369 + // back to the left. 370 + if ($inline->getIsNewFile()) { 371 + $xforms[] = array($src_id, $src_id, true); 372 + } 373 + 374 + // Transform it across rebases. 375 + $xforms[] = array($src_id, $dst_id, false); 376 + 377 + // If the comment is on the right, transform it back onto the right. 378 + if ($inline->getIsNewFile()) { 379 + $xforms[] = array($dst_id, $dst_id, false); 380 + } 381 + 382 + $key = array(); 383 + foreach ($xforms as $xform) { 384 + list($u, $v, $inverse) = $xform; 385 + 386 + $short = $u.'/'.$v; 387 + $need[$short] = array($u, $v); 388 + 389 + $part = $u.($inverse ? '<' : '>').$v; 390 + $key[] = $part; 391 + } 392 + $key = implode(',', $key); 393 + 394 + if (empty($plan[$key])) { 395 + $plan[$key] = array( 396 + 'xforms' => $xforms, 397 + 'inlines' => array(), 398 + ); 399 + } 400 + 401 + $plan[$key]['inlines'][] = $inline; 402 + } 403 + 404 + if ($need) { 405 + $maps = DifferentialLineAdjustmentMap::loadMaps($need); 406 + } else { 407 + $maps = array(); 408 + } 409 + 410 + foreach ($plan as $step) { 411 + $xforms = $step['xforms']; 412 + 413 + $chain = null; 414 + foreach ($xforms as $xform) { 415 + list($u, $v, $inverse) = $xform; 416 + $map = idx(idx($maps, $u, array()), $v); 417 + if (!$map) { 418 + continue 2; 419 + } 420 + 421 + if ($inverse) { 422 + $map = DifferentialLineAdjustmentMap::newInverseMap($map); 423 + } else { 424 + $map = clone $map; 425 + } 426 + 427 + if ($chain) { 428 + $chain->addMapToChain($map); 429 + } else { 430 + $chain = $map; 431 + } 432 + } 433 + 434 + foreach ($step['inlines'] as $inline) { 435 + $head_line = $inline->getLineNumber(); 436 + $tail_line = ($head_line + $inline->getLineLength()); 437 + 438 + $head_info = $chain->mapLine($head_line, false); 439 + $tail_info = $chain->mapLine($tail_line, true); 440 + 441 + list($head_deleted, $head_offset, $head_line) = $head_info; 442 + list($tail_deleted, $tail_offset, $tail_line) = $tail_info; 443 + 444 + if ($head_offset !== false) { 445 + $inline->setLineNumber($head_line + 1 + $head_offset); 446 + } else { 447 + $inline->setLineNumber($head_line); 448 + $inline->setLineLength($tail_line - $head_line); 449 + } 348 450 } 349 451 } 350 452
+1
src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
··· 278 278 279 279 $scaffold->addInlineView($companion); 280 280 unset($new_comments[$n_num][$key]); 281 + break; 281 282 } 282 283 } 283 284 }
+1 -1
src/applications/differential/storage/DifferentialHunk.php
··· 117 117 return $this->splitLines; 118 118 } 119 119 120 - private function getStructuredLines() { 120 + public function getStructuredLines() { 121 121 if ($this->structuredLines === null) { 122 122 $lines = $this->getSplitLines(); 123 123
+294
src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php
··· 1 + <?php 2 + 3 + final class DifferentialAdjustmentMapTestCase extends ArcanistPhutilTestCase { 4 + 5 + public function testBasicMaps() { 6 + $change_map = array( 7 + 1 => array(1), 8 + 2 => array(2), 9 + 3 => array(3), 10 + 4 => array(), 11 + 5 => array(), 12 + 6 => array(), 13 + 7 => array(4), 14 + 8 => array(5), 15 + 9 => array(6), 16 + 10 => array(7), 17 + 11 => array(8), 18 + 12 => array(9), 19 + 13 => array(10), 20 + 14 => array(11), 21 + 15 => array(12), 22 + 16 => array(13), 23 + 17 => array(14), 24 + 18 => array(15), 25 + 19 => array(16), 26 + 20 => array(17, 20), 27 + 21 => array(21), 28 + 22 => array(22), 29 + 23 => array(23), 30 + 24 => array(24), 31 + 25 => array(25), 32 + 26 => array(26), 33 + ); 34 + 35 + $hunks = $this->loadHunks('add.diff'); 36 + $this->assertEqual( 37 + array( 38 + 0 => array(1, 26), 39 + ), 40 + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); 41 + 42 + $hunks = $this->loadHunks('change.diff'); 43 + $this->assertEqual( 44 + $change_map, 45 + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); 46 + 47 + $hunks = $this->loadHunks('remove.diff'); 48 + $this->assertEqual( 49 + array_fill_keys(range(1, 26), array()), 50 + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); 51 + 52 + // With the contextless diff, we don't get the last few similar lines 53 + // in the map. 54 + $reduced_map = $change_map; 55 + unset($reduced_map[24]); 56 + unset($reduced_map[25]); 57 + unset($reduced_map[26]); 58 + 59 + $hunks = $this->loadHunks('context.diff'); 60 + $this->assertEqual( 61 + $reduced_map, 62 + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); 63 + } 64 + 65 + 66 + public function testInverseMaps() { 67 + $change_map = array( 68 + 1 => array(1), 69 + 2 => array(2), 70 + 3 => array(3, 6), 71 + 4 => array(7), 72 + 5 => array(8), 73 + 6 => array(9), 74 + 7 => array(10), 75 + 8 => array(11), 76 + 9 => array(12), 77 + 10 => array(13), 78 + 11 => array(14), 79 + 12 => array(15), 80 + 13 => array(16), 81 + 14 => array(17), 82 + 15 => array(18), 83 + 16 => array(19), 84 + 17 => array(20), 85 + 18 => array(), 86 + 19 => array(), 87 + 20 => array(), 88 + 21 => array(21), 89 + 22 => array(22), 90 + 23 => array(23), 91 + 24 => array(24), 92 + 25 => array(25), 93 + 26 => array(26), 94 + ); 95 + 96 + $hunks = $this->loadHunks('add.diff'); 97 + $this->assertEqual( 98 + array_fill_keys(range(1, 26), array()), 99 + DifferentialLineAdjustmentMap::newInverseMap( 100 + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); 101 + 102 + $hunks = $this->loadHunks('change.diff'); 103 + $this->assertEqual( 104 + $change_map, 105 + DifferentialLineAdjustmentMap::newInverseMap( 106 + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); 107 + 108 + $hunks = $this->loadHunks('remove.diff'); 109 + $this->assertEqual( 110 + array( 111 + 0 => array(1, 26), 112 + ), 113 + DifferentialLineAdjustmentMap::newInverseMap( 114 + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); 115 + 116 + // With the contextless diff, we don't get the last few similar lines 117 + // in the map. 118 + $reduced_map = $change_map; 119 + unset($reduced_map[24]); 120 + unset($reduced_map[25]); 121 + unset($reduced_map[26]); 122 + 123 + $hunks = $this->loadHunks('context.diff'); 124 + $this->assertEqual( 125 + $reduced_map, 126 + DifferentialLineAdjustmentMap::newInverseMap( 127 + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); 128 + } 129 + 130 + 131 + public function testNearestMaps() { 132 + $change_map = array( 133 + 1 => array(1), 134 + 2 => array(2), 135 + 3 => array(3), 136 + 4 => array(-3, -4), 137 + 5 => array(-3, -4), 138 + 6 => array(-3, -4), 139 + 7 => array(4), 140 + 8 => array(5), 141 + 9 => array(6), 142 + 10 => array(7), 143 + 11 => array(8), 144 + 12 => array(9), 145 + 13 => array(10), 146 + 14 => array(11), 147 + 15 => array(12), 148 + 16 => array(13), 149 + 17 => array(14), 150 + 18 => array(15), 151 + 19 => array(16), 152 + 20 => array(17, 20), 153 + 21 => array(21), 154 + 22 => array(22), 155 + 23 => array(23), 156 + 24 => array(24), 157 + 25 => array(25), 158 + 26 => array(26), 159 + ); 160 + 161 + $hunks = $this->loadHunks('add.diff'); 162 + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); 163 + $this->assertEqual( 164 + array( 165 + 0 => array(1, 26), 166 + ), 167 + $map->getNearestMap()); 168 + $this->assertEqual(26, $map->getFinalOffset()); 169 + 170 + 171 + $hunks = $this->loadHunks('change.diff'); 172 + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); 173 + $this->assertEqual( 174 + $change_map, 175 + $map->getNearestMap()); 176 + $this->assertEqual(0, $map->getFinalOffset()); 177 + 178 + 179 + $hunks = $this->loadHunks('remove.diff'); 180 + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); 181 + $this->assertEqual( 182 + array_fill_keys( 183 + range(1, 26), 184 + array(0, 0)), 185 + $map->getNearestMap()); 186 + $this->assertEqual(-26, $map->getFinalOffset()); 187 + 188 + 189 + $reduced_map = $change_map; 190 + unset($reduced_map[24]); 191 + unset($reduced_map[25]); 192 + unset($reduced_map[26]); 193 + 194 + $hunks = $this->loadHunks('context.diff'); 195 + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); 196 + $this->assertEqual( 197 + $reduced_map, 198 + $map->getNearestMap()); 199 + $this->assertEqual(0, $map->getFinalOffset()); 200 + 201 + 202 + $hunks = $this->loadHunks('insert.diff'); 203 + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); 204 + $this->assertEqual( 205 + array( 206 + 1 => array(1), 207 + 2 => array(2), 208 + 3 => array(3), 209 + 4 => array(4), 210 + 5 => array(5), 211 + 6 => array(6), 212 + 7 => array(7), 213 + 8 => array(8), 214 + 9 => array(9), 215 + 10 => array(10, 13), 216 + 11 => array(14), 217 + 12 => array(15), 218 + 13 => array(16), 219 + ), 220 + $map->getNearestMap()); 221 + $this->assertEqual(3, $map->getFinalOffset()); 222 + } 223 + 224 + 225 + public function testChainMaps() { 226 + // This test simulates porting inlines forward across a rebase. 227 + // Part 1 is the original diff. 228 + // Part 2 is the rebase, which we would normally compute synthetically. 229 + // Part 3 is the updated diff against the rebased changes. 230 + 231 + $diff1 = $this->loadHunks('chain.adjust.1.diff'); 232 + $diff2 = $this->loadHunks('chain.adjust.2.diff'); 233 + $diff3 = $this->loadHunks('chain.adjust.3.diff'); 234 + 235 + $map = DifferentialLineAdjustmentMap::newInverseMap( 236 + DifferentialLineAdjustmentMap::newFromHunks($diff1)); 237 + 238 + $map->addMapToChain( 239 + DifferentialLineAdjustmentMap::newFromHunks($diff2)); 240 + 241 + $map->addMapToChain( 242 + DifferentialLineAdjustmentMap::newFromHunks($diff3)); 243 + 244 + $actual = array(); 245 + for ($ii = 1; $ii <= 13; $ii++) { 246 + $actual[$ii] = array( 247 + $map->mapLine($ii, false), 248 + $map->mapLine($ii, true), 249 + ); 250 + } 251 + 252 + $this->assertEqual( 253 + array( 254 + 1 => array(array(false, false, 1), array(false, false, 1)), 255 + 2 => array(array(true, false, 1), array(true, false, 2)), 256 + 3 => array(array(true, false, 1), array(true, false, 2)), 257 + 4 => array(array(false, false, 2), array(false, false, 2)), 258 + 5 => array(array(false, false, 3), array(false, false, 3)), 259 + 6 => array(array(false, false, 4), array(false, false, 4)), 260 + 7 => array(array(false, false, 5), array(false, false, 8)), 261 + 8 => array(array(false, 0, 5), array(false, false, 9)), 262 + 9 => array(array(false, 1, 5), array(false, false, 9)), 263 + 10 => array(array(false, 2, 5), array(false, false, 9)), 264 + 11 => array(array(false, false, 9), array(false, false, 9)), 265 + 12 => array(array(false, false, 10), array(false, false, 10)), 266 + 13 => array(array(false, false, 11), array(false, false, 11)), 267 + ), 268 + $actual); 269 + } 270 + 271 + 272 + private function loadHunks($name) { 273 + $root = dirname(__FILE__).'/map/'; 274 + $data = Filesystem::readFile($root.$name); 275 + 276 + $parser = new ArcanistDiffParser(); 277 + $changes = $parser->parseDiff($data); 278 + 279 + $viewer = PhabricatorUser::getOmnipotentUser(); 280 + $diff = DifferentialDiff::newFromRawChanges($viewer, $changes); 281 + 282 + $changesets = $diff->getChangesets(); 283 + if (count($changesets) !== 1) { 284 + throw new Exception( 285 + pht( 286 + 'Expected exactly one changeset from "%s".', 287 + $name)); 288 + } 289 + $changeset = head($changesets); 290 + 291 + return $changeset->getHunks(); 292 + } 293 + 294 + }
+32
src/applications/differential/storage/__tests__/map/add.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + new file mode 100644 3 + index 0000000..0edb856 4 + --- /dev/null 5 + +++ b/alphabet 6 + @@ -0,0 +1,26 @@ 7 + +a 8 + +b 9 + +c 10 + +d 11 + +e 12 + +f 13 + +g 14 + +h 15 + +i 16 + +j 17 + +k 18 + +l 19 + +m 20 + +n 21 + +o 22 + +p 23 + +q 24 + +r 25 + +s 26 + +t 27 + +u 28 + +v 29 + +w 30 + +x 31 + +y 32 + +z
+14
src/applications/differential/storage/__tests__/map/chain.adjust.1.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + index 92dfa21..292798b 100644 3 + --- a/alphabet 4 + +++ b/alphabet 5 + @@ -5,6 +5,9 @@ d 6 + e 7 + f 8 + g 9 + +G1 10 + +G2 11 + +G3 12 + h 13 + i 14 + j
+11
src/applications/differential/storage/__tests__/map/chain.adjust.2.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + index 92dfa21..e3344af 100644 3 + --- a/alphabet 4 + +++ b/alphabet 5 + @@ -1,6 +1,4 @@ 6 + a 7 + -b 8 + -c 9 + d 10 + e 11 + f
+14
src/applications/differential/storage/__tests__/map/chain.adjust.3.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + index e3344af..febfe3e 100644 3 + --- a/alphabet 4 + +++ b/alphabet 5 + @@ -3,6 +3,9 @@ d 6 + e 7 + f 8 + g 9 + +G1x 10 + +G2x 11 + +G3x 12 + h 13 + i 14 + j
+34
src/applications/differential/storage/__tests__/map/change.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + index 0edb856..2449de2 100644 3 + --- a/alphabet 4 + +++ b/alphabet 5 + @@ -1,26 +1,26 @@ 6 + a 7 + b 8 + c 9 + -d 10 + -e 11 + -f 12 + g 13 + h 14 + i 15 + j 16 + k 17 + l 18 + m 19 + n 20 + o 21 + p 22 + q 23 + r 24 + s 25 + t 26 + +tx 27 + +ty 28 + +tz 29 + u 30 + v 31 + w 32 + x 33 + y 34 + z
+24
src/applications/differential/storage/__tests__/map/context.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + index 0edb856..2449de2 100644 3 + --- a/alphabet 4 + +++ b/alphabet 5 + @@ -1,9 +1,6 @@ 6 + a 7 + b 8 + c 9 + -d 10 + -e 11 + -f 12 + g 13 + h 14 + i 15 + @@ -18,6 +15,9 @@ q 16 + r 17 + s 18 + t 19 + +tx 20 + +ty 21 + +tz 22 + u 23 + v 24 + w
+14
src/applications/differential/storage/__tests__/map/insert.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + index f2b41ef..755b349 100644 3 + --- a/alphabet 4 + +++ b/alphabet 5 + @@ -8,6 +8,9 @@ g 6 + h 7 + i 8 + j 9 + +j1 10 + +j2 11 + +j3 12 + k 13 + l 14 + n
+32
src/applications/differential/storage/__tests__/map/remove.diff
··· 1 + diff --git a/alphabet b/alphabet 2 + deleted file mode 100644 3 + index 2449de2..0000000 4 + --- a/alphabet 5 + +++ /dev/null 6 + @@ -1,26 +0,0 @@ 7 + -a 8 + -b 9 + -c 10 + -g 11 + -h 12 + -i 13 + -j 14 + -k 15 + -l 16 + -m 17 + -n 18 + -o 19 + -p 20 + -q 21 + -r 22 + -s 23 + -t 24 + -tx 25 + -ty 26 + -tz 27 + -u 28 + -v 29 + -w 30 + -x 31 + -y 32 + -z