mercurial/manifest.py
changeset 24225 3e5c4af69808
parent 24224 d71837d06597
child 24226 b992769dd1be
equal deleted inserted replaced
24224:d71837d06597 24225:3e5c4af69808
     7 
     7 
     8 from i18n import _
     8 from i18n import _
     9 import mdiff, parsers, error, revlog, util
     9 import mdiff, parsers, error, revlog, util
    10 import array, struct
    10 import array, struct
    11 
    11 
    12 # Pure Python fallback
    12 
    13 def _parsemanifest(mfdict, fdict, lines):
    13 class _lazymanifest(dict):
    14     bin = revlog.bin
    14     """This is the pure implementation of lazymanifest.
    15     for l in lines.splitlines():
    15 
    16         f, n = l.split('\0')
    16     It has not been optimized *at all* and is not lazy.
    17         if len(n) > 40:
    17     """
    18             fdict[f] = n[40:]
    18 
    19             mfdict[f] = bin(n[:40])
    19     def __init__(self, data):
    20         else:
    20         # This init method does a little bit of excessive-looking
    21             mfdict[f] = bin(n)
    21         # precondition checking. This is so that the behavior of this
    22 
    22         # class exactly matches its C counterpart to try and help
    23 def _parse(lines, mfdict, flags):
    23         # prevent surprise breakage for anyone that develops against
    24     try:
    24         # the pure version.
    25         parsers.parse_manifest(mfdict, flags, lines)
    25         if data and data[-1] != '\n':
    26     except AttributeError:
    26             raise ValueError('Manifest did not end in a newline.')
    27         _parsemanifest(mfdict, flags, lines)
    27         dict.__init__(self)
    28     return mfdict
    28         prev = None
    29 
    29         for l in data.splitlines():
    30 class manifestdict(dict):
    30             if prev is not None and prev > l:
       
    31                 raise ValueError('Manifest lines not in sorted order.')
       
    32             prev = l
       
    33             f, n = l.split('\0')
       
    34             if len(n) > 40:
       
    35                 self[f] = revlog.bin(n[:40]), n[40:]
       
    36             else:
       
    37                 self[f] = revlog.bin(n), ''
       
    38 
       
    39     def __setitem__(self, k, v):
       
    40         node, flag = v
       
    41         assert node is not None
       
    42         if len(node) > 21:
       
    43             node = node[:21] # match c implementation behavior
       
    44         dict.__setitem__(self, k, (node, flag))
       
    45 
       
    46     def __iter__(self):
       
    47         return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
       
    48 
       
    49     def copy(self):
       
    50         c = _lazymanifest('')
       
    51         c.update(self)
       
    52         return c
       
    53 
       
    54     def diff(self, m2, clean=False):
       
    55         '''Finds changes between the current manifest and m2.'''
       
    56         diff = {}
       
    57 
       
    58         for fn, e1 in self.iteritems():
       
    59             if fn not in m2:
       
    60                 diff[fn] = e1, (None, '')
       
    61             else:
       
    62                 e2 = m2[fn]
       
    63                 if e1 != e2:
       
    64                     diff[fn] = e1, e2
       
    65                 elif clean:
       
    66                     diff[fn] = None
       
    67 
       
    68         for fn, e2 in m2.iteritems():
       
    69             if fn not in self:
       
    70                 diff[fn] = (None, ''), e2
       
    71 
       
    72         return diff
       
    73 
       
    74     def filtercopy(self, filterfn):
       
    75         c = _lazymanifest('')
       
    76         for f, n, fl in self:
       
    77             if filterfn(f):
       
    78                 c[f] = n, fl
       
    79         return c
       
    80 
       
    81     def text(self):
       
    82         """Get the full data of this manifest as a bytestring."""
       
    83         fl = sorted(self)
       
    84 
       
    85         _hex = revlog.hex
       
    86         # if this is changed to support newlines in filenames,
       
    87         # be sure to check the templates/ dir again (especially *-raw.tmpl)
       
    88         return ''.join("%s\0%s%s\n" % (
       
    89             f, _hex(n[:20]), flag) for f, n, flag in fl)
       
    90 
       
    91 class manifestdict(object):
    31     def __init__(self, data=''):
    92     def __init__(self, data=''):
    32         self._flags = {}
    93         self._lm = _lazymanifest(data)
    33         _parse(data, self, self._flags)
    94 
    34 
    95     def __getitem__(self, key):
    35     def __setitem__(self, k, v):
    96         return self._lm[key][0]
    36         assert v is not None
    97 
    37         dict.__setitem__(self, k, v)
    98     def __len__(self):
    38     def flags(self, f):
    99         return len(self._lm)
    39         return self._flags.get(f, "")
   100 
    40     def setflag(self, f, flags):
   101     def __setitem__(self, key, node):
    41         """Set the flags (symlink, executable) for path f."""
   102         self._lm[key] = node, self.flags(key, '')
    42         self._flags[f] = flags
   103 
    43     def copy(self):
   104     def __contains__(self, key):
    44         copy = manifestdict()
   105         return key in self._lm
    45         dict.__init__(copy, self)
   106 
    46         copy._flags = dict.copy(self._flags)
   107     def __delitem__(self, key):
    47         return copy
   108         del self._lm[key]
       
   109 
       
   110     def keys(self):
       
   111         return [x[0] for x in self._lm]
       
   112 
       
   113     def iterkeys(self):
       
   114         return iter(self.keys())
       
   115 
    48     def intersectfiles(self, files):
   116     def intersectfiles(self, files):
    49         '''make a new manifestdict with the intersection of self with files
   117         '''make a new lazymanifest with the intersection of self with files
    50 
   118 
    51         The algorithm assumes that files is much smaller than self.'''
   119         The algorithm assumes that files is much smaller than self.'''
    52         ret = manifestdict()
   120         ret = manifestdict()
       
   121         lm = self._lm
    53         for fn in files:
   122         for fn in files:
    54             if fn in self:
   123             if fn in lm:
    55                 ret[fn] = self[fn]
   124                 ret._lm[fn] = self._lm[fn]
    56                 flags = self._flags.get(fn, None)
       
    57                 if flags:
       
    58                     ret._flags[fn] = flags
       
    59         return ret
   125         return ret
    60 
   126 
    61     def filesnotin(self, m2):
   127     def filesnotin(self, m2):
    62         '''Set of files in this manifest that are not in the other'''
   128         '''Set of files in this manifest that are not in the other'''
    63         files = set(self.iterkeys())
   129         files = set(self.iterkeys())
    72         files = match.files()
   138         files = match.files()
    73         if (match.matchfn == match.exact or
   139         if (match.matchfn == match.exact or
    74             (not match.anypats() and util.all(fn in self for fn in files))):
   140             (not match.anypats() and util.all(fn in self for fn in files))):
    75             return self.intersectfiles(files)
   141             return self.intersectfiles(files)
    76 
   142 
    77         m = self.copy()
   143         lm = manifestdict('')
    78         for fn in m.keys():
   144         lm._lm = self._lm.filtercopy(match)
    79             if not match(fn):
   145         return lm
    80                 del m[fn]
       
    81         return m
       
    82 
   146 
    83     def diff(self, m2, clean=False):
   147     def diff(self, m2, clean=False):
    84         '''Finds changes between the current manifest and m2.
   148         '''Finds changes between the current manifest and m2.
    85 
   149 
    86         Args:
   150         Args:
    93         nodeid in the current/other manifest and fl1/fl2 is the flag
   157         nodeid in the current/other manifest and fl1/fl2 is the flag
    94         in the current/other manifest. Where the file does not exist,
   158         in the current/other manifest. Where the file does not exist,
    95         the nodeid will be None and the flags will be the empty
   159         the nodeid will be None and the flags will be the empty
    96         string.
   160         string.
    97         '''
   161         '''
    98         diff = {}
   162         return self._lm.diff(m2._lm, clean)
    99 
   163 
   100         for fn, n1 in self.iteritems():
   164     def setflag(self, key, flag):
   101             fl1 = self._flags.get(fn, '')
   165         self._lm[key] = self[key], flag
   102             n2 = m2.get(fn, None)
   166 
   103             fl2 = m2._flags.get(fn, '')
   167     def get(self, key, default=None):
   104             if n2 is None:
   168         try:
   105                 fl2 = ''
   169             return self._lm[key][0]
   106             if n1 != n2 or fl1 != fl2:
   170         except KeyError:
   107                 diff[fn] = ((n1, fl1), (n2, fl2))
   171             return default
   108             elif clean:
   172 
   109                 diff[fn] = None
   173     def flags(self, key, default=''):
   110 
   174         try:
   111         for fn, n2 in m2.iteritems():
   175             return self._lm[key][1]
   112             if fn not in self:
   176         except KeyError:
   113                 fl2 = m2._flags.get(fn, '')
   177             return default
   114                 diff[fn] = ((None, ''), (n2, fl2))
   178 
   115 
   179     def __iter__(self):
   116         return diff
   180         return (x[0] for x in self._lm)
       
   181 
       
   182     def copy(self):
       
   183         c = manifestdict('')
       
   184         c._lm = self._lm.copy()
       
   185         return c
       
   186 
       
   187     def iteritems(self):
       
   188         return (x[:2] for x in self._lm)
   117 
   189 
   118     def text(self):
   190     def text(self):
   119         """Get the full data of this manifest as a bytestring."""
   191         return self._lm.text()
   120         fl = sorted(self)
       
   121         _checkforbidden(fl)
       
   122 
       
   123         hex, flags = revlog.hex, self.flags
       
   124         # if this is changed to support newlines in filenames,
       
   125         # be sure to check the templates/ dir again (especially *-raw.tmpl)
       
   126         return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
       
   127 
   192 
   128     def fastdelta(self, base, changes):
   193     def fastdelta(self, base, changes):
   129         """Given a base manifest text as an array.array and a list of changes
   194         """Given a base manifest text as an array.array and a list of changes
   130         relative to that text, compute a delta that can be used by revlog.
   195         relative to that text, compute a delta that can be used by revlog.
   131         """
   196         """
   141         # each line and creates the deltas
   206         # each line and creates the deltas
   142         for f, todelete in changes:
   207         for f, todelete in changes:
   143             # bs will either be the index of the item or the insert point
   208             # bs will either be the index of the item or the insert point
   144             start, end = _msearch(addbuf, f, start)
   209             start, end = _msearch(addbuf, f, start)
   145             if not todelete:
   210             if not todelete:
   146                 l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
   211                 h, fl = self._lm[f]
       
   212                 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
   147             else:
   213             else:
   148                 if start == end:
   214                 if start == end:
   149                     # item we want to delete was not found, error out
   215                     # item we want to delete was not found, error out
   150                     raise AssertionError(
   216                     raise AssertionError(
   151                             _("failed to remove %s from manifest") % f)
   217                             _("failed to remove %s from manifest") % f)
   278         return (node, flags) pair if found, (None, None) if not.'''
   344         return (node, flags) pair if found, (None, None) if not.'''
   279         if node in self._mancache:
   345         if node in self._mancache:
   280             m = self._mancache[node][0]
   346             m = self._mancache[node][0]
   281             return m.get(f), m.flags(f)
   347             return m.get(f), m.flags(f)
   282         text = self.revision(node)
   348         text = self.revision(node)
   283         start, end = _msearch(text, f)
   349         try:
   284         if start == end:
   350             return manifestdict(text)._lm[f]
       
   351         except KeyError:
   285             return None, None
   352             return None, None
   286         l = text[start:end]
       
   287         f, n = l.split('\0')
       
   288         return revlog.bin(n[:40]), n[40:-1]
       
   289 
   353 
   290     def add(self, m, transaction, link, p1, p2, added, removed):
   354     def add(self, m, transaction, link, p1, p2, added, removed):
   291         if p1 in self._mancache:
   355         if p1 in self._mancache:
   292             # If our first parent is in the manifest cache, we can
   356             # If our first parent is in the manifest cache, we can
   293             # compute a delta here using properties we know about the
   357             # compute a delta here using properties we know about the