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