Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F104119812
DifferentialLineAdjustmentMap.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Mar 6, 14:18
Size
8 KB
Mime Type
text/x-php
Expires
Sat, Mar 8, 14:18 (1 d, 20 h)
Engine
blob
Format
Raw Data
Handle
24678933
Attached To
rPH Phabricator
DifferentialLineAdjustmentMap.php
View Options
<?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
.
')'
;
}
}
Event Timeline
Log In to Comment