Mercurial > hg
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 |