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