From 9fd67a367045e12854d045d734adcba1a51481ab Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Mon, 6 May 2013 15:59:16 +0200 Subject: [PATCH] Tighten up logic in MergeFrontier.compute_by_bisection(). This removes some redundant automerge attempts. Also add lots of comments and expand the method docstring. --- git-imerge | 134 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 26 deletions(-) diff --git a/git-imerge b/git-imerge index 9199bf3..a5b7574 100755 --- a/git-imerge +++ b/git-imerge @@ -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)