21 from __future__ import absolute_import, print_function |
21 from __future__ import absolute_import, print_function |
22 |
22 |
23 import abc |
23 import abc |
24 import struct |
24 import struct |
25 |
25 |
26 from .thirdparty import ( |
26 from .thirdparty import attr |
27 attr, |
27 from . import pycompat |
28 ) |
|
29 from . import ( |
|
30 pycompat, |
|
31 ) |
|
32 |
28 |
33 _llentry = struct.Struct('>II') |
29 _llentry = struct.Struct('>II') |
|
30 |
34 |
31 |
35 class LineLogError(Exception): |
32 class LineLogError(Exception): |
36 """Error raised when something bad happens internally in linelog.""" |
33 """Error raised when something bad happens internally in linelog.""" |
|
34 |
37 |
35 |
38 @attr.s |
36 @attr.s |
39 class lineinfo(object): |
37 class lineinfo(object): |
40 # Introducing revision of this line. |
38 # Introducing revision of this line. |
41 rev = attr.ib() |
39 rev = attr.ib() |
42 # Line number for this line in its introducing revision. |
40 # Line number for this line in its introducing revision. |
43 linenum = attr.ib() |
41 linenum = attr.ib() |
44 # Private. Offset in the linelog program of this line. Used internally. |
42 # Private. Offset in the linelog program of this line. Used internally. |
45 _offset = attr.ib() |
43 _offset = attr.ib() |
46 |
44 |
|
45 |
47 @attr.s |
46 @attr.s |
48 class annotateresult(object): |
47 class annotateresult(object): |
49 rev = attr.ib() |
48 rev = attr.ib() |
50 lines = attr.ib() |
49 lines = attr.ib() |
51 _eof = attr.ib() |
50 _eof = attr.ib() |
52 |
51 |
53 def __iter__(self): |
52 def __iter__(self): |
54 return iter(self.lines) |
53 return iter(self.lines) |
55 |
54 |
|
55 |
56 class _llinstruction(object): |
56 class _llinstruction(object): |
57 |
57 |
58 __metaclass__ = abc.ABCMeta |
58 __metaclass__ = abc.ABCMeta |
59 |
59 |
60 @abc.abstractmethod |
60 @abc.abstractmethod |
88 Returns: |
88 Returns: |
89 The new value of pc. Returns None if exeuction should stop |
89 The new value of pc. Returns None if exeuction should stop |
90 (that is, we've found the end of the file.) |
90 (that is, we've found the end of the file.) |
91 """ |
91 """ |
92 |
92 |
|
93 |
93 class _jge(_llinstruction): |
94 class _jge(_llinstruction): |
94 """If the current rev is greater than or equal to op1, jump to op2.""" |
95 """If the current rev is greater than or equal to op1, jump to op2.""" |
95 |
96 |
96 def __init__(self, op1, op2): |
97 def __init__(self, op1, op2): |
97 self._cmprev = op1 |
98 self._cmprev = op1 |
99 |
100 |
100 def __str__(self): |
101 def __str__(self): |
101 return r'JGE %d %d' % (self._cmprev, self._target) |
102 return r'JGE %d %d' % (self._cmprev, self._target) |
102 |
103 |
103 def __eq__(self, other): |
104 def __eq__(self, other): |
104 return (type(self) == type(other) |
105 return ( |
105 and self._cmprev == other._cmprev |
106 type(self) == type(other) |
106 and self._target == other._target) |
107 and self._cmprev == other._cmprev |
|
108 and self._target == other._target |
|
109 ) |
107 |
110 |
108 def encode(self): |
111 def encode(self): |
109 return _llentry.pack(self._cmprev << 2, self._target) |
112 return _llentry.pack(self._cmprev << 2, self._target) |
110 |
113 |
111 def execute(self, rev, pc, emit): |
114 def execute(self, rev, pc, emit): |
112 if rev >= self._cmprev: |
115 if rev >= self._cmprev: |
113 return self._target |
116 return self._target |
114 return pc + 1 |
117 return pc + 1 |
115 |
118 |
|
119 |
116 class _jump(_llinstruction): |
120 class _jump(_llinstruction): |
117 """Unconditional jumps are expressed as a JGE with op1 set to 0.""" |
121 """Unconditional jumps are expressed as a JGE with op1 set to 0.""" |
118 |
122 |
119 def __init__(self, op1, op2): |
123 def __init__(self, op1, op2): |
120 if op1 != 0: |
124 if op1 != 0: |
123 |
127 |
124 def __str__(self): |
128 def __str__(self): |
125 return r'JUMP %d' % (self._target) |
129 return r'JUMP %d' % (self._target) |
126 |
130 |
127 def __eq__(self, other): |
131 def __eq__(self, other): |
128 return (type(self) == type(other) |
132 return type(self) == type(other) and self._target == other._target |
129 and self._target == other._target) |
|
130 |
133 |
131 def encode(self): |
134 def encode(self): |
132 return _llentry.pack(0, self._target) |
135 return _llentry.pack(0, self._target) |
133 |
136 |
134 def execute(self, rev, pc, emit): |
137 def execute(self, rev, pc, emit): |
135 return self._target |
138 return self._target |
|
139 |
136 |
140 |
137 class _eof(_llinstruction): |
141 class _eof(_llinstruction): |
138 """EOF is expressed as a JGE that always jumps to 0.""" |
142 """EOF is expressed as a JGE that always jumps to 0.""" |
139 |
143 |
140 def __init__(self, op1, op2): |
144 def __init__(self, op1, op2): |
153 return _llentry.pack(0, 0) |
157 return _llentry.pack(0, 0) |
154 |
158 |
155 def execute(self, rev, pc, emit): |
159 def execute(self, rev, pc, emit): |
156 return None |
160 return None |
157 |
161 |
|
162 |
158 class _jl(_llinstruction): |
163 class _jl(_llinstruction): |
159 """If the current rev is less than op1, jump to op2.""" |
164 """If the current rev is less than op1, jump to op2.""" |
160 |
165 |
161 def __init__(self, op1, op2): |
166 def __init__(self, op1, op2): |
162 self._cmprev = op1 |
167 self._cmprev = op1 |
164 |
169 |
165 def __str__(self): |
170 def __str__(self): |
166 return r'JL %d %d' % (self._cmprev, self._target) |
171 return r'JL %d %d' % (self._cmprev, self._target) |
167 |
172 |
168 def __eq__(self, other): |
173 def __eq__(self, other): |
169 return (type(self) == type(other) |
174 return ( |
170 and self._cmprev == other._cmprev |
175 type(self) == type(other) |
171 and self._target == other._target) |
176 and self._cmprev == other._cmprev |
|
177 and self._target == other._target |
|
178 ) |
172 |
179 |
173 def encode(self): |
180 def encode(self): |
174 return _llentry.pack(1 | (self._cmprev << 2), self._target) |
181 return _llentry.pack(1 | (self._cmprev << 2), self._target) |
175 |
182 |
176 def execute(self, rev, pc, emit): |
183 def execute(self, rev, pc, emit): |
177 if rev < self._cmprev: |
184 if rev < self._cmprev: |
178 return self._target |
185 return self._target |
179 return pc + 1 |
186 return pc + 1 |
|
187 |
180 |
188 |
181 class _line(_llinstruction): |
189 class _line(_llinstruction): |
182 """Emit a line.""" |
190 """Emit a line.""" |
183 |
191 |
184 def __init__(self, op1, op2): |
192 def __init__(self, op1, op2): |
189 |
197 |
190 def __str__(self): |
198 def __str__(self): |
191 return r'LINE %d %d' % (self._rev, self._origlineno) |
199 return r'LINE %d %d' % (self._rev, self._origlineno) |
192 |
200 |
193 def __eq__(self, other): |
201 def __eq__(self, other): |
194 return (type(self) == type(other) |
202 return ( |
195 and self._rev == other._rev |
203 type(self) == type(other) |
196 and self._origlineno == other._origlineno) |
204 and self._rev == other._rev |
|
205 and self._origlineno == other._origlineno |
|
206 ) |
197 |
207 |
198 def encode(self): |
208 def encode(self): |
199 return _llentry.pack(2 | (self._rev << 2), self._origlineno) |
209 return _llentry.pack(2 | (self._rev << 2), self._origlineno) |
200 |
210 |
201 def execute(self, rev, pc, emit): |
211 def execute(self, rev, pc, emit): |
202 emit(lineinfo(self._rev, self._origlineno, pc)) |
212 emit(lineinfo(self._rev, self._origlineno, pc)) |
203 return pc + 1 |
213 return pc + 1 |
|
214 |
204 |
215 |
205 def _decodeone(data, offset): |
216 def _decodeone(data, offset): |
206 """Decode a single linelog instruction from an offset in a buffer.""" |
217 """Decode a single linelog instruction from an offset in a buffer.""" |
207 try: |
218 try: |
208 op1, op2 = _llentry.unpack_from(data, offset) |
219 op1, op2 = _llentry.unpack_from(data, offset) |
234 self._program = program |
246 self._program = program |
235 self._lastannotate = None |
247 self._lastannotate = None |
236 self._maxrev = maxrev |
248 self._maxrev = maxrev |
237 |
249 |
238 def __eq__(self, other): |
250 def __eq__(self, other): |
239 return (type(self) == type(other) |
251 return ( |
240 and self._program == other._program |
252 type(self) == type(other) |
241 and self._maxrev == other._maxrev) |
253 and self._program == other._program |
|
254 and self._maxrev == other._maxrev |
|
255 ) |
242 |
256 |
243 def __repr__(self): |
257 def __repr__(self): |
244 return '<linelog at %s: maxrev=%d size=%d>' % ( |
258 return '<linelog at %s: maxrev=%d size=%d>' % ( |
245 hex(id(self)), self._maxrev, len(self._program)) |
259 hex(id(self)), |
|
260 self._maxrev, |
|
261 len(self._program), |
|
262 ) |
246 |
263 |
247 def debugstr(self): |
264 def debugstr(self): |
248 fmt = r'%%%dd %%s' % len(str(len(self._program))) |
265 fmt = r'%%%dd %%s' % len(str(len(self._program))) |
249 return pycompat.sysstr('\n').join( |
266 return pycompat.sysstr('\n').join( |
250 fmt % (idx, i) for idx, i in enumerate(self._program[1:], 1)) |
267 fmt % (idx, i) for idx, i in enumerate(self._program[1:], 1) |
|
268 ) |
251 |
269 |
252 @classmethod |
270 @classmethod |
253 def fromdata(cls, buf): |
271 def fromdata(cls, buf): |
254 if len(buf) % _llentry.size != 0: |
272 if len(buf) % _llentry.size != 0: |
255 raise LineLogError( |
273 raise LineLogError( |
256 "invalid linelog buffer size %d (must be a multiple of %d)" % ( |
274 "invalid linelog buffer size %d (must be a multiple of %d)" |
257 len(buf), _llentry.size)) |
275 % (len(buf), _llentry.size) |
|
276 ) |
258 expected = len(buf) / _llentry.size |
277 expected = len(buf) / _llentry.size |
259 fakejge = _decodeone(buf, 0) |
278 fakejge = _decodeone(buf, 0) |
260 if isinstance(fakejge, _jump): |
279 if isinstance(fakejge, _jump): |
261 maxrev = 0 |
280 maxrev = 0 |
262 else: |
281 else: |
263 maxrev = fakejge._cmprev |
282 maxrev = fakejge._cmprev |
264 numentries = fakejge._target |
283 numentries = fakejge._target |
265 if expected != numentries: |
284 if expected != numentries: |
266 raise LineLogError("corrupt linelog data: claimed" |
285 raise LineLogError( |
267 " %d entries but given data for %d entries" % ( |
286 "corrupt linelog data: claimed" |
268 expected, numentries)) |
287 " %d entries but given data for %d entries" |
|
288 % (expected, numentries) |
|
289 ) |
269 instructions = [_eof(0, 0)] |
290 instructions = [_eof(0, 0)] |
270 for offset in pycompat.xrange(1, numentries): |
291 for offset in pycompat.xrange(1, numentries): |
271 instructions.append(_decodeone(buf, offset * _llentry.size)) |
292 instructions.append(_decodeone(buf, offset * _llentry.size)) |
272 return cls(instructions, maxrev=maxrev) |
293 return cls(instructions, maxrev=maxrev) |
273 |
294 |
279 self._program = [] |
300 self._program = [] |
280 self._maxrev = 0 |
301 self._maxrev = 0 |
281 self._lastannotate = None |
302 self._lastannotate = None |
282 |
303 |
283 def replacelines_vec(self, rev, a1, a2, blines): |
304 def replacelines_vec(self, rev, a1, a2, blines): |
284 return self.replacelines(rev, a1, a2, 0, len(blines), |
305 return self.replacelines( |
285 _internal_blines=blines) |
306 rev, a1, a2, 0, len(blines), _internal_blines=blines |
|
307 ) |
286 |
308 |
287 def replacelines(self, rev, a1, a2, b1, b2, _internal_blines=None): |
309 def replacelines(self, rev, a1, a2, b1, b2, _internal_blines=None): |
288 """Replace lines [a1, a2) with lines [b1, b2).""" |
310 """Replace lines [a1, a2) with lines [b1, b2).""" |
289 if self._lastannotate: |
311 if self._lastannotate: |
290 # TODO(augie): make replacelines() accept a revision at |
312 # TODO(augie): make replacelines() accept a revision at |
296 else: |
318 else: |
297 ar = self.annotate(rev) |
319 ar = self.annotate(rev) |
298 # ar = self.annotate(self._maxrev) |
320 # ar = self.annotate(self._maxrev) |
299 if a1 > len(ar.lines): |
321 if a1 > len(ar.lines): |
300 raise LineLogError( |
322 raise LineLogError( |
301 '%d contains %d lines, tried to access line %d' % ( |
323 '%d contains %d lines, tried to access line %d' |
302 rev, len(ar.lines), a1)) |
324 % (rev, len(ar.lines), a1) |
|
325 ) |
303 elif a1 == len(ar.lines): |
326 elif a1 == len(ar.lines): |
304 # Simulated EOF instruction since we're at EOF, which |
327 # Simulated EOF instruction since we're at EOF, which |
305 # doesn't have a "real" line. |
328 # doesn't have a "real" line. |
306 a1inst = _eof(0, 0) |
329 a1inst = _eof(0, 0) |
307 a1info = lineinfo(0, 0, ar._eof) |
330 a1info = lineinfo(0, 0, ar._eof) |
331 appendinst(_line(newrev, newlinenum)) |
354 appendinst(_line(newrev, newlinenum)) |
332 # delete |
355 # delete |
333 if a1 < a2: |
356 if a1 < a2: |
334 if a2 > len(ar.lines): |
357 if a2 > len(ar.lines): |
335 raise LineLogError( |
358 raise LineLogError( |
336 '%d contains %d lines, tried to access line %d' % ( |
359 '%d contains %d lines, tried to access line %d' |
337 rev, len(ar.lines), a2)) |
360 % (rev, len(ar.lines), a2) |
|
361 ) |
338 elif a2 == len(ar.lines): |
362 elif a2 == len(ar.lines): |
339 endaddr = ar._eof |
363 endaddr = ar._eof |
340 else: |
364 else: |
341 endaddr = ar.lines[a2]._offset |
365 endaddr = ar.lines[a2]._offset |
342 if a2 > 0 and rev < self._maxrev: |
366 if a2 > 0 and rev < self._maxrev: |