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