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 |