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