Tighten up logic in MergeFrontier.compute_by_bisection().

This removes some redundant automerge attempts.

Also add lots of comments and expand the method docstring.
This commit is contained in:
Michael Haggerty 2013-05-06 15:59:16 +02:00
parent 3922ea6563
commit 9fd67a3670

View File

@ -714,45 +714,127 @@ class MergeFrontier(object):
@staticmethod
def compute_by_bisection(block):
"""Return a MergeFrontier instance for block."""
"""Return a MergeFrontier instance for block.
if block.len1 <= 1 or block.len2 <= 1:
We make the following assumptions (using Python subscript
notation):
0. All of the merges in block[1:,0] and block[0,1:] are
already known. (This is an invariant of the Block class.)
1. If a direct merge can be done between block[i1-1,0] and
block[0,i2-1], then all of the pairwise merges in
block[1:i1, 1:i2] can also be done.
2. If a direct merge fails between block[i1-1,0] and
block[0,i2-1], then all of the pairwise merges in
block[i1-1:,i2-1:] would also fail.
Under these assumptions, the merge frontier is a stepstair
pattern going from the bottom-left to the top-right, and
bisection can be used to find the transition between mergeable
and conflicting in any row or column.
Of course these assumptions are not rigorously true, so the
MergeFrontier returned by this function is only an
approximation of the real merge diagram. We check for and
correct such inconsistencies later."""
# Given that these diagrams typically have few blocks, check
# the end of a range first to see if the whole range can be
# determined, and fall back to bisection otherwise. We
# determine the frontier block by block, starting in the lower
# left.
if block.len1 <= 1 or block.len2 <= 1 or not block.is_mergeable(1, 1):
# There are no mergeable blocks in block.
return MergeFrontier(block, [])
blocks = []
# At this point, we know that there is at least one mergeable
# commit in the first column. Find the height of the success
# block in column 1:
i1 = 1
i2 = block.len2
i2 = find_first_false(
lambda i: block.is_mergeable(i1, i),
1, block.len2,
)
# Now we know that (1,i2-1) is mergeable but (1,i2) is not;
# e.g., (i1, i2) is like 'A' (or maybe 'B') in the following
# diagram (where '*' means mergeable, 'x' means not mergeable,
# and '?' means indeterminate) and that the merge under 'A' is
# not mergeable:
#
# i1
#
# 0123456
# 0 *******
# 1 **?????
# i2 2 **?????
# 3 **?????
# 4 *Axxxxx
# 5 *xxxxxx
# B
# Given that these diagrams typically have few blocks, check the
# end of a range first to see if the whole range can be filled in,
# and fall back to bisection otherwise.
while True:
# Find the width of the success rectangle at row (i2-1) and fill it in:
if block.is_mergeable(block.len1 - 1, i2 - 1):
newi1 = block.len1
else:
newi1 = find_first_false(
lambda i: block.is_mergeable(i, i2 - 1),
i1, block.len1 - 1,
)
blocks.append(block[:newi1,:i2])
i1 = newi1
if i1 == block.len1:
if i2 == 1:
break
# Find the height of the conflict rectangle at column i1 and fill it in:
if not block.is_mergeable(i1, 1):
newi2 = 1
# At this point in the loop, we know that any blocks to
# the left of 'A' have already been recorded, (i1, i2-1)
# is mergeable but (i1, i2) is not; e.g., we are at a
# place like 'A' in the following diagram:
#
# i1
#
# 0123456
# 0 **|****
# 1 **|*???
# i2 2 **|*???
# 3 **|Axxx
# 4 --+xxxx
# 5 *xxxxxx
#
# This implies that (i1, i2) is the first unmergeable
# commit in a blocker block (though blocker blocks are not
# recorded explicitly). It also implies that a mergeable
# block has its last mergeable commit somewhere in row
# i2-1; find its width.
if block.is_mergeable(block.len1 - 1, i2 - 1):
i1 = block.len1
blocks.append(block[:i1,:i2])
break
else:
newi2 = find_first_false(
lambda i: block.is_mergeable(i1, i),
2, i2,
i1 = find_first_false(
lambda i: block.is_mergeable(i, i2 - 1),
i1 + 1, block.len1 - 1,
)
i2 = newi2
blocks.append(block[:i1,:i2])
if i2 == 1:
# At this point in the loop, (i1-1, i2-1) is mergeable but
# (i1, i2-1) is not; e.g., 'A' in the following diagram:
#
# i1
#
# 0123456
# 0 **|*|**
# 1 **|*|??
# i2 2 --+-+xx
# 3 **|xxAx
# 4 --+xxxx
# 5 *xxxxxx
#
# The block ending at (i1-1,i2-1) has just been recorded.
# Now find the height of the conflict rectangle at column
# i1 and fill it in:
if block.is_mergeable(i1, 1):
i2 = find_first_false(
lambda i: block.is_mergeable(i1, i),
2, i2 - 1,
)
else:
break
return MergeFrontier(block, blocks)