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