##// END OF EJS Templates
manifest: move manifest parsing to module-level...
Augie Fackler -
r22786:079a0ed5 default
parent child Browse files
Show More
@@ -1,234 +1,233 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import mdiff, parsers, error, revlog, util, dicthelpers
9 import mdiff, parsers, error, revlog, util, dicthelpers
10 import array, struct
10 import array, struct
11
11
12 class manifestdict(dict):
12 class manifestdict(dict):
13 def __init__(self, mapping=None, flags=None):
13 def __init__(self, mapping=None, flags=None):
14 if mapping is None:
14 if mapping is None:
15 mapping = {}
15 mapping = {}
16 if flags is None:
16 if flags is None:
17 flags = {}
17 flags = {}
18 dict.__init__(self, mapping)
18 dict.__init__(self, mapping)
19 self._flags = flags
19 self._flags = flags
20 def flags(self, f):
20 def flags(self, f):
21 return self._flags.get(f, "")
21 return self._flags.get(f, "")
22 def withflags(self):
22 def withflags(self):
23 return set(self._flags.keys())
23 return set(self._flags.keys())
24 def set(self, f, flags):
24 def set(self, f, flags):
25 self._flags[f] = flags
25 self._flags[f] = flags
26 def copy(self):
26 def copy(self):
27 return manifestdict(self, dict.copy(self._flags))
27 return manifestdict(self, dict.copy(self._flags))
28 def intersectfiles(self, files):
28 def intersectfiles(self, files):
29 '''make a new manifestdict with the intersection of self with files
29 '''make a new manifestdict with the intersection of self with files
30
30
31 The algorithm assumes that files is much smaller than self.'''
31 The algorithm assumes that files is much smaller than self.'''
32 ret = manifestdict()
32 ret = manifestdict()
33 for fn in files:
33 for fn in files:
34 if fn in self:
34 if fn in self:
35 ret[fn] = self[fn]
35 ret[fn] = self[fn]
36 flags = self._flags.get(fn, None)
36 flags = self._flags.get(fn, None)
37 if flags:
37 if flags:
38 ret._flags[fn] = flags
38 ret._flags[fn] = flags
39 return ret
39 return ret
40 def flagsdiff(self, d2):
40 def flagsdiff(self, d2):
41 return dicthelpers.diff(self._flags, d2._flags, "")
41 return dicthelpers.diff(self._flags, d2._flags, "")
42
42
43
43
44 def _checkforbidden(l):
44 def _checkforbidden(l):
45 """Check filenames for illegal characters."""
45 """Check filenames for illegal characters."""
46 for f in l:
46 for f in l:
47 if '\n' in f or '\r' in f:
47 if '\n' in f or '\r' in f:
48 raise error.RevlogError(
48 raise error.RevlogError(
49 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
49 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
50
50
51
51
52 # apply the changes collected during the bisect loop to our addlist
52 # apply the changes collected during the bisect loop to our addlist
53 # return a delta suitable for addrevision
53 # return a delta suitable for addrevision
54 def _addlistdelta(addlist, x):
54 def _addlistdelta(addlist, x):
55 # for large addlist arrays, building a new array is cheaper
55 # for large addlist arrays, building a new array is cheaper
56 # than repeatedly modifying the existing one
56 # than repeatedly modifying the existing one
57 currentposition = 0
57 currentposition = 0
58 newaddlist = array.array('c')
58 newaddlist = array.array('c')
59
59
60 for start, end, content in x:
60 for start, end, content in x:
61 newaddlist += addlist[currentposition:start]
61 newaddlist += addlist[currentposition:start]
62 if content:
62 if content:
63 newaddlist += array.array('c', content)
63 newaddlist += array.array('c', content)
64
64
65 currentposition = end
65 currentposition = end
66
66
67 newaddlist += addlist[currentposition:]
67 newaddlist += addlist[currentposition:]
68
68
69 deltatext = "".join(struct.pack(">lll", start, end, len(content))
69 deltatext = "".join(struct.pack(">lll", start, end, len(content))
70 + content for start, end, content in x)
70 + content for start, end, content in x)
71 return deltatext, newaddlist
71 return deltatext, newaddlist
72
72
73 def _parse(lines):
74 mfdict = manifestdict()
75 parsers.parse_manifest(mfdict, mfdict._flags, lines)
76 return mfdict
73
77
74 class manifest(revlog.revlog):
78 class manifest(revlog.revlog):
75 def __init__(self, opener):
79 def __init__(self, opener):
76 # we expect to deal with not more than four revs at a time,
80 # we expect to deal with not more than four revs at a time,
77 # during a commit --amend
81 # during a commit --amend
78 self._mancache = util.lrucachedict(4)
82 self._mancache = util.lrucachedict(4)
79 revlog.revlog.__init__(self, opener, "00manifest.i")
83 revlog.revlog.__init__(self, opener, "00manifest.i")
80
84
81 def parse(self, lines):
82 mfdict = manifestdict()
83 parsers.parse_manifest(mfdict, mfdict._flags, lines)
84 return mfdict
85
86 def readdelta(self, node):
85 def readdelta(self, node):
87 r = self.rev(node)
86 r = self.rev(node)
88 return self.parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
87 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
89
88
90 def readfast(self, node):
89 def readfast(self, node):
91 '''use the faster of readdelta or read'''
90 '''use the faster of readdelta or read'''
92 r = self.rev(node)
91 r = self.rev(node)
93 deltaparent = self.deltaparent(r)
92 deltaparent = self.deltaparent(r)
94 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
93 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
95 return self.readdelta(node)
94 return self.readdelta(node)
96 return self.read(node)
95 return self.read(node)
97
96
98 def read(self, node):
97 def read(self, node):
99 if node == revlog.nullid:
98 if node == revlog.nullid:
100 return manifestdict() # don't upset local cache
99 return manifestdict() # don't upset local cache
101 if node in self._mancache:
100 if node in self._mancache:
102 return self._mancache[node][0]
101 return self._mancache[node][0]
103 text = self.revision(node)
102 text = self.revision(node)
104 arraytext = array.array('c', text)
103 arraytext = array.array('c', text)
105 mapping = self.parse(text)
104 mapping = _parse(text)
106 self._mancache[node] = (mapping, arraytext)
105 self._mancache[node] = (mapping, arraytext)
107 return mapping
106 return mapping
108
107
109 def _search(self, m, s, lo=0, hi=None):
108 def _search(self, m, s, lo=0, hi=None):
110 '''return a tuple (start, end) that says where to find s within m.
109 '''return a tuple (start, end) that says where to find s within m.
111
110
112 If the string is found m[start:end] are the line containing
111 If the string is found m[start:end] are the line containing
113 that string. If start == end the string was not found and
112 that string. If start == end the string was not found and
114 they indicate the proper sorted insertion point.
113 they indicate the proper sorted insertion point.
115
114
116 m should be a buffer or a string
115 m should be a buffer or a string
117 s is a string'''
116 s is a string'''
118 def advance(i, c):
117 def advance(i, c):
119 while i < lenm and m[i] != c:
118 while i < lenm and m[i] != c:
120 i += 1
119 i += 1
121 return i
120 return i
122 if not s:
121 if not s:
123 return (lo, lo)
122 return (lo, lo)
124 lenm = len(m)
123 lenm = len(m)
125 if not hi:
124 if not hi:
126 hi = lenm
125 hi = lenm
127 while lo < hi:
126 while lo < hi:
128 mid = (lo + hi) // 2
127 mid = (lo + hi) // 2
129 start = mid
128 start = mid
130 while start > 0 and m[start - 1] != '\n':
129 while start > 0 and m[start - 1] != '\n':
131 start -= 1
130 start -= 1
132 end = advance(start, '\0')
131 end = advance(start, '\0')
133 if m[start:end] < s:
132 if m[start:end] < s:
134 # we know that after the null there are 40 bytes of sha1
133 # we know that after the null there are 40 bytes of sha1
135 # this translates to the bisect lo = mid + 1
134 # this translates to the bisect lo = mid + 1
136 lo = advance(end + 40, '\n') + 1
135 lo = advance(end + 40, '\n') + 1
137 else:
136 else:
138 # this translates to the bisect hi = mid
137 # this translates to the bisect hi = mid
139 hi = start
138 hi = start
140 end = advance(lo, '\0')
139 end = advance(lo, '\0')
141 found = m[lo:end]
140 found = m[lo:end]
142 if s == found:
141 if s == found:
143 # we know that after the null there are 40 bytes of sha1
142 # we know that after the null there are 40 bytes of sha1
144 end = advance(end + 40, '\n')
143 end = advance(end + 40, '\n')
145 return (lo, end + 1)
144 return (lo, end + 1)
146 else:
145 else:
147 return (lo, lo)
146 return (lo, lo)
148
147
149 def find(self, node, f):
148 def find(self, node, f):
150 '''look up entry for a single file efficiently.
149 '''look up entry for a single file efficiently.
151 return (node, flags) pair if found, (None, None) if not.'''
150 return (node, flags) pair if found, (None, None) if not.'''
152 if node in self._mancache:
151 if node in self._mancache:
153 mapping = self._mancache[node][0]
152 mapping = self._mancache[node][0]
154 return mapping.get(f), mapping.flags(f)
153 return mapping.get(f), mapping.flags(f)
155 text = self.revision(node)
154 text = self.revision(node)
156 start, end = self._search(text, f)
155 start, end = self._search(text, f)
157 if start == end:
156 if start == end:
158 return None, None
157 return None, None
159 l = text[start:end]
158 l = text[start:end]
160 f, n = l.split('\0')
159 f, n = l.split('\0')
161 return revlog.bin(n[:40]), n[40:-1]
160 return revlog.bin(n[:40]), n[40:-1]
162
161
163 def add(self, map, transaction, link, p1=None, p2=None,
162 def add(self, map, transaction, link, p1=None, p2=None,
164 changed=None):
163 changed=None):
165 # if we're using the cache, make sure it is valid and
164 # if we're using the cache, make sure it is valid and
166 # parented by the same node we're diffing against
165 # parented by the same node we're diffing against
167 if not (changed and p1 and (p1 in self._mancache)):
166 if not (changed and p1 and (p1 in self._mancache)):
168 files = sorted(map)
167 files = sorted(map)
169 _checkforbidden(files)
168 _checkforbidden(files)
170
169
171 # if this is changed to support newlines in filenames,
170 # if this is changed to support newlines in filenames,
172 # be sure to check the templates/ dir again (especially *-raw.tmpl)
171 # be sure to check the templates/ dir again (especially *-raw.tmpl)
173 hex, flags = revlog.hex, map.flags
172 hex, flags = revlog.hex, map.flags
174 text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f))
173 text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f))
175 for f in files)
174 for f in files)
176 arraytext = array.array('c', text)
175 arraytext = array.array('c', text)
177 cachedelta = None
176 cachedelta = None
178 else:
177 else:
179 added, removed = changed
178 added, removed = changed
180 addlist = self._mancache[p1][1]
179 addlist = self._mancache[p1][1]
181
180
182 _checkforbidden(added)
181 _checkforbidden(added)
183 # combine the changed lists into one list for sorting
182 # combine the changed lists into one list for sorting
184 work = [(x, False) for x in added]
183 work = [(x, False) for x in added]
185 work.extend((x, True) for x in removed)
184 work.extend((x, True) for x in removed)
186 # this could use heapq.merge() (from Python 2.6+) or equivalent
185 # this could use heapq.merge() (from Python 2.6+) or equivalent
187 # since the lists are already sorted
186 # since the lists are already sorted
188 work.sort()
187 work.sort()
189
188
190 delta = []
189 delta = []
191 dstart = None
190 dstart = None
192 dend = None
191 dend = None
193 dline = [""]
192 dline = [""]
194 start = 0
193 start = 0
195 # zero copy representation of addlist as a buffer
194 # zero copy representation of addlist as a buffer
196 addbuf = util.buffer(addlist)
195 addbuf = util.buffer(addlist)
197
196
198 # start with a readonly loop that finds the offset of
197 # start with a readonly loop that finds the offset of
199 # each line and creates the deltas
198 # each line and creates the deltas
200 for f, todelete in work:
199 for f, todelete in work:
201 # bs will either be the index of the item or the insert point
200 # bs will either be the index of the item or the insert point
202 start, end = self._search(addbuf, f, start)
201 start, end = self._search(addbuf, f, start)
203 if not todelete:
202 if not todelete:
204 l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
203 l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
205 else:
204 else:
206 if start == end:
205 if start == end:
207 # item we want to delete was not found, error out
206 # item we want to delete was not found, error out
208 raise AssertionError(
207 raise AssertionError(
209 _("failed to remove %s from manifest") % f)
208 _("failed to remove %s from manifest") % f)
210 l = ""
209 l = ""
211 if dstart is not None and dstart <= start and dend >= start:
210 if dstart is not None and dstart <= start and dend >= start:
212 if dend < end:
211 if dend < end:
213 dend = end
212 dend = end
214 if l:
213 if l:
215 dline.append(l)
214 dline.append(l)
216 else:
215 else:
217 if dstart is not None:
216 if dstart is not None:
218 delta.append([dstart, dend, "".join(dline)])
217 delta.append([dstart, dend, "".join(dline)])
219 dstart = start
218 dstart = start
220 dend = end
219 dend = end
221 dline = [l]
220 dline = [l]
222
221
223 if dstart is not None:
222 if dstart is not None:
224 delta.append([dstart, dend, "".join(dline)])
223 delta.append([dstart, dend, "".join(dline)])
225 # apply the delta to the addlist, and get a delta for addrevision
224 # apply the delta to the addlist, and get a delta for addrevision
226 deltatext, addlist = _addlistdelta(addlist, delta)
225 deltatext, addlist = _addlistdelta(addlist, delta)
227 cachedelta = (self.rev(p1), deltatext)
226 cachedelta = (self.rev(p1), deltatext)
228 arraytext = addlist
227 arraytext = addlist
229 text = util.buffer(arraytext)
228 text = util.buffer(arraytext)
230
229
231 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
230 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
232 self._mancache[n] = (map, arraytext)
231 self._mancache[n] = (map, arraytext)
233
232
234 return n
233 return n
General Comments 0
You need to be logged in to leave comments. Login now