def find_unconflicted(self): """Return a list of ranges in base that are not conflicted.""" am = patiencediff.PatienceSequenceMatcher( None, self.base, self.a).get_matching_blocks() bm = patiencediff.PatienceSequenceMatcher( None, self.base, self.b).get_matching_blocks() unc = [] while am and bm: # there is an unconflicted block at i; how long does it # extend? until whichever one ends earlier. a1 = am[0][0] a2 = a1 + am[0][2] b1 = bm[0][0] b2 = b1 + bm[0][2] i = intersect((a1, a2), (b1, b2)) if i: unc.append(i) if a2 < b2: del am[0] else: del bm[0] return unc
def find_sync_regions(self): """Return a list of sync regions, where both descendents match the base. Generates a list of (base1, base2, a1, a2, b1, b2). There is always a zero-length sync region at the end of all the files. """ ia = ib = 0 amatches = patiencediff.PatienceSequenceMatcher( None, self.base, self.a).get_matching_blocks() bmatches = patiencediff.PatienceSequenceMatcher( None, self.base, self.b).get_matching_blocks() len_a = len(amatches) len_b = len(bmatches) sl = [] while ia < len_a and ib < len_b: abase, amatch, alen = amatches[ia] bbase, bmatch, blen = bmatches[ib] # there is an unconflicted block at i; how long does it # extend? until whichever one ends earlier. i = intersect((abase, abase + alen), (bbase, bbase + blen)) if i: intbase = i[0] intend = i[1] intlen = intend - intbase # found a match of base[i[0], i[1]]; this may be less than # the region that matches in either one # assert intlen <= alen # assert intlen <= blen # assert abase <= intbase # assert bbase <= intbase asub = amatch + (intbase - abase) bsub = bmatch + (intbase - bbase) aend = asub + intlen bend = bsub + intlen # assert self.base[intbase:intend] == self.a[asub:aend], \ # (self.base[intbase:intend], self.a[asub:aend]) # assert self.base[intbase:intend] == self.b[bsub:bend] sl.append((intbase, intend, asub, aend, bsub, bend)) # advance whichever one ends first in the base text if (abase + alen) < (bbase + blen): ia += 1 else: ib += 1 intbase = len(self.base) abase = len(self.a) bbase = len(self.b) sl.append((intbase, intbase, abase, abase, bbase, bbase)) return sl
def reprocess_merge_regions(self, merge_regions): """Where there are conflict regions, remove the agreed lines. Lines where both A and B have made the same changes are eliminated. """ for region in merge_regions: if region[0] != "conflict": yield region continue type, iz, zmatch, ia, amatch, ib, bmatch = region a_region = self.a[ia:amatch] b_region = self.b[ib:bmatch] matches = patiencediff.PatienceSequenceMatcher( None, a_region, b_region).get_matching_blocks() next_a = ia next_b = ib for region_ia, region_ib, region_len in matches[:-1]: region_ia += ia region_ib += ib reg = self.mismatch_region(next_a, region_ia, next_b, region_ib) if reg is not None: yield reg yield 'same', region_ia, region_len + region_ia next_a = region_ia + region_len next_b = region_ib + region_len reg = self.mismatch_region(next_a, amatch, next_b, bmatch) if reg is not None: yield reg
def test_compare_two_parents_blocks(self): matcher = patiencediff.PatienceSequenceMatcher(None, LINES_2, LINES_1) blocks = matcher.get_matching_blocks() diff = multiparent.MultiParent.from_lines(LINES_1, [LINES_2, LINES_3], left_blocks=blocks) self.assertEqual([multiparent.ParentText(1, 0, 0, 4), multiparent.ParentText(0, 3, 4, 1)], diff.hunks)
def _refine_cherrypick_conflict(self, zstart, zend, astart, aend, bstart, bend): """When cherrypicking b => a, ignore matches with b and base.""" # Do not emit regions which match, only regions which do not match matches = patiencediff.PatienceSequenceMatcher( None, self.base[zstart:zend], self.b[bstart:bend]).get_matching_blocks() last_base_idx = 0 last_b_idx = 0 last_b_idx = 0 yielded_a = False for base_idx, b_idx, match_len in matches: #conflict_z_len = base_idx - last_base_idx conflict_b_len = b_idx - last_b_idx if conflict_b_len == 0: # There are no lines in b which conflict, # so skip it pass else: if yielded_a: yield ('conflict', zstart + last_base_idx, zstart + base_idx, aend, aend, bstart + last_b_idx, bstart + b_idx) else: # The first conflict gets the a-range yielded_a = True yield ('conflict', zstart + last_base_idx, zstart + base_idx, astart, aend, bstart + last_b_idx, bstart + b_idx) last_base_idx = base_idx + match_len last_b_idx = b_idx + match_len if last_base_idx != zend - zstart or last_b_idx != bend - bstart: if yielded_a: yield ('conflict', zstart + last_base_idx, zstart + base_idx, aend, aend, bstart + last_b_idx, bstart + b_idx) else: # The first conflict gets the a-range yielded_a = True yield ('conflict', zstart + last_base_idx, zstart + base_idx, astart, aend, bstart + last_b_idx, bstart + b_idx) if not yielded_a: yield ('conflict', zstart, zend, astart, aend, bstart, bend)
def guess_command(cmd_name): """Guess what command a user typoed. :param cmd_name: Command to search for :return: None if no command was found, name of a command otherwise """ names = set() for name in all_command_names(): names.add(name) cmd = get_cmd_object(name) names.update(cmd.aliases) # candidate: modified levenshtein distance against cmd_name. costs = {} import patiencediff for name in sorted(names): matcher = patiencediff.PatienceSequenceMatcher(None, cmd_name, name) distance = 0.0 opcodes = matcher.get_opcodes() for opcode, l1, l2, r1, r2 in opcodes: if opcode == 'delete': distance += l2 - l1 elif opcode == 'replace': distance += max(l2 - l1, r2 - l1) elif opcode == 'insert': distance += r2 - r1 elif opcode == 'equal': # Score equal ranges lower, making similar commands of equal # length closer than arbitrary same length commands. distance -= 0.1 * (l2 - l1) costs[name] = distance costs.update(_GUESS_OVERRIDES.get(cmd_name, {})) costs = sorted((costs[key], key) for key in costs) if not costs: return if costs[0][0] > 4: return candidate = costs[0][1] return candidate
def _count_changed_regions(old_lines, new_lines): matcher = patiencediff.PatienceSequenceMatcher(None, old_lines, new_lines) blocks = matcher.get_matching_blocks() return len(blocks) - 2
def make(self): """Make the template. If NEWS is missing or not not modified, the original template is returned unaltered. Otherwise the changes from NEWS are concatenated with whatever message was provided to __init__. """ delta = self.commit.builder.get_basis_delta() found_old_path = None found_entry = None for old_path, new_path, fileid, entry in delta: if new_path in self.filespec: found_entry = entry found_old_path = old_path break if not found_entry: return self.message if found_old_path is None: # New file _, new_chunks = next( self.commit.builder.repository.iter_files_bytes([ (found_entry.file_id, found_entry.revision, None) ])) content = b''.join(new_chunks).decode('utf-8') return self.merge_message(content) else: # Get a diff. XXX Is this hookable? I thought it was, can't find it # though.... add DiffTree.diff_factories. Sadly thats not at the # right level: we want to identify the changed lines, not have the # final diff: because we want to grab the sections for regions # changed in new version of the file. So for now a direct diff # using patiencediff is done. old_revision = self.commit.basis_tree.get_file_revision(old_path) needed = [(found_entry.file_id, found_entry.revision, 'new'), (found_entry.file_id, old_revision, 'old')] contents = self.commit.builder.repository.iter_files_bytes(needed) lines = {} for name, chunks in contents: lines[name] = osutils.chunks_to_lines(list(chunks)) new = lines['new'] sequence_matcher = patiencediff.PatienceSequenceMatcher( None, lines['old'], new) new_lines = [] for group in sequence_matcher.get_opcodes(): tag, i1, i2, j1, j2 = group if tag == 'equal': continue if tag == 'delete': continue new_lines.extend([l.decode('utf-8') for l in new[j1:j2]]) if not self.commit.revprops.get('bugs'): # TODO: Allow the user to configure the bug tracker to use # rather than hardcoding Launchpad. bt = bugtracker.tracker_registry.get('launchpad') bugids = [] for line in new_lines: bugids.extend(_BUG_MATCH.findall(line)) self.commit.revprops['bugs'] = \ bugtracker.encode_fixes_bug_urls( [(bt.get_bug_url(bugid), bugtracker.FIXED) for bugid in bugids]) return self.merge_message(''.join(new_lines))
def _matched_lines(old, new): matcher = patiencediff.PatienceSequenceMatcher(None, old, new) matched_lines = sum(n for i, j, n in matcher.get_matching_blocks()) return matched_lines