Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15398492
D12741.id30666.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
28 KB
Referenced Files
None
Subscribers
None
D12741.id30666.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Tue, Mar 18, 12:21 AM (1 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7709117
Default Alt Text
D12741.id30666.diff (28 KB)
Attached To
Mode
D12741: Implement rough content-aware inline adjustment rules for ghosts
Attached
Detach File
Event Timeline
Log In to Comment