mercurial/linelog.py
changeset 43076 2372284d9457
parent 38971 ee97f7a677f3
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    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)
   220         return _jl(op1, op2)
   231         return _jl(op1, op2)
   221     elif opcode == 2:
   232     elif opcode == 2:
   222         return _line(op1, op2)
   233         return _line(op1, op2)
   223     raise NotImplementedError('Unimplemented opcode %r' % opcode)
   234     raise NotImplementedError('Unimplemented opcode %r' % opcode)
   224 
   235 
       
   236 
   225 class linelog(object):
   237 class linelog(object):
   226     """Efficient cache for per-line history information."""
   238     """Efficient cache for per-line history information."""
   227 
   239 
   228     def __init__(self, program=None, maxrev=0):
   240     def __init__(self, program=None, maxrev=0):
   229         if program is None:
   241         if program is None:
   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:
   382             lastpc = pc
   406             lastpc = pc
   383             pc = inst.execute(rev, pc, lines.append)
   407             pc = inst.execute(rev, pc, lines.append)
   384             executed += 1
   408             executed += 1
   385         if pc is not None:
   409         if pc is not None:
   386             raise LineLogError(
   410             raise LineLogError(
   387                 r'Probably hit an infinite loop in linelog. Program:\n' +
   411                 r'Probably hit an infinite loop in linelog. Program:\n'
   388                 self.debugstr())
   412                 + self.debugstr()
       
   413             )
   389         ar = annotateresult(rev, lines, lastpc)
   414         ar = annotateresult(rev, lines, lastpc)
   390         self._lastannotate = ar
   415         self._lastannotate = ar
   391         return ar
   416         return ar
   392 
   417 
   393     @property
   418     @property