comparison mercurial/context.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 ffa3026d4196
children 5d3abd6a5b25
comparison
equal deleted inserted replaced
36922:1b9f6440506b 36923:7affcabf561e
24 short, 24 short,
25 wdirid, 25 wdirid,
26 wdirnodes, 26 wdirnodes,
27 wdirrev, 27 wdirrev,
28 ) 28 )
29 from .thirdparty import (
30 attr,
31 )
32 from . import ( 29 from . import (
30 dagop,
33 encoding, 31 encoding,
34 error, 32 error,
35 fileset, 33 fileset,
36 match as matchmod, 34 match as matchmod,
37 mdiff, 35 mdiff,
976 in the file, where ctx is the filectx of the node where 974 in the file, where ctx is the filectx of the node where
977 that line was last changed; if linenumber parameter is true, number is 975 that line was last changed; if linenumber parameter is true, number is
978 the line number at the first appearance in the managed file, otherwise, 976 the line number at the first appearance in the managed file, otherwise,
979 number has a fixed value of False. 977 number has a fixed value of False.
980 ''' 978 '''
979 annotateline = dagop.annotateline
980 _annotatepair = dagop._annotatepair
981 981
982 def lines(text): 982 def lines(text):
983 if text.endswith("\n"): 983 if text.endswith("\n"):
984 return text.count("\n") 984 return text.count("\n")
985 return text.count("\n") + int(bool(text)) 985 return text.count("\n") + int(bool(text))
1102 """Returns `data()` after running repository decoding filters. 1102 """Returns `data()` after running repository decoding filters.
1103 1103
1104 This is often equivalent to how the data would be expressed on disk. 1104 This is often equivalent to how the data would be expressed on disk.
1105 """ 1105 """
1106 return self._repo.wwritedata(self.path(), self.data()) 1106 return self._repo.wwritedata(self.path(), self.data())
1107
1108 @attr.s(slots=True, frozen=True)
1109 class annotateline(object):
1110 fctx = attr.ib()
1111 lineno = attr.ib(default=False)
1112 # Whether this annotation was the result of a skip-annotate.
1113 skip = attr.ib(default=False)
1114
1115 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
1116 r'''
1117 Given parent and child fctxes and annotate data for parents, for all lines
1118 in either parent that match the child, annotate the child with the parent's
1119 data.
1120
1121 Additionally, if `skipchild` is True, replace all other lines with parent
1122 annotate data as well such that child is never blamed for any lines.
1123
1124 See test-annotate.py for unit tests.
1125 '''
1126 pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
1127 for parent in parents]
1128
1129 if skipchild:
1130 # Need to iterate over the blocks twice -- make it a list
1131 pblocks = [(p, list(blocks)) for (p, blocks) in pblocks]
1132 # Mercurial currently prefers p2 over p1 for annotate.
1133 # TODO: change this?
1134 for parent, blocks in pblocks:
1135 for (a1, a2, b1, b2), t in blocks:
1136 # Changed blocks ('!') or blocks made only of blank lines ('~')
1137 # belong to the child.
1138 if t == '=':
1139 child[0][b1:b2] = parent[0][a1:a2]
1140
1141 if skipchild:
1142 # Now try and match up anything that couldn't be matched,
1143 # Reversing pblocks maintains bias towards p2, matching above
1144 # behavior.
1145 pblocks.reverse()
1146
1147 # The heuristics are:
1148 # * Work on blocks of changed lines (effectively diff hunks with -U0).
1149 # This could potentially be smarter but works well enough.
1150 # * For a non-matching section, do a best-effort fit. Match lines in
1151 # diff hunks 1:1, dropping lines as necessary.
1152 # * Repeat the last line as a last resort.
1153
1154 # First, replace as much as possible without repeating the last line.
1155 remaining = [(parent, []) for parent, _blocks in pblocks]
1156 for idx, (parent, blocks) in enumerate(pblocks):
1157 for (a1, a2, b1, b2), _t in blocks:
1158 if a2 - a1 >= b2 - b1:
1159 for bk in xrange(b1, b2):
1160 if child[0][bk].fctx == childfctx:
1161 ak = min(a1 + (bk - b1), a2 - 1)
1162 child[0][bk] = attr.evolve(parent[0][ak], skip=True)
1163 else:
1164 remaining[idx][1].append((a1, a2, b1, b2))
1165
1166 # Then, look at anything left, which might involve repeating the last
1167 # line.
1168 for parent, blocks in remaining:
1169 for a1, a2, b1, b2 in blocks:
1170 for bk in xrange(b1, b2):
1171 if child[0][bk].fctx == childfctx:
1172 ak = min(a1 + (bk - b1), a2 - 1)
1173 child[0][bk] = attr.evolve(parent[0][ak], skip=True)
1174 return child
1175 1107
1176 class filectx(basefilectx): 1108 class filectx(basefilectx):
1177 """A filecontext object makes access to data related to a particular 1109 """A filecontext object makes access to data related to a particular
1178 filerevision convenient.""" 1110 filerevision convenient."""
1179 def __init__(self, repo, path, changeid=None, fileid=None, 1111 def __init__(self, repo, path, changeid=None, fileid=None,