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, |