##// END OF EJS Templates
manifest: disallow setting the node id of an entry to None...
Augie Fackler -
r23594:6f53629a stable
parent child Browse files
Show More
@@ -1,268 +1,271 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
9 import mdiff, parsers, error, revlog, util
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 __setitem__(self, k, v):
21 assert v is not None
22 dict.__setitem__(self, k, v)
20 def flags(self, f):
23 def flags(self, f):
21 return self._flags.get(f, "")
24 return self._flags.get(f, "")
22 def withflags(self):
25 def withflags(self):
23 return set(self._flags.keys())
26 return set(self._flags.keys())
24 def setflag(self, f, flags):
27 def setflag(self, f, flags):
25 """Set the flags (symlink, executable) for path f."""
28 """Set the flags (symlink, executable) for path f."""
26 self._flags[f] = flags
29 self._flags[f] = flags
27 def copy(self):
30 def copy(self):
28 return manifestdict(self, dict.copy(self._flags))
31 return manifestdict(self, dict.copy(self._flags))
29 def intersectfiles(self, files):
32 def intersectfiles(self, files):
30 '''make a new manifestdict with the intersection of self with files
33 '''make a new manifestdict with the intersection of self with files
31
34
32 The algorithm assumes that files is much smaller than self.'''
35 The algorithm assumes that files is much smaller than self.'''
33 ret = manifestdict()
36 ret = manifestdict()
34 for fn in files:
37 for fn in files:
35 if fn in self:
38 if fn in self:
36 ret[fn] = self[fn]
39 ret[fn] = self[fn]
37 flags = self._flags.get(fn, None)
40 flags = self._flags.get(fn, None)
38 if flags:
41 if flags:
39 ret._flags[fn] = flags
42 ret._flags[fn] = flags
40 return ret
43 return ret
41
44
42 def diff(self, m2):
45 def diff(self, m2):
43 '''Finds changes between the current manifest and m2. The result is
46 '''Finds changes between the current manifest and m2. The result is
44 returned as a dict with filename as key and values of the form
47 returned as a dict with filename as key and values of the form
45 ((n1,fl1),(n2,fl2)), where n1/n2 is the nodeid in the current/other
48 ((n1,fl1),(n2,fl2)), where n1/n2 is the nodeid in the current/other
46 manifest and fl1/fl2 is the flag in the current/other manifest. Where
49 manifest and fl1/fl2 is the flag in the current/other manifest. Where
47 the file does not exist, the nodeid will be None and the flags will be
50 the file does not exist, the nodeid will be None and the flags will be
48 the empty string.'''
51 the empty string.'''
49 diff = {}
52 diff = {}
50
53
51 for fn, n1 in self.iteritems():
54 for fn, n1 in self.iteritems():
52 fl1 = self._flags.get(fn, '')
55 fl1 = self._flags.get(fn, '')
53 n2 = m2.get(fn, None)
56 n2 = m2.get(fn, None)
54 fl2 = m2._flags.get(fn, '')
57 fl2 = m2._flags.get(fn, '')
55 if n2 is None:
58 if n2 is None:
56 fl2 = ''
59 fl2 = ''
57 if n1 != n2 or fl1 != fl2:
60 if n1 != n2 or fl1 != fl2:
58 diff[fn] = ((n1, fl1), (n2, fl2))
61 diff[fn] = ((n1, fl1), (n2, fl2))
59
62
60 for fn, n2 in m2.iteritems():
63 for fn, n2 in m2.iteritems():
61 if fn not in self:
64 if fn not in self:
62 fl2 = m2._flags.get(fn, '')
65 fl2 = m2._flags.get(fn, '')
63 diff[fn] = ((None, ''), (n2, fl2))
66 diff[fn] = ((None, ''), (n2, fl2))
64
67
65 return diff
68 return diff
66
69
67 def text(self):
70 def text(self):
68 """Get the full data of this manifest as a bytestring."""
71 """Get the full data of this manifest as a bytestring."""
69 fl = sorted(self)
72 fl = sorted(self)
70 _checkforbidden(fl)
73 _checkforbidden(fl)
71
74
72 hex, flags = revlog.hex, self.flags
75 hex, flags = revlog.hex, self.flags
73 # if this is changed to support newlines in filenames,
76 # if this is changed to support newlines in filenames,
74 # be sure to check the templates/ dir again (especially *-raw.tmpl)
77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
75 return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
78 return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
76
79
77 def fastdelta(self, base, changes):
80 def fastdelta(self, base, changes):
78 """Given a base manifest text as an array.array and a list of changes
81 """Given a base manifest text as an array.array and a list of changes
79 relative to that text, compute a delta that can be used by revlog.
82 relative to that text, compute a delta that can be used by revlog.
80 """
83 """
81 delta = []
84 delta = []
82 dstart = None
85 dstart = None
83 dend = None
86 dend = None
84 dline = [""]
87 dline = [""]
85 start = 0
88 start = 0
86 # zero copy representation of base as a buffer
89 # zero copy representation of base as a buffer
87 addbuf = util.buffer(base)
90 addbuf = util.buffer(base)
88
91
89 # start with a readonly loop that finds the offset of
92 # start with a readonly loop that finds the offset of
90 # each line and creates the deltas
93 # each line and creates the deltas
91 for f, todelete in changes:
94 for f, todelete in changes:
92 # bs will either be the index of the item or the insert point
95 # bs will either be the index of the item or the insert point
93 start, end = _msearch(addbuf, f, start)
96 start, end = _msearch(addbuf, f, start)
94 if not todelete:
97 if not todelete:
95 l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
98 l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
96 else:
99 else:
97 if start == end:
100 if start == end:
98 # item we want to delete was not found, error out
101 # item we want to delete was not found, error out
99 raise AssertionError(
102 raise AssertionError(
100 _("failed to remove %s from manifest") % f)
103 _("failed to remove %s from manifest") % f)
101 l = ""
104 l = ""
102 if dstart is not None and dstart <= start and dend >= start:
105 if dstart is not None and dstart <= start and dend >= start:
103 if dend < end:
106 if dend < end:
104 dend = end
107 dend = end
105 if l:
108 if l:
106 dline.append(l)
109 dline.append(l)
107 else:
110 else:
108 if dstart is not None:
111 if dstart is not None:
109 delta.append([dstart, dend, "".join(dline)])
112 delta.append([dstart, dend, "".join(dline)])
110 dstart = start
113 dstart = start
111 dend = end
114 dend = end
112 dline = [l]
115 dline = [l]
113
116
114 if dstart is not None:
117 if dstart is not None:
115 delta.append([dstart, dend, "".join(dline)])
118 delta.append([dstart, dend, "".join(dline)])
116 # apply the delta to the base, and get a delta for addrevision
119 # apply the delta to the base, and get a delta for addrevision
117 deltatext, arraytext = _addlistdelta(base, delta)
120 deltatext, arraytext = _addlistdelta(base, delta)
118 return arraytext, deltatext
121 return arraytext, deltatext
119
122
120 def _msearch(m, s, lo=0, hi=None):
123 def _msearch(m, s, lo=0, hi=None):
121 '''return a tuple (start, end) that says where to find s within m.
124 '''return a tuple (start, end) that says where to find s within m.
122
125
123 If the string is found m[start:end] are the line containing
126 If the string is found m[start:end] are the line containing
124 that string. If start == end the string was not found and
127 that string. If start == end the string was not found and
125 they indicate the proper sorted insertion point.
128 they indicate the proper sorted insertion point.
126
129
127 m should be a buffer or a string
130 m should be a buffer or a string
128 s is a string'''
131 s is a string'''
129 def advance(i, c):
132 def advance(i, c):
130 while i < lenm and m[i] != c:
133 while i < lenm and m[i] != c:
131 i += 1
134 i += 1
132 return i
135 return i
133 if not s:
136 if not s:
134 return (lo, lo)
137 return (lo, lo)
135 lenm = len(m)
138 lenm = len(m)
136 if not hi:
139 if not hi:
137 hi = lenm
140 hi = lenm
138 while lo < hi:
141 while lo < hi:
139 mid = (lo + hi) // 2
142 mid = (lo + hi) // 2
140 start = mid
143 start = mid
141 while start > 0 and m[start - 1] != '\n':
144 while start > 0 and m[start - 1] != '\n':
142 start -= 1
145 start -= 1
143 end = advance(start, '\0')
146 end = advance(start, '\0')
144 if m[start:end] < s:
147 if m[start:end] < s:
145 # we know that after the null there are 40 bytes of sha1
148 # we know that after the null there are 40 bytes of sha1
146 # this translates to the bisect lo = mid + 1
149 # this translates to the bisect lo = mid + 1
147 lo = advance(end + 40, '\n') + 1
150 lo = advance(end + 40, '\n') + 1
148 else:
151 else:
149 # this translates to the bisect hi = mid
152 # this translates to the bisect hi = mid
150 hi = start
153 hi = start
151 end = advance(lo, '\0')
154 end = advance(lo, '\0')
152 found = m[lo:end]
155 found = m[lo:end]
153 if s == found:
156 if s == found:
154 # we know that after the null there are 40 bytes of sha1
157 # we know that after the null there are 40 bytes of sha1
155 end = advance(end + 40, '\n')
158 end = advance(end + 40, '\n')
156 return (lo, end + 1)
159 return (lo, end + 1)
157 else:
160 else:
158 return (lo, lo)
161 return (lo, lo)
159
162
160 def _checkforbidden(l):
163 def _checkforbidden(l):
161 """Check filenames for illegal characters."""
164 """Check filenames for illegal characters."""
162 for f in l:
165 for f in l:
163 if '\n' in f or '\r' in f:
166 if '\n' in f or '\r' in f:
164 raise error.RevlogError(
167 raise error.RevlogError(
165 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
168 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
166
169
167
170
168 # apply the changes collected during the bisect loop to our addlist
171 # apply the changes collected during the bisect loop to our addlist
169 # return a delta suitable for addrevision
172 # return a delta suitable for addrevision
170 def _addlistdelta(addlist, x):
173 def _addlistdelta(addlist, x):
171 # for large addlist arrays, building a new array is cheaper
174 # for large addlist arrays, building a new array is cheaper
172 # than repeatedly modifying the existing one
175 # than repeatedly modifying the existing one
173 currentposition = 0
176 currentposition = 0
174 newaddlist = array.array('c')
177 newaddlist = array.array('c')
175
178
176 for start, end, content in x:
179 for start, end, content in x:
177 newaddlist += addlist[currentposition:start]
180 newaddlist += addlist[currentposition:start]
178 if content:
181 if content:
179 newaddlist += array.array('c', content)
182 newaddlist += array.array('c', content)
180
183
181 currentposition = end
184 currentposition = end
182
185
183 newaddlist += addlist[currentposition:]
186 newaddlist += addlist[currentposition:]
184
187
185 deltatext = "".join(struct.pack(">lll", start, end, len(content))
188 deltatext = "".join(struct.pack(">lll", start, end, len(content))
186 + content for start, end, content in x)
189 + content for start, end, content in x)
187 return deltatext, newaddlist
190 return deltatext, newaddlist
188
191
189 def _parse(lines):
192 def _parse(lines):
190 mfdict = manifestdict()
193 mfdict = manifestdict()
191 parsers.parse_manifest(mfdict, mfdict._flags, lines)
194 parsers.parse_manifest(mfdict, mfdict._flags, lines)
192 return mfdict
195 return mfdict
193
196
194 class manifest(revlog.revlog):
197 class manifest(revlog.revlog):
195 def __init__(self, opener):
198 def __init__(self, opener):
196 # we expect to deal with not more than four revs at a time,
199 # we expect to deal with not more than four revs at a time,
197 # during a commit --amend
200 # during a commit --amend
198 self._mancache = util.lrucachedict(4)
201 self._mancache = util.lrucachedict(4)
199 revlog.revlog.__init__(self, opener, "00manifest.i")
202 revlog.revlog.__init__(self, opener, "00manifest.i")
200
203
201 def readdelta(self, node):
204 def readdelta(self, node):
202 r = self.rev(node)
205 r = self.rev(node)
203 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
206 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
204
207
205 def readfast(self, node):
208 def readfast(self, node):
206 '''use the faster of readdelta or read'''
209 '''use the faster of readdelta or read'''
207 r = self.rev(node)
210 r = self.rev(node)
208 deltaparent = self.deltaparent(r)
211 deltaparent = self.deltaparent(r)
209 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
212 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
210 return self.readdelta(node)
213 return self.readdelta(node)
211 return self.read(node)
214 return self.read(node)
212
215
213 def read(self, node):
216 def read(self, node):
214 if node == revlog.nullid:
217 if node == revlog.nullid:
215 return manifestdict() # don't upset local cache
218 return manifestdict() # don't upset local cache
216 if node in self._mancache:
219 if node in self._mancache:
217 return self._mancache[node][0]
220 return self._mancache[node][0]
218 text = self.revision(node)
221 text = self.revision(node)
219 arraytext = array.array('c', text)
222 arraytext = array.array('c', text)
220 mapping = _parse(text)
223 mapping = _parse(text)
221 self._mancache[node] = (mapping, arraytext)
224 self._mancache[node] = (mapping, arraytext)
222 return mapping
225 return mapping
223
226
224 def find(self, node, f):
227 def find(self, node, f):
225 '''look up entry for a single file efficiently.
228 '''look up entry for a single file efficiently.
226 return (node, flags) pair if found, (None, None) if not.'''
229 return (node, flags) pair if found, (None, None) if not.'''
227 if node in self._mancache:
230 if node in self._mancache:
228 mapping = self._mancache[node][0]
231 mapping = self._mancache[node][0]
229 return mapping.get(f), mapping.flags(f)
232 return mapping.get(f), mapping.flags(f)
230 text = self.revision(node)
233 text = self.revision(node)
231 start, end = _msearch(text, f)
234 start, end = _msearch(text, f)
232 if start == end:
235 if start == end:
233 return None, None
236 return None, None
234 l = text[start:end]
237 l = text[start:end]
235 f, n = l.split('\0')
238 f, n = l.split('\0')
236 return revlog.bin(n[:40]), n[40:-1]
239 return revlog.bin(n[:40]), n[40:-1]
237
240
238 def add(self, map, transaction, link, p1, p2, added, removed):
241 def add(self, map, transaction, link, p1, p2, added, removed):
239 if p1 in self._mancache:
242 if p1 in self._mancache:
240 # If our first parent is in the manifest cache, we can
243 # If our first parent is in the manifest cache, we can
241 # compute a delta here using properties we know about the
244 # compute a delta here using properties we know about the
242 # manifest up-front, which may save time later for the
245 # manifest up-front, which may save time later for the
243 # revlog layer.
246 # revlog layer.
244
247
245 _checkforbidden(added)
248 _checkforbidden(added)
246 # combine the changed lists into one list for sorting
249 # combine the changed lists into one list for sorting
247 work = [(x, False) for x in added]
250 work = [(x, False) for x in added]
248 work.extend((x, True) for x in removed)
251 work.extend((x, True) for x in removed)
249 # this could use heapq.merge() (from Python 2.6+) or equivalent
252 # this could use heapq.merge() (from Python 2.6+) or equivalent
250 # since the lists are already sorted
253 # since the lists are already sorted
251 work.sort()
254 work.sort()
252
255
253 arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work)
256 arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work)
254 cachedelta = self.rev(p1), deltatext
257 cachedelta = self.rev(p1), deltatext
255 text = util.buffer(arraytext)
258 text = util.buffer(arraytext)
256 else:
259 else:
257 # The first parent manifest isn't already loaded, so we'll
260 # The first parent manifest isn't already loaded, so we'll
258 # just encode a fulltext of the manifest and pass that
261 # just encode a fulltext of the manifest and pass that
259 # through to the revlog layer, and let it handle the delta
262 # through to the revlog layer, and let it handle the delta
260 # process.
263 # process.
261 text = map.text()
264 text = map.text()
262 arraytext = array.array('c', text)
265 arraytext = array.array('c', text)
263 cachedelta = None
266 cachedelta = None
264
267
265 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
268 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
266 self._mancache[n] = (map, arraytext)
269 self._mancache[n] = (map, arraytext)
267
270
268 return n
271 return n
General Comments 0
You need to be logged in to leave comments. Login now