mercurial/dagop.py
changeset 37067 434e520adb8c
parent 36941 ec46b0ee2e3c
child 37068 b235bde38a83
equal deleted inserted replaced
37066:39304dd63589 37067:434e520adb8c
   367     fctx = attr.ib()
   367     fctx = attr.ib()
   368     lineno = attr.ib(default=False)
   368     lineno = attr.ib(default=False)
   369     # Whether this annotation was the result of a skip-annotate.
   369     # Whether this annotation was the result of a skip-annotate.
   370     skip = attr.ib(default=False)
   370     skip = attr.ib(default=False)
   371 
   371 
       
   372 @attr.s(slots=True, frozen=True)
       
   373 class _annotatedfile(object):
       
   374     # list indexed by lineno - 1
       
   375     fctxs = attr.ib()
       
   376     linenos = attr.ib()
       
   377     skips = attr.ib()
       
   378     # full file content
       
   379     text = attr.ib()
       
   380 
   372 def _countlines(text):
   381 def _countlines(text):
   373     if text.endswith("\n"):
   382     if text.endswith("\n"):
   374         return text.count("\n")
   383         return text.count("\n")
   375     return text.count("\n") + int(bool(text))
   384     return text.count("\n") + int(bool(text))
   376 
   385 
   383     Additionally, if `skipchild` is True, replace all other lines with parent
   392     Additionally, if `skipchild` is True, replace all other lines with parent
   384     annotate data as well such that child is never blamed for any lines.
   393     annotate data as well such that child is never blamed for any lines.
   385 
   394 
   386     See test-annotate.py for unit tests.
   395     See test-annotate.py for unit tests.
   387     '''
   396     '''
   388     pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
   397     pblocks = [(parent, mdiff.allblocks(parent.text, child.text, opts=diffopts))
   389                for parent in parents]
   398                for parent in parents]
   390 
   399 
   391     if skipchild:
   400     if skipchild:
   392         # Need to iterate over the blocks twice -- make it a list
   401         # Need to iterate over the blocks twice -- make it a list
   393         pblocks = [(p, list(blocks)) for (p, blocks) in pblocks]
   402         pblocks = [(p, list(blocks)) for (p, blocks) in pblocks]
   396     for parent, blocks in pblocks:
   405     for parent, blocks in pblocks:
   397         for (a1, a2, b1, b2), t in blocks:
   406         for (a1, a2, b1, b2), t in blocks:
   398             # Changed blocks ('!') or blocks made only of blank lines ('~')
   407             # Changed blocks ('!') or blocks made only of blank lines ('~')
   399             # belong to the child.
   408             # belong to the child.
   400             if t == '=':
   409             if t == '=':
   401                 child[0][b1:b2] = parent[0][a1:a2]
   410                 child.fctxs[b1:b2] = parent.fctxs[a1:a2]
       
   411                 child.linenos[b1:b2] = parent.linenos[a1:a2]
       
   412                 child.skips[b1:b2] = parent.skips[a1:a2]
   402 
   413 
   403     if skipchild:
   414     if skipchild:
   404         # Now try and match up anything that couldn't be matched,
   415         # Now try and match up anything that couldn't be matched,
   405         # Reversing pblocks maintains bias towards p2, matching above
   416         # Reversing pblocks maintains bias towards p2, matching above
   406         # behavior.
   417         # behavior.
   417         remaining = [(parent, []) for parent, _blocks in pblocks]
   428         remaining = [(parent, []) for parent, _blocks in pblocks]
   418         for idx, (parent, blocks) in enumerate(pblocks):
   429         for idx, (parent, blocks) in enumerate(pblocks):
   419             for (a1, a2, b1, b2), _t in blocks:
   430             for (a1, a2, b1, b2), _t in blocks:
   420                 if a2 - a1 >= b2 - b1:
   431                 if a2 - a1 >= b2 - b1:
   421                     for bk in xrange(b1, b2):
   432                     for bk in xrange(b1, b2):
   422                         if child[0][bk].fctx == childfctx:
   433                         if child.fctxs[bk] == childfctx:
   423                             ak = min(a1 + (bk - b1), a2 - 1)
   434                             ak = min(a1 + (bk - b1), a2 - 1)
   424                             child[0][bk] = attr.evolve(parent[0][ak], skip=True)
   435                             child.fctxs[bk] = parent.fctxs[ak]
       
   436                             child.linenos[bk] = parent.linenos[ak]
       
   437                             child.skips[bk] = True
   425                 else:
   438                 else:
   426                     remaining[idx][1].append((a1, a2, b1, b2))
   439                     remaining[idx][1].append((a1, a2, b1, b2))
   427 
   440 
   428         # Then, look at anything left, which might involve repeating the last
   441         # Then, look at anything left, which might involve repeating the last
   429         # line.
   442         # line.
   430         for parent, blocks in remaining:
   443         for parent, blocks in remaining:
   431             for a1, a2, b1, b2 in blocks:
   444             for a1, a2, b1, b2 in blocks:
   432                 for bk in xrange(b1, b2):
   445                 for bk in xrange(b1, b2):
   433                     if child[0][bk].fctx == childfctx:
   446                     if child.fctxs[bk] == childfctx:
   434                         ak = min(a1 + (bk - b1), a2 - 1)
   447                         ak = min(a1 + (bk - b1), a2 - 1)
   435                         child[0][bk] = attr.evolve(parent[0][ak], skip=True)
   448                         child.fctxs[bk] = parent.fctxs[ak]
       
   449                         child.linenos[bk] = parent.linenos[ak]
       
   450                         child.skips[bk] = True
   436     return child
   451     return child
   437 
   452 
   438 def annotate(base, parents, linenumber=False, skiprevs=None, diffopts=None):
   453 def annotate(base, parents, linenumber=False, skiprevs=None, diffopts=None):
   439     """Core algorithm for filectx.annotate()
   454     """Core algorithm for filectx.annotate()
   440 
   455 
   441     `parents(fctx)` is a function returning a list of parent filectxs.
   456     `parents(fctx)` is a function returning a list of parent filectxs.
   442     """
   457     """
   443 
   458 
   444     if linenumber:
   459     if linenumber:
   445         def decorate(text, fctx):
   460         def decorate(text, fctx):
   446             return ([annotateline(fctx=fctx, lineno=i)
   461             n = _countlines(text)
   447                      for i in xrange(1, _countlines(text) + 1)], text)
   462             linenos = pycompat.rangelist(1, n + 1)
       
   463             return _annotatedfile([fctx] * n, linenos, [False] * n, text)
   448     else:
   464     else:
   449         def decorate(text, fctx):
   465         def decorate(text, fctx):
   450             return ([annotateline(fctx=fctx)] * _countlines(text), text)
   466             n = _countlines(text)
       
   467             return _annotatedfile([fctx] * n, [False] * n, [False] * n, text)
   451 
   468 
   452     # This algorithm would prefer to be recursive, but Python is a
   469     # This algorithm would prefer to be recursive, but Python is a
   453     # bit recursion-hostile. Instead we do an iterative
   470     # bit recursion-hostile. Instead we do an iterative
   454     # depth-first search.
   471     # depth-first search.
   455 
   472 
   499                     needed[p] -= 1
   516                     needed[p] -= 1
   500 
   517 
   501             hist[f] = curr
   518             hist[f] = curr
   502             del pcache[f]
   519             del pcache[f]
   503 
   520 
   504     lineattrs, text = hist[base]
   521     a = hist[base]
   505     return pycompat.ziplist(lineattrs, mdiff.splitnewlines(text))
   522     return [(annotateline(fctx, lineno, skip), line)
       
   523             for fctx, lineno, skip, line
       
   524             in zip(a.fctxs, a.linenos, a.skips, mdiff.splitnewlines(a.text))]
   506 
   525 
   507 def toposort(revs, parentsfunc, firstbranch=()):
   526 def toposort(revs, parentsfunc, firstbranch=()):
   508     """Yield revisions from heads to roots one (topo) branch at a time.
   527     """Yield revisions from heads to roots one (topo) branch at a time.
   509 
   528 
   510     This function aims to be used by a graph generator that wishes to minimize
   529     This function aims to be used by a graph generator that wishes to minimize