Mercurial > hg-stable
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, |