comparison mercurial/manifest.py @ 24225:3e5c4af69808

manifest: split manifestdict into high-level and low-level logic The low-level logic type (_lazymanifest) matches the behavior of the C implementation introduced in a5f1bccd. A future patch will use that when available.
author Augie Fackler <augie@google.com>
date Sat, 07 Mar 2015 12:04:39 -0500
parents d71837d06597
children b992769dd1be
comparison
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