Page MenuHomePhabricator

D12741.diff
No OneTemporary

D12741.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -293,6 +293,7 @@
'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php',
'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php',
+ 'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
'DifferentialApplyPatchField' => 'applications/differential/customfield/DifferentialApplyPatchField.php',
'DifferentialArcanistProjectField' => 'applications/differential/customfield/DifferentialArcanistProjectField.php',
@@ -391,6 +392,7 @@
'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php',
'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php',
'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php',
+ 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php',
'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php',
'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
@@ -3530,6 +3532,7 @@
'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
'DifferentialActionMenuEventListener' => 'PhabricatorEventListener',
'DifferentialAddCommentView' => 'AphrontView',
+ 'DifferentialAdjustmentMapTestCase' => 'ArcanistPhutilTestCase',
'DifferentialAffectedPath' => 'DifferentialDAO',
'DifferentialApplyPatchField' => 'DifferentialCustomField',
'DifferentialArcanistProjectField' => 'DifferentialCustomField',
@@ -3632,6 +3635,7 @@
'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener',
'DifferentialLegacyHunk' => 'DifferentialHunk',
+ 'DifferentialLineAdjustmentMap' => 'Phobject',
'DifferentialLintField' => 'DifferentialCustomField',
'DifferentialLocalCommitsView' => 'AphrontView',
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
diff --git a/src/applications/differential/parser/DifferentialLineAdjustmentMap.php b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php
@@ -0,0 +1,376 @@
+<?php
+
+/**
+ * Datastructure which follows lines of code across source changes.
+ *
+ * This map is used to update the positions of inline comments after diff
+ * updates. For example, if a inline comment appeared on line 30 of a diff
+ * but the next update adds 15 more lines above it, the comment should move
+ * down to line 45.
+ *
+ */
+final class DifferentialLineAdjustmentMap extends Phobject {
+
+ private $map;
+ private $nearestMap;
+ private $isInverse;
+ private $finalOffset;
+ private $nextMapInChain;
+
+ /**
+ * Get the raw adjustment map.
+ */
+ public function getMap() {
+ return $this->map;
+ }
+
+ public function getNearestMap() {
+ if ($this->nearestMap === null) {
+ $this->buildNearestMap();
+ }
+
+ return $this->nearestMap;
+ }
+
+ public function getFinalOffset() {
+ // Make sure we've built this map already.
+ $this->getNearestMap();
+ return $this->finalOffset;
+ }
+
+
+ /**
+ * Add a map to the end of the chain.
+ *
+ * When a line is mapped with @{method:mapLine}, it is mapped through all
+ * maps in the chain.
+ */
+ public function addMapToChain(DifferentialLineAdjustmentMap $map) {
+ if ($this->nextMapInChain) {
+ $this->nextMapInChain->addMapToChain($map);
+ } else {
+ $this->nextMapInChain = $map;
+ }
+ return $this;
+ }
+
+
+ /**
+ * Map a line across a change, or a series of changes.
+ *
+ * @param int Line to map
+ * @param bool True to map it as the end of a range.
+ * @return wild Spooky magic.
+ */
+ public function mapLine($line, $is_end) {
+ $nmap = $this->getNearestMap();
+
+ $deleted = false;
+ $offset = false;
+ if (isset($nmap[$line])) {
+ $line_range = $nmap[$line];
+ if ($is_end) {
+ $to_line = end($line_range);
+ } else {
+ $to_line = reset($line_range);
+ }
+ if ($to_line <= 0) {
+ // If we're tracing the first line and this block is collapsing,
+ // compute the offset from the top of the block.
+ if (!$is_end && $this->isInverse) {
+ $offset = 0;
+ $cursor = $line - 1;
+ while (isset($nmap[$cursor])) {
+ $prev = $nmap[$cursor];
+ $prev = reset($prev);
+ if ($prev == $to_line) {
+ $offset++;
+ } else {
+ break;
+ }
+ $cursor--;
+ }
+ }
+
+ $to_line = -$to_line;
+ if (!$this->isInverse) {
+ $deleted = true;
+ }
+ }
+ $line = $to_line;
+ } else {
+ $line = $line + $this->finalOffset;
+ }
+
+ if ($this->nextMapInChain) {
+ $chain = $this->nextMapInChain->mapLine($line, $is_end);
+ list($chain_deleted, $chain_offset, $line) = $chain;
+ $deleted = ($deleted || $chain_deleted);
+ if ($chain_offset !== false) {
+ if ($offset === false) {
+ $offset = 0;
+ }
+ $offset += $chain_offset;
+ }
+ }
+
+ return array($deleted, $offset, $line);
+ }
+
+
+ /**
+ * Build a derived map which maps deleted lines to the nearest valid line.
+ *
+ * This computes a "nearest line" map and a final-line offset. These
+ * derived maps allow us to map deleted code to the previous (or next) line
+ * which actually exists.
+ */
+ private function buildNearestMap() {
+ $map = $this->map;
+ $nmap = array();
+
+ $nearest = 0;
+ foreach ($map as $key => $value) {
+ if ($value) {
+ $nmap[$key] = $value;
+ $nearest = end($value);
+ } else {
+ $nmap[$key][0] = -$nearest;
+ }
+ }
+
+ if (isset($key)) {
+ $this->finalOffset = ($nearest - $key);
+ } else {
+ $this->finalOffset = 0;
+ }
+
+ foreach (array_reverse($map, true) as $key => $value) {
+ if ($value) {
+ $nearest = reset($value);
+ } else {
+ $nmap[$key][1] = -$nearest;
+ }
+ }
+
+ $this->nearestMap = $nmap;
+
+ return $this;
+ }
+
+ public static function newFromHunks(array $hunks) {
+ assert_instances_of($hunks, 'DifferentialHunk');
+
+ $map = array();
+ $o = 0;
+ $n = 0;
+
+ $hunks = msort($hunks, 'getOldOffset');
+ foreach ($hunks as $hunk) {
+
+ // If the hunks are disjoint, add the implied missing lines where
+ // nothing changed.
+ $min = ($hunk->getOldOffset() - 1);
+ while ($o < $min) {
+ $o++;
+ $n++;
+ $map[$o][] = $n;
+ }
+
+ $lines = $hunk->getStructuredLines();
+ foreach ($lines as $line) {
+ switch ($line['type']) {
+ case '-':
+ $o++;
+ $map[$o] = array();
+ break;
+ case '+':
+ $n++;
+ $map[$o][] = $n;
+ break;
+ case ' ':
+ $o++;
+ $n++;
+ $map[$o][] = $n;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ $map = self::reduceMapRanges($map);
+
+ return self::newFromMap($map);
+ }
+
+ public static function newFromMap(array $map) {
+ $obj = new DifferentialLineAdjustmentMap();
+ $obj->map = $map;
+ return $obj;
+ }
+
+ public static function newInverseMap(DifferentialLineAdjustmentMap $map) {
+ $old = $map->getMap();
+ $inv = array();
+ $last = 0;
+ foreach ($old as $k => $v) {
+ if (count($v) > 1) {
+ $v = range(reset($v), end($v));
+ }
+ if ($k == 0) {
+ foreach ($v as $line) {
+ $inv[$line] = array();
+ $last = $line;
+ }
+ } else if ($v) {
+ $first = true;
+ foreach ($v as $line) {
+ if ($first) {
+ $first = false;
+ $inv[$line][] = $k;
+ $last = $line;
+ } else {
+ $inv[$line] = array();
+ }
+ }
+ } else {
+ $inv[$last][] = $k;
+ }
+ }
+
+ $inv = self::reduceMapRanges($inv);
+
+ $obj = new DifferentialLineAdjustmentMap();
+ $obj->map = $inv;
+ $obj->isInverse = !$map->isInverse;
+ return $obj;
+ }
+
+ private static function reduceMapRanges(array $map) {
+ foreach ($map as $key => $values) {
+ if (count($values) > 2) {
+ $map[$key] = array(reset($values), end($values));
+ }
+ }
+ return $map;
+ }
+
+
+ public static function loadMaps(array $maps) {
+ $keys = array();
+ foreach ($maps as $map) {
+ list($u, $v) = $map;
+ $keys[self::getCacheKey($u, $v)] = $map;
+ }
+
+ $cache = new PhabricatorKeyValueDatabaseCache();
+ $cache = new PhutilKeyValueCacheProfiler($cache);
+ $cache->setProfiler(PhutilServiceProfiler::getInstance());
+
+ $results = array();
+
+ if ($keys) {
+ $caches = $cache->getKeys(array_keys($keys));
+ foreach ($caches as $key => $value) {
+ list($u, $v) = $keys[$key];
+ try {
+ $results[$u][$v] = self::newFromMap(
+ phutil_json_decode($value));
+ } catch (Exception $ex) {
+ // Ignore, rebuild below.
+ }
+ unset($keys[$key]);
+ }
+ }
+
+ if ($keys) {
+ $built = self::buildMaps($maps);
+
+ $write = array();
+ foreach ($built as $u => $list) {
+ foreach ($list as $v => $map) {
+ $write[self::getCacheKey($u, $v)] = json_encode($map->getMap());
+ $results[$u][$v] = $map;
+ }
+ }
+
+ $cache->setKeys($write);
+ }
+
+ return $results;
+ }
+
+ private static function buildMaps(array $maps) {
+ $need = array();
+ foreach ($maps as $map) {
+ list($u, $v) = $map;
+ $need[$u] = $u;
+ $need[$v] = $v;
+ }
+
+ if ($need) {
+ $changesets = id(new DifferentialChangesetQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withIDs($need)
+ ->needHunks(true)
+ ->execute();
+ $changesets = mpull($changesets, null, 'getID');
+ }
+
+ $results = array();
+ foreach ($maps as $map) {
+ list($u, $v) = $map;
+ $u_set = idx($changesets, $u);
+ $v_set = idx($changesets, $v);
+
+ if (!$u_set || !$v_set) {
+ continue;
+ }
+
+ // This is the simple case.
+ if ($u == $v) {
+ $results[$u][$v] = self::newFromHunks(
+ $u_set->getHunks());
+ continue;
+ }
+
+ $u_old = $u_set->makeOldFile();
+ $v_old = $v_set->makeOldFile();
+
+ // No difference between the two left sides.
+ if ($u_old == $v_old) {
+ $results[$u][$v] = self::newFromMap(
+ array());
+ continue;
+ }
+
+ // If we're missing context, this won't currently work. We can
+ // make this case work, but it's fairly rare.
+ $u_hunks = $u_set->getHunks();
+ $v_hunks = $v_set->getHunks();
+ if (count($u_hunks) != 1 ||
+ count($v_hunks) != 1 ||
+ head($u_hunks)->getOldOffset() != 1 ||
+ head($u_hunks)->getNewOffset() != 1 ||
+ head($v_hunks)->getOldOffset() != 1 ||
+ head($v_hunks)->getNewOffset() != 1) {
+ continue;
+ }
+
+ $changeset = id(new PhabricatorDifferenceEngine())
+ ->setIgnoreWhitespace(true)
+ ->generateChangesetFromFileContent($u_old, $v_old);
+
+ $results[$u][$v] = self::newFromHunks(
+ $changeset->getHunks());
+ }
+
+ return $results;
+ }
+
+ private static function getCacheKey($u, $v) {
+ return 'diffadjust.v1('.$u.','.$v.')';
+ }
+
+}
diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php
--- a/src/applications/differential/query/DifferentialInlineCommentQuery.php
+++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php
@@ -323,6 +323,7 @@
'new' => $is_new,
'reason' => $reason,
'href' => $href,
+ 'originalID' => $changeset->getID(),
));
$results[] = $inline;
@@ -348,6 +349,107 @@
}
}
+ // Adjust inline line numbers to account for content changes across
+ // updates and rebases.
+ $plan = array();
+ $need = array();
+ foreach ($results as $inline) {
+ $ghost = $inline->getIsGhost();
+ if (!$ghost) {
+ // If this isn't a "ghost" inline, ignore it.
+ continue;
+ }
+
+ $src_id = $ghost['originalID'];
+ $dst_id = $inline->getChangesetID();
+
+ $xforms = array();
+
+ // If the comment is on the right, transform it through the inverse map
+ // back to the left.
+ if ($inline->getIsNewFile()) {
+ $xforms[] = array($src_id, $src_id, true);
+ }
+
+ // Transform it across rebases.
+ $xforms[] = array($src_id, $dst_id, false);
+
+ // If the comment is on the right, transform it back onto the right.
+ if ($inline->getIsNewFile()) {
+ $xforms[] = array($dst_id, $dst_id, false);
+ }
+
+ $key = array();
+ foreach ($xforms as $xform) {
+ list($u, $v, $inverse) = $xform;
+
+ $short = $u.'/'.$v;
+ $need[$short] = array($u, $v);
+
+ $part = $u.($inverse ? '<' : '>').$v;
+ $key[] = $part;
+ }
+ $key = implode(',', $key);
+
+ if (empty($plan[$key])) {
+ $plan[$key] = array(
+ 'xforms' => $xforms,
+ 'inlines' => array(),
+ );
+ }
+
+ $plan[$key]['inlines'][] = $inline;
+ }
+
+ if ($need) {
+ $maps = DifferentialLineAdjustmentMap::loadMaps($need);
+ } else {
+ $maps = array();
+ }
+
+ foreach ($plan as $step) {
+ $xforms = $step['xforms'];
+
+ $chain = null;
+ foreach ($xforms as $xform) {
+ list($u, $v, $inverse) = $xform;
+ $map = idx(idx($maps, $u, array()), $v);
+ if (!$map) {
+ continue 2;
+ }
+
+ if ($inverse) {
+ $map = DifferentialLineAdjustmentMap::newInverseMap($map);
+ } else {
+ $map = clone $map;
+ }
+
+ if ($chain) {
+ $chain->addMapToChain($map);
+ } else {
+ $chain = $map;
+ }
+ }
+
+ foreach ($step['inlines'] as $inline) {
+ $head_line = $inline->getLineNumber();
+ $tail_line = ($head_line + $inline->getLineLength());
+
+ $head_info = $chain->mapLine($head_line, false);
+ $tail_info = $chain->mapLine($tail_line, true);
+
+ list($head_deleted, $head_offset, $head_line) = $head_info;
+ list($tail_deleted, $tail_offset, $tail_line) = $tail_info;
+
+ if ($head_offset !== false) {
+ $inline->setLineNumber($head_line + 1 + $head_offset);
+ } else {
+ $inline->setLineNumber($head_line);
+ $inline->setLineLength($tail_line - $head_line);
+ }
+ }
+ }
+
return $results;
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -278,6 +278,7 @@
$scaffold->addInlineView($companion);
unset($new_comments[$n_num][$key]);
+ break;
}
}
}
diff --git a/src/applications/differential/storage/DifferentialHunk.php b/src/applications/differential/storage/DifferentialHunk.php
--- a/src/applications/differential/storage/DifferentialHunk.php
+++ b/src/applications/differential/storage/DifferentialHunk.php
@@ -117,7 +117,7 @@
return $this->splitLines;
}
- private function getStructuredLines() {
+ public function getStructuredLines() {
if ($this->structuredLines === null) {
$lines = $this->getSplitLines();
diff --git a/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php b/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php
@@ -0,0 +1,294 @@
+<?php
+
+final class DifferentialAdjustmentMapTestCase extends ArcanistPhutilTestCase {
+
+ public function testBasicMaps() {
+ $change_map = array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3),
+ 4 => array(),
+ 5 => array(),
+ 6 => array(),
+ 7 => array(4),
+ 8 => array(5),
+ 9 => array(6),
+ 10 => array(7),
+ 11 => array(8),
+ 12 => array(9),
+ 13 => array(10),
+ 14 => array(11),
+ 15 => array(12),
+ 16 => array(13),
+ 17 => array(14),
+ 18 => array(15),
+ 19 => array(16),
+ 20 => array(17, 20),
+ 21 => array(21),
+ 22 => array(22),
+ 23 => array(23),
+ 24 => array(24),
+ 25 => array(25),
+ 26 => array(26),
+ );
+
+ $hunks = $this->loadHunks('add.diff');
+ $this->assertEqual(
+ array(
+ 0 => array(1, 26),
+ ),
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+
+ $hunks = $this->loadHunks('change.diff');
+ $this->assertEqual(
+ $change_map,
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+
+ $hunks = $this->loadHunks('remove.diff');
+ $this->assertEqual(
+ array_fill_keys(range(1, 26), array()),
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+
+ // With the contextless diff, we don't get the last few similar lines
+ // in the map.
+ $reduced_map = $change_map;
+ unset($reduced_map[24]);
+ unset($reduced_map[25]);
+ unset($reduced_map[26]);
+
+ $hunks = $this->loadHunks('context.diff');
+ $this->assertEqual(
+ $reduced_map,
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+ }
+
+
+ public function testInverseMaps() {
+ $change_map = array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3, 6),
+ 4 => array(7),
+ 5 => array(8),
+ 6 => array(9),
+ 7 => array(10),
+ 8 => array(11),
+ 9 => array(12),
+ 10 => array(13),
+ 11 => array(14),
+ 12 => array(15),
+ 13 => array(16),
+ 14 => array(17),
+ 15 => array(18),
+ 16 => array(19),
+ 17 => array(20),
+ 18 => array(),
+ 19 => array(),
+ 20 => array(),
+ 21 => array(21),
+ 22 => array(22),
+ 23 => array(23),
+ 24 => array(24),
+ 25 => array(25),
+ 26 => array(26),
+ );
+
+ $hunks = $this->loadHunks('add.diff');
+ $this->assertEqual(
+ array_fill_keys(range(1, 26), array()),
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+
+ $hunks = $this->loadHunks('change.diff');
+ $this->assertEqual(
+ $change_map,
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+
+ $hunks = $this->loadHunks('remove.diff');
+ $this->assertEqual(
+ array(
+ 0 => array(1, 26),
+ ),
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+
+ // With the contextless diff, we don't get the last few similar lines
+ // in the map.
+ $reduced_map = $change_map;
+ unset($reduced_map[24]);
+ unset($reduced_map[25]);
+ unset($reduced_map[26]);
+
+ $hunks = $this->loadHunks('context.diff');
+ $this->assertEqual(
+ $reduced_map,
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+ }
+
+
+ public function testNearestMaps() {
+ $change_map = array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3),
+ 4 => array(-3, -4),
+ 5 => array(-3, -4),
+ 6 => array(-3, -4),
+ 7 => array(4),
+ 8 => array(5),
+ 9 => array(6),
+ 10 => array(7),
+ 11 => array(8),
+ 12 => array(9),
+ 13 => array(10),
+ 14 => array(11),
+ 15 => array(12),
+ 16 => array(13),
+ 17 => array(14),
+ 18 => array(15),
+ 19 => array(16),
+ 20 => array(17, 20),
+ 21 => array(21),
+ 22 => array(22),
+ 23 => array(23),
+ 24 => array(24),
+ 25 => array(25),
+ 26 => array(26),
+ );
+
+ $hunks = $this->loadHunks('add.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ array(
+ 0 => array(1, 26),
+ ),
+ $map->getNearestMap());
+ $this->assertEqual(26, $map->getFinalOffset());
+
+
+ $hunks = $this->loadHunks('change.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ $change_map,
+ $map->getNearestMap());
+ $this->assertEqual(0, $map->getFinalOffset());
+
+
+ $hunks = $this->loadHunks('remove.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ array_fill_keys(
+ range(1, 26),
+ array(0, 0)),
+ $map->getNearestMap());
+ $this->assertEqual(-26, $map->getFinalOffset());
+
+
+ $reduced_map = $change_map;
+ unset($reduced_map[24]);
+ unset($reduced_map[25]);
+ unset($reduced_map[26]);
+
+ $hunks = $this->loadHunks('context.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ $reduced_map,
+ $map->getNearestMap());
+ $this->assertEqual(0, $map->getFinalOffset());
+
+
+ $hunks = $this->loadHunks('insert.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3),
+ 4 => array(4),
+ 5 => array(5),
+ 6 => array(6),
+ 7 => array(7),
+ 8 => array(8),
+ 9 => array(9),
+ 10 => array(10, 13),
+ 11 => array(14),
+ 12 => array(15),
+ 13 => array(16),
+ ),
+ $map->getNearestMap());
+ $this->assertEqual(3, $map->getFinalOffset());
+ }
+
+
+ public function testChainMaps() {
+ // This test simulates porting inlines forward across a rebase.
+ // Part 1 is the original diff.
+ // Part 2 is the rebase, which we would normally compute synthetically.
+ // Part 3 is the updated diff against the rebased changes.
+
+ $diff1 = $this->loadHunks('chain.adjust.1.diff');
+ $diff2 = $this->loadHunks('chain.adjust.2.diff');
+ $diff3 = $this->loadHunks('chain.adjust.3.diff');
+
+ $map = DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($diff1));
+
+ $map->addMapToChain(
+ DifferentialLineAdjustmentMap::newFromHunks($diff2));
+
+ $map->addMapToChain(
+ DifferentialLineAdjustmentMap::newFromHunks($diff3));
+
+ $actual = array();
+ for ($ii = 1; $ii <= 13; $ii++) {
+ $actual[$ii] = array(
+ $map->mapLine($ii, false),
+ $map->mapLine($ii, true),
+ );
+ }
+
+ $this->assertEqual(
+ array(
+ 1 => array(array(false, false, 1), array(false, false, 1)),
+ 2 => array(array(true, false, 1), array(true, false, 2)),
+ 3 => array(array(true, false, 1), array(true, false, 2)),
+ 4 => array(array(false, false, 2), array(false, false, 2)),
+ 5 => array(array(false, false, 3), array(false, false, 3)),
+ 6 => array(array(false, false, 4), array(false, false, 4)),
+ 7 => array(array(false, false, 5), array(false, false, 8)),
+ 8 => array(array(false, 0, 5), array(false, false, 9)),
+ 9 => array(array(false, 1, 5), array(false, false, 9)),
+ 10 => array(array(false, 2, 5), array(false, false, 9)),
+ 11 => array(array(false, false, 9), array(false, false, 9)),
+ 12 => array(array(false, false, 10), array(false, false, 10)),
+ 13 => array(array(false, false, 11), array(false, false, 11)),
+ ),
+ $actual);
+ }
+
+
+ private function loadHunks($name) {
+ $root = dirname(__FILE__).'/map/';
+ $data = Filesystem::readFile($root.$name);
+
+ $parser = new ArcanistDiffParser();
+ $changes = $parser->parseDiff($data);
+
+ $viewer = PhabricatorUser::getOmnipotentUser();
+ $diff = DifferentialDiff::newFromRawChanges($viewer, $changes);
+
+ $changesets = $diff->getChangesets();
+ if (count($changesets) !== 1) {
+ throw new Exception(
+ pht(
+ 'Expected exactly one changeset from "%s".',
+ $name));
+ }
+ $changeset = head($changesets);
+
+ return $changeset->getHunks();
+ }
+
+}
diff --git a/src/applications/differential/storage/__tests__/map/add.diff b/src/applications/differential/storage/__tests__/map/add.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/add.diff
@@ -0,0 +1,32 @@
+diff --git a/alphabet b/alphabet
+new file mode 100644
+index 0000000..0edb856
+--- /dev/null
++++ b/alphabet
+@@ -0,0 +1,26 @@
++a
++b
++c
++d
++e
++f
++g
++h
++i
++j
++k
++l
++m
++n
++o
++p
++q
++r
++s
++t
++u
++v
++w
++x
++y
++z
diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff
@@ -0,0 +1,14 @@
+diff --git a/alphabet b/alphabet
+index 92dfa21..292798b 100644
+--- a/alphabet
++++ b/alphabet
+@@ -5,6 +5,9 @@ d
+ e
+ f
+ g
++G1
++G2
++G3
+ h
+ i
+ j
diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff
@@ -0,0 +1,11 @@
+diff --git a/alphabet b/alphabet
+index 92dfa21..e3344af 100644
+--- a/alphabet
++++ b/alphabet
+@@ -1,6 +1,4 @@
+ a
+-b
+-c
+ d
+ e
+ f
diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff
@@ -0,0 +1,14 @@
+diff --git a/alphabet b/alphabet
+index e3344af..febfe3e 100644
+--- a/alphabet
++++ b/alphabet
+@@ -3,6 +3,9 @@ d
+ e
+ f
+ g
++G1x
++G2x
++G3x
+ h
+ i
+ j
diff --git a/src/applications/differential/storage/__tests__/map/change.diff b/src/applications/differential/storage/__tests__/map/change.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/change.diff
@@ -0,0 +1,34 @@
+diff --git a/alphabet b/alphabet
+index 0edb856..2449de2 100644
+--- a/alphabet
++++ b/alphabet
+@@ -1,26 +1,26 @@
+ a
+ b
+ c
+-d
+-e
+-f
+ g
+ h
+ i
+ j
+ k
+ l
+ m
+ n
+ o
+ p
+ q
+ r
+ s
+ t
++tx
++ty
++tz
+ u
+ v
+ w
+ x
+ y
+ z
diff --git a/src/applications/differential/storage/__tests__/map/context.diff b/src/applications/differential/storage/__tests__/map/context.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/context.diff
@@ -0,0 +1,24 @@
+diff --git a/alphabet b/alphabet
+index 0edb856..2449de2 100644
+--- a/alphabet
++++ b/alphabet
+@@ -1,9 +1,6 @@
+ a
+ b
+ c
+-d
+-e
+-f
+ g
+ h
+ i
+@@ -18,6 +15,9 @@ q
+ r
+ s
+ t
++tx
++ty
++tz
+ u
+ v
+ w
diff --git a/src/applications/differential/storage/__tests__/map/insert.diff b/src/applications/differential/storage/__tests__/map/insert.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/insert.diff
@@ -0,0 +1,14 @@
+diff --git a/alphabet b/alphabet
+index f2b41ef..755b349 100644
+--- a/alphabet
++++ b/alphabet
+@@ -8,6 +8,9 @@ g
+ h
+ i
+ j
++j1
++j2
++j3
+ k
+ l
+ n
diff --git a/src/applications/differential/storage/__tests__/map/remove.diff b/src/applications/differential/storage/__tests__/map/remove.diff
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/remove.diff
@@ -0,0 +1,32 @@
+diff --git a/alphabet b/alphabet
+deleted file mode 100644
+index 2449de2..0000000
+--- a/alphabet
++++ /dev/null
+@@ -1,26 +0,0 @@
+-a
+-b
+-c
+-g
+-h
+-i
+-j
+-k
+-l
+-m
+-n
+-o
+-p
+-q
+-r
+-s
+-t
+-tx
+-ty
+-tz
+-u
+-v
+-w
+-x
+-y
+-z

File Metadata

Mime Type
text/plain
Expires
Sat, Jun 22, 10:27 AM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6289410
Default Alt Text
D12741.diff (28 KB)

Event Timeline