Mercurial > public > mercurial-scm > hg-stable
diff mercurial/dagop.py @ 36923:7affcabf561e
dagop: move annotateline and _annotatepair from context.py
The annotate logic is large. Let's move it out of the context module, which
is basically an abstraction layer of repository operations.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Wed, 28 Feb 2018 15:09:05 -0500 |
parents | c9144396099b |
children | 5d3abd6a5b25 |
line wrap: on
line diff
--- a/mercurial/dagop.py Fri Mar 09 21:59:07 2018 -0500 +++ b/mercurial/dagop.py Wed Feb 28 15:09:05 2018 -0500 @@ -9,6 +9,9 @@ import heapq +from .thirdparty import ( + attr, +) from . import ( error, mdiff, @@ -358,6 +361,74 @@ if inrange: yield c, linerange1 +@attr.s(slots=True, frozen=True) +class annotateline(object): + fctx = attr.ib() + lineno = attr.ib(default=False) + # Whether this annotation was the result of a skip-annotate. + skip = attr.ib(default=False) + +def _annotatepair(parents, childfctx, child, skipchild, diffopts): + r''' + Given parent and child fctxes and annotate data for parents, for all lines + in either parent that match the child, annotate the child with the parent's + data. + + Additionally, if `skipchild` is True, replace all other lines with parent + annotate data as well such that child is never blamed for any lines. + + See test-annotate.py for unit tests. + ''' + pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts)) + for parent in parents] + + if skipchild: + # Need to iterate over the blocks twice -- make it a list + pblocks = [(p, list(blocks)) for (p, blocks) in pblocks] + # Mercurial currently prefers p2 over p1 for annotate. + # TODO: change this? + for parent, blocks in pblocks: + for (a1, a2, b1, b2), t in blocks: + # Changed blocks ('!') or blocks made only of blank lines ('~') + # belong to the child. + if t == '=': + child[0][b1:b2] = parent[0][a1:a2] + + if skipchild: + # Now try and match up anything that couldn't be matched, + # Reversing pblocks maintains bias towards p2, matching above + # behavior. + pblocks.reverse() + + # The heuristics are: + # * Work on blocks of changed lines (effectively diff hunks with -U0). + # This could potentially be smarter but works well enough. + # * For a non-matching section, do a best-effort fit. Match lines in + # diff hunks 1:1, dropping lines as necessary. + # * Repeat the last line as a last resort. + + # First, replace as much as possible without repeating the last line. + remaining = [(parent, []) for parent, _blocks in pblocks] + for idx, (parent, blocks) in enumerate(pblocks): + for (a1, a2, b1, b2), _t in blocks: + if a2 - a1 >= b2 - b1: + for bk in xrange(b1, b2): + if child[0][bk].fctx == childfctx: + ak = min(a1 + (bk - b1), a2 - 1) + child[0][bk] = attr.evolve(parent[0][ak], skip=True) + else: + remaining[idx][1].append((a1, a2, b1, b2)) + + # Then, look at anything left, which might involve repeating the last + # line. + for parent, blocks in remaining: + for a1, a2, b1, b2 in blocks: + for bk in xrange(b1, b2): + if child[0][bk].fctx == childfctx: + ak = min(a1 + (bk - b1), a2 - 1) + child[0][bk] = attr.evolve(parent[0][ak], skip=True) + return child + def toposort(revs, parentsfunc, firstbranch=()): """Yield revisions from heads to roots one (topo) branch at a time.