##// END OF EJS Templates
manifest: get rid of manifest.readshallowfast...
Durham Goode -
r30294:bce79dfc default
parent child Browse files
Show More
@@ -1,1043 +1,1044 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 branchmap,
23 branchmap,
24 dagutil,
24 dagutil,
25 discovery,
25 discovery,
26 error,
26 error,
27 mdiff,
27 mdiff,
28 phases,
28 phases,
29 util,
29 util,
30 )
30 )
31
31
32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
35
35
36 def readexactly(stream, n):
36 def readexactly(stream, n):
37 '''read n bytes from stream.read and abort if less was available'''
37 '''read n bytes from stream.read and abort if less was available'''
38 s = stream.read(n)
38 s = stream.read(n)
39 if len(s) < n:
39 if len(s) < n:
40 raise error.Abort(_("stream ended unexpectedly"
40 raise error.Abort(_("stream ended unexpectedly"
41 " (got %d bytes, expected %d)")
41 " (got %d bytes, expected %d)")
42 % (len(s), n))
42 % (len(s), n))
43 return s
43 return s
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(">l", d)[0]
48 l = struct.unpack(">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise error.Abort(_("invalid chunk length %d") % l)
51 raise error.Abort(_("invalid chunk length %d") % l)
52 return ""
52 return ""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55 def chunkheader(length):
55 def chunkheader(length):
56 """return a changegroup chunk header (string)"""
56 """return a changegroup chunk header (string)"""
57 return struct.pack(">l", length + 4)
57 return struct.pack(">l", length + 4)
58
58
59 def closechunk():
59 def closechunk():
60 """return a changegroup chunk header (string) for a zero-length chunk"""
60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 return struct.pack(">l", 0)
61 return struct.pack(">l", 0)
62
62
63 def combineresults(results):
63 def combineresults(results):
64 """logic to combine 0 or more addchangegroup results into one"""
64 """logic to combine 0 or more addchangegroup results into one"""
65 changedheads = 0
65 changedheads = 0
66 result = 1
66 result = 1
67 for ret in results:
67 for ret in results:
68 # If any changegroup result is 0, return 0
68 # If any changegroup result is 0, return 0
69 if ret == 0:
69 if ret == 0:
70 result = 0
70 result = 0
71 break
71 break
72 if ret < -1:
72 if ret < -1:
73 changedheads += ret + 1
73 changedheads += ret + 1
74 elif ret > 1:
74 elif ret > 1:
75 changedheads += ret - 1
75 changedheads += ret - 1
76 if changedheads > 0:
76 if changedheads > 0:
77 result = 1 + changedheads
77 result = 1 + changedheads
78 elif changedheads < 0:
78 elif changedheads < 0:
79 result = -1 + changedheads
79 result = -1 + changedheads
80 return result
80 return result
81
81
82 def writechunks(ui, chunks, filename, vfs=None):
82 def writechunks(ui, chunks, filename, vfs=None):
83 """Write chunks to a file and return its filename.
83 """Write chunks to a file and return its filename.
84
84
85 The stream is assumed to be a bundle file.
85 The stream is assumed to be a bundle file.
86 Existing files will not be overwritten.
86 Existing files will not be overwritten.
87 If no filename is specified, a temporary file is created.
87 If no filename is specified, a temporary file is created.
88 """
88 """
89 fh = None
89 fh = None
90 cleanup = None
90 cleanup = None
91 try:
91 try:
92 if filename:
92 if filename:
93 if vfs:
93 if vfs:
94 fh = vfs.open(filename, "wb")
94 fh = vfs.open(filename, "wb")
95 else:
95 else:
96 # Increase default buffer size because default is usually
96 # Increase default buffer size because default is usually
97 # small (4k is common on Linux).
97 # small (4k is common on Linux).
98 fh = open(filename, "wb", 131072)
98 fh = open(filename, "wb", 131072)
99 else:
99 else:
100 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
100 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
101 fh = os.fdopen(fd, "wb")
101 fh = os.fdopen(fd, "wb")
102 cleanup = filename
102 cleanup = filename
103 for c in chunks:
103 for c in chunks:
104 fh.write(c)
104 fh.write(c)
105 cleanup = None
105 cleanup = None
106 return filename
106 return filename
107 finally:
107 finally:
108 if fh is not None:
108 if fh is not None:
109 fh.close()
109 fh.close()
110 if cleanup is not None:
110 if cleanup is not None:
111 if filename and vfs:
111 if filename and vfs:
112 vfs.unlink(cleanup)
112 vfs.unlink(cleanup)
113 else:
113 else:
114 os.unlink(cleanup)
114 os.unlink(cleanup)
115
115
116 class cg1unpacker(object):
116 class cg1unpacker(object):
117 """Unpacker for cg1 changegroup streams.
117 """Unpacker for cg1 changegroup streams.
118
118
119 A changegroup unpacker handles the framing of the revision data in
119 A changegroup unpacker handles the framing of the revision data in
120 the wire format. Most consumers will want to use the apply()
120 the wire format. Most consumers will want to use the apply()
121 method to add the changes from the changegroup to a repository.
121 method to add the changes from the changegroup to a repository.
122
122
123 If you're forwarding a changegroup unmodified to another consumer,
123 If you're forwarding a changegroup unmodified to another consumer,
124 use getchunks(), which returns an iterator of changegroup
124 use getchunks(), which returns an iterator of changegroup
125 chunks. This is mostly useful for cases where you need to know the
125 chunks. This is mostly useful for cases where you need to know the
126 data stream has ended by observing the end of the changegroup.
126 data stream has ended by observing the end of the changegroup.
127
127
128 deltachunk() is useful only if you're applying delta data. Most
128 deltachunk() is useful only if you're applying delta data. Most
129 consumers should prefer apply() instead.
129 consumers should prefer apply() instead.
130
130
131 A few other public methods exist. Those are used only for
131 A few other public methods exist. Those are used only for
132 bundlerepo and some debug commands - their use is discouraged.
132 bundlerepo and some debug commands - their use is discouraged.
133 """
133 """
134 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
134 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
135 deltaheadersize = struct.calcsize(deltaheader)
135 deltaheadersize = struct.calcsize(deltaheader)
136 version = '01'
136 version = '01'
137 _grouplistcount = 1 # One list of files after the manifests
137 _grouplistcount = 1 # One list of files after the manifests
138
138
139 def __init__(self, fh, alg, extras=None):
139 def __init__(self, fh, alg, extras=None):
140 if alg == 'UN':
140 if alg == 'UN':
141 alg = None # get more modern without breaking too much
141 alg = None # get more modern without breaking too much
142 if not alg in util.decompressors:
142 if not alg in util.decompressors:
143 raise error.Abort(_('unknown stream compression type: %s')
143 raise error.Abort(_('unknown stream compression type: %s')
144 % alg)
144 % alg)
145 if alg == 'BZ':
145 if alg == 'BZ':
146 alg = '_truncatedBZ'
146 alg = '_truncatedBZ'
147 self._stream = util.decompressors[alg](fh)
147 self._stream = util.decompressors[alg](fh)
148 self._type = alg
148 self._type = alg
149 self.extras = extras or {}
149 self.extras = extras or {}
150 self.callback = None
150 self.callback = None
151
151
152 # These methods (compressed, read, seek, tell) all appear to only
152 # These methods (compressed, read, seek, tell) all appear to only
153 # be used by bundlerepo, but it's a little hard to tell.
153 # be used by bundlerepo, but it's a little hard to tell.
154 def compressed(self):
154 def compressed(self):
155 return self._type is not None
155 return self._type is not None
156 def read(self, l):
156 def read(self, l):
157 return self._stream.read(l)
157 return self._stream.read(l)
158 def seek(self, pos):
158 def seek(self, pos):
159 return self._stream.seek(pos)
159 return self._stream.seek(pos)
160 def tell(self):
160 def tell(self):
161 return self._stream.tell()
161 return self._stream.tell()
162 def close(self):
162 def close(self):
163 return self._stream.close()
163 return self._stream.close()
164
164
165 def _chunklength(self):
165 def _chunklength(self):
166 d = readexactly(self._stream, 4)
166 d = readexactly(self._stream, 4)
167 l = struct.unpack(">l", d)[0]
167 l = struct.unpack(">l", d)[0]
168 if l <= 4:
168 if l <= 4:
169 if l:
169 if l:
170 raise error.Abort(_("invalid chunk length %d") % l)
170 raise error.Abort(_("invalid chunk length %d") % l)
171 return 0
171 return 0
172 if self.callback:
172 if self.callback:
173 self.callback()
173 self.callback()
174 return l - 4
174 return l - 4
175
175
176 def changelogheader(self):
176 def changelogheader(self):
177 """v10 does not have a changelog header chunk"""
177 """v10 does not have a changelog header chunk"""
178 return {}
178 return {}
179
179
180 def manifestheader(self):
180 def manifestheader(self):
181 """v10 does not have a manifest header chunk"""
181 """v10 does not have a manifest header chunk"""
182 return {}
182 return {}
183
183
184 def filelogheader(self):
184 def filelogheader(self):
185 """return the header of the filelogs chunk, v10 only has the filename"""
185 """return the header of the filelogs chunk, v10 only has the filename"""
186 l = self._chunklength()
186 l = self._chunklength()
187 if not l:
187 if not l:
188 return {}
188 return {}
189 fname = readexactly(self._stream, l)
189 fname = readexactly(self._stream, l)
190 return {'filename': fname}
190 return {'filename': fname}
191
191
192 def _deltaheader(self, headertuple, prevnode):
192 def _deltaheader(self, headertuple, prevnode):
193 node, p1, p2, cs = headertuple
193 node, p1, p2, cs = headertuple
194 if prevnode is None:
194 if prevnode is None:
195 deltabase = p1
195 deltabase = p1
196 else:
196 else:
197 deltabase = prevnode
197 deltabase = prevnode
198 flags = 0
198 flags = 0
199 return node, p1, p2, deltabase, cs, flags
199 return node, p1, p2, deltabase, cs, flags
200
200
201 def deltachunk(self, prevnode):
201 def deltachunk(self, prevnode):
202 l = self._chunklength()
202 l = self._chunklength()
203 if not l:
203 if not l:
204 return {}
204 return {}
205 headerdata = readexactly(self._stream, self.deltaheadersize)
205 headerdata = readexactly(self._stream, self.deltaheadersize)
206 header = struct.unpack(self.deltaheader, headerdata)
206 header = struct.unpack(self.deltaheader, headerdata)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
208 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
208 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
209 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
209 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
210 'deltabase': deltabase, 'delta': delta, 'flags': flags}
210 'deltabase': deltabase, 'delta': delta, 'flags': flags}
211
211
212 def getchunks(self):
212 def getchunks(self):
213 """returns all the chunks contains in the bundle
213 """returns all the chunks contains in the bundle
214
214
215 Used when you need to forward the binary stream to a file or another
215 Used when you need to forward the binary stream to a file or another
216 network API. To do so, it parse the changegroup data, otherwise it will
216 network API. To do so, it parse the changegroup data, otherwise it will
217 block in case of sshrepo because it don't know the end of the stream.
217 block in case of sshrepo because it don't know the end of the stream.
218 """
218 """
219 # an empty chunkgroup is the end of the changegroup
219 # an empty chunkgroup is the end of the changegroup
220 # a changegroup has at least 2 chunkgroups (changelog and manifest).
220 # a changegroup has at least 2 chunkgroups (changelog and manifest).
221 # after that, changegroup versions 1 and 2 have a series of groups
221 # after that, changegroup versions 1 and 2 have a series of groups
222 # with one group per file. changegroup 3 has a series of directory
222 # with one group per file. changegroup 3 has a series of directory
223 # manifests before the files.
223 # manifests before the files.
224 count = 0
224 count = 0
225 emptycount = 0
225 emptycount = 0
226 while emptycount < self._grouplistcount:
226 while emptycount < self._grouplistcount:
227 empty = True
227 empty = True
228 count += 1
228 count += 1
229 while True:
229 while True:
230 chunk = getchunk(self)
230 chunk = getchunk(self)
231 if not chunk:
231 if not chunk:
232 if empty and count > 2:
232 if empty and count > 2:
233 emptycount += 1
233 emptycount += 1
234 break
234 break
235 empty = False
235 empty = False
236 yield chunkheader(len(chunk))
236 yield chunkheader(len(chunk))
237 pos = 0
237 pos = 0
238 while pos < len(chunk):
238 while pos < len(chunk):
239 next = pos + 2**20
239 next = pos + 2**20
240 yield chunk[pos:next]
240 yield chunk[pos:next]
241 pos = next
241 pos = next
242 yield closechunk()
242 yield closechunk()
243
243
244 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
244 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
245 # We know that we'll never have more manifests than we had
245 # We know that we'll never have more manifests than we had
246 # changesets.
246 # changesets.
247 self.callback = prog(_('manifests'), numchanges)
247 self.callback = prog(_('manifests'), numchanges)
248 # no need to check for empty manifest group here:
248 # no need to check for empty manifest group here:
249 # if the result of the merge of 1 and 2 is the same in 3 and 4,
249 # if the result of the merge of 1 and 2 is the same in 3 and 4,
250 # no new manifest will be created and the manifest group will
250 # no new manifest will be created and the manifest group will
251 # be empty during the pull
251 # be empty during the pull
252 self.manifestheader()
252 self.manifestheader()
253 repo.manifest.addgroup(self, revmap, trp)
253 repo.manifest.addgroup(self, revmap, trp)
254 repo.ui.progress(_('manifests'), None)
254 repo.ui.progress(_('manifests'), None)
255 self.callback = None
255 self.callback = None
256
256
257 def apply(self, repo, srctype, url, emptyok=False,
257 def apply(self, repo, srctype, url, emptyok=False,
258 targetphase=phases.draft, expectedtotal=None):
258 targetphase=phases.draft, expectedtotal=None):
259 """Add the changegroup returned by source.read() to this repo.
259 """Add the changegroup returned by source.read() to this repo.
260 srctype is a string like 'push', 'pull', or 'unbundle'. url is
260 srctype is a string like 'push', 'pull', or 'unbundle'. url is
261 the URL of the repo where this changegroup is coming from.
261 the URL of the repo where this changegroup is coming from.
262
262
263 Return an integer summarizing the change to this repo:
263 Return an integer summarizing the change to this repo:
264 - nothing changed or no source: 0
264 - nothing changed or no source: 0
265 - more heads than before: 1+added heads (2..n)
265 - more heads than before: 1+added heads (2..n)
266 - fewer heads than before: -1-removed heads (-2..-n)
266 - fewer heads than before: -1-removed heads (-2..-n)
267 - number of heads stays the same: 1
267 - number of heads stays the same: 1
268 """
268 """
269 repo = repo.unfiltered()
269 repo = repo.unfiltered()
270 def csmap(x):
270 def csmap(x):
271 repo.ui.debug("add changeset %s\n" % short(x))
271 repo.ui.debug("add changeset %s\n" % short(x))
272 return len(cl)
272 return len(cl)
273
273
274 def revmap(x):
274 def revmap(x):
275 return cl.rev(x)
275 return cl.rev(x)
276
276
277 changesets = files = revisions = 0
277 changesets = files = revisions = 0
278
278
279 try:
279 try:
280 with repo.transaction("\n".join([srctype,
280 with repo.transaction("\n".join([srctype,
281 util.hidepassword(url)])) as tr:
281 util.hidepassword(url)])) as tr:
282 # The transaction could have been created before and already
282 # The transaction could have been created before and already
283 # carries source information. In this case we use the top
283 # carries source information. In this case we use the top
284 # level data. We overwrite the argument because we need to use
284 # level data. We overwrite the argument because we need to use
285 # the top level value (if they exist) in this function.
285 # the top level value (if they exist) in this function.
286 srctype = tr.hookargs.setdefault('source', srctype)
286 srctype = tr.hookargs.setdefault('source', srctype)
287 url = tr.hookargs.setdefault('url', url)
287 url = tr.hookargs.setdefault('url', url)
288 repo.hook('prechangegroup', throw=True, **tr.hookargs)
288 repo.hook('prechangegroup', throw=True, **tr.hookargs)
289
289
290 # write changelog data to temp files so concurrent readers
290 # write changelog data to temp files so concurrent readers
291 # will not see an inconsistent view
291 # will not see an inconsistent view
292 cl = repo.changelog
292 cl = repo.changelog
293 cl.delayupdate(tr)
293 cl.delayupdate(tr)
294 oldheads = cl.heads()
294 oldheads = cl.heads()
295
295
296 trp = weakref.proxy(tr)
296 trp = weakref.proxy(tr)
297 # pull off the changeset group
297 # pull off the changeset group
298 repo.ui.status(_("adding changesets\n"))
298 repo.ui.status(_("adding changesets\n"))
299 clstart = len(cl)
299 clstart = len(cl)
300 class prog(object):
300 class prog(object):
301 def __init__(self, step, total):
301 def __init__(self, step, total):
302 self._step = step
302 self._step = step
303 self._total = total
303 self._total = total
304 self._count = 1
304 self._count = 1
305 def __call__(self):
305 def __call__(self):
306 repo.ui.progress(self._step, self._count,
306 repo.ui.progress(self._step, self._count,
307 unit=_('chunks'), total=self._total)
307 unit=_('chunks'), total=self._total)
308 self._count += 1
308 self._count += 1
309 self.callback = prog(_('changesets'), expectedtotal)
309 self.callback = prog(_('changesets'), expectedtotal)
310
310
311 efiles = set()
311 efiles = set()
312 def onchangelog(cl, node):
312 def onchangelog(cl, node):
313 efiles.update(cl.readfiles(node))
313 efiles.update(cl.readfiles(node))
314
314
315 self.changelogheader()
315 self.changelogheader()
316 srccontent = cl.addgroup(self, csmap, trp,
316 srccontent = cl.addgroup(self, csmap, trp,
317 addrevisioncb=onchangelog)
317 addrevisioncb=onchangelog)
318 efiles = len(efiles)
318 efiles = len(efiles)
319
319
320 if not (srccontent or emptyok):
320 if not (srccontent or emptyok):
321 raise error.Abort(_("received changelog group is empty"))
321 raise error.Abort(_("received changelog group is empty"))
322 clend = len(cl)
322 clend = len(cl)
323 changesets = clend - clstart
323 changesets = clend - clstart
324 repo.ui.progress(_('changesets'), None)
324 repo.ui.progress(_('changesets'), None)
325 self.callback = None
325 self.callback = None
326
326
327 # pull off the manifest group
327 # pull off the manifest group
328 repo.ui.status(_("adding manifests\n"))
328 repo.ui.status(_("adding manifests\n"))
329 self._unpackmanifests(repo, revmap, trp, prog, changesets)
329 self._unpackmanifests(repo, revmap, trp, prog, changesets)
330
330
331 needfiles = {}
331 needfiles = {}
332 if repo.ui.configbool('server', 'validate', default=False):
332 if repo.ui.configbool('server', 'validate', default=False):
333 cl = repo.changelog
333 cl = repo.changelog
334 ml = repo.manifestlog
334 ml = repo.manifestlog
335 # validate incoming csets have their manifests
335 # validate incoming csets have their manifests
336 for cset in xrange(clstart, clend):
336 for cset in xrange(clstart, clend):
337 mfnode = cl.changelogrevision(cset).manifest
337 mfnode = cl.changelogrevision(cset).manifest
338 mfest = ml[mfnode].readdelta()
338 mfest = ml[mfnode].readdelta()
339 # store file nodes we must see
339 # store file nodes we must see
340 for f, n in mfest.iteritems():
340 for f, n in mfest.iteritems():
341 needfiles.setdefault(f, set()).add(n)
341 needfiles.setdefault(f, set()).add(n)
342
342
343 # process the files
343 # process the files
344 repo.ui.status(_("adding file changes\n"))
344 repo.ui.status(_("adding file changes\n"))
345 newrevs, newfiles = _addchangegroupfiles(
345 newrevs, newfiles = _addchangegroupfiles(
346 repo, self, revmap, trp, efiles, needfiles)
346 repo, self, revmap, trp, efiles, needfiles)
347 revisions += newrevs
347 revisions += newrevs
348 files += newfiles
348 files += newfiles
349
349
350 dh = 0
350 dh = 0
351 if oldheads:
351 if oldheads:
352 heads = cl.heads()
352 heads = cl.heads()
353 dh = len(heads) - len(oldheads)
353 dh = len(heads) - len(oldheads)
354 for h in heads:
354 for h in heads:
355 if h not in oldheads and repo[h].closesbranch():
355 if h not in oldheads and repo[h].closesbranch():
356 dh -= 1
356 dh -= 1
357 htext = ""
357 htext = ""
358 if dh:
358 if dh:
359 htext = _(" (%+d heads)") % dh
359 htext = _(" (%+d heads)") % dh
360
360
361 repo.ui.status(_("added %d changesets"
361 repo.ui.status(_("added %d changesets"
362 " with %d changes to %d files%s\n")
362 " with %d changes to %d files%s\n")
363 % (changesets, revisions, files, htext))
363 % (changesets, revisions, files, htext))
364 repo.invalidatevolatilesets()
364 repo.invalidatevolatilesets()
365
365
366 if changesets > 0:
366 if changesets > 0:
367 if 'node' not in tr.hookargs:
367 if 'node' not in tr.hookargs:
368 tr.hookargs['node'] = hex(cl.node(clstart))
368 tr.hookargs['node'] = hex(cl.node(clstart))
369 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
369 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
370 hookargs = dict(tr.hookargs)
370 hookargs = dict(tr.hookargs)
371 else:
371 else:
372 hookargs = dict(tr.hookargs)
372 hookargs = dict(tr.hookargs)
373 hookargs['node'] = hex(cl.node(clstart))
373 hookargs['node'] = hex(cl.node(clstart))
374 hookargs['node_last'] = hex(cl.node(clend - 1))
374 hookargs['node_last'] = hex(cl.node(clend - 1))
375 repo.hook('pretxnchangegroup', throw=True, **hookargs)
375 repo.hook('pretxnchangegroup', throw=True, **hookargs)
376
376
377 added = [cl.node(r) for r in xrange(clstart, clend)]
377 added = [cl.node(r) for r in xrange(clstart, clend)]
378 publishing = repo.publishing()
378 publishing = repo.publishing()
379 if srctype in ('push', 'serve'):
379 if srctype in ('push', 'serve'):
380 # Old servers can not push the boundary themselves.
380 # Old servers can not push the boundary themselves.
381 # New servers won't push the boundary if changeset already
381 # New servers won't push the boundary if changeset already
382 # exists locally as secret
382 # exists locally as secret
383 #
383 #
384 # We should not use added here but the list of all change in
384 # We should not use added here but the list of all change in
385 # the bundle
385 # the bundle
386 if publishing:
386 if publishing:
387 phases.advanceboundary(repo, tr, phases.public,
387 phases.advanceboundary(repo, tr, phases.public,
388 srccontent)
388 srccontent)
389 else:
389 else:
390 # Those changesets have been pushed from the
390 # Those changesets have been pushed from the
391 # outside, their phases are going to be pushed
391 # outside, their phases are going to be pushed
392 # alongside. Therefor `targetphase` is
392 # alongside. Therefor `targetphase` is
393 # ignored.
393 # ignored.
394 phases.advanceboundary(repo, tr, phases.draft,
394 phases.advanceboundary(repo, tr, phases.draft,
395 srccontent)
395 srccontent)
396 phases.retractboundary(repo, tr, phases.draft, added)
396 phases.retractboundary(repo, tr, phases.draft, added)
397 elif srctype != 'strip':
397 elif srctype != 'strip':
398 # publishing only alter behavior during push
398 # publishing only alter behavior during push
399 #
399 #
400 # strip should not touch boundary at all
400 # strip should not touch boundary at all
401 phases.retractboundary(repo, tr, targetphase, added)
401 phases.retractboundary(repo, tr, targetphase, added)
402
402
403 if changesets > 0:
403 if changesets > 0:
404 if srctype != 'strip':
404 if srctype != 'strip':
405 # During strip, branchcache is invalid but
405 # During strip, branchcache is invalid but
406 # coming call to `destroyed` will repair it.
406 # coming call to `destroyed` will repair it.
407 # In other case we can safely update cache on
407 # In other case we can safely update cache on
408 # disk.
408 # disk.
409 repo.ui.debug('updating the branch cache\n')
409 repo.ui.debug('updating the branch cache\n')
410 branchmap.updatecache(repo.filtered('served'))
410 branchmap.updatecache(repo.filtered('served'))
411
411
412 def runhooks():
412 def runhooks():
413 # These hooks run when the lock releases, not when the
413 # These hooks run when the lock releases, not when the
414 # transaction closes. So it's possible for the changelog
414 # transaction closes. So it's possible for the changelog
415 # to have changed since we last saw it.
415 # to have changed since we last saw it.
416 if clstart >= len(repo):
416 if clstart >= len(repo):
417 return
417 return
418
418
419 repo.hook("changegroup", **hookargs)
419 repo.hook("changegroup", **hookargs)
420
420
421 for n in added:
421 for n in added:
422 args = hookargs.copy()
422 args = hookargs.copy()
423 args['node'] = hex(n)
423 args['node'] = hex(n)
424 del args['node_last']
424 del args['node_last']
425 repo.hook("incoming", **args)
425 repo.hook("incoming", **args)
426
426
427 newheads = [h for h in repo.heads()
427 newheads = [h for h in repo.heads()
428 if h not in oldheads]
428 if h not in oldheads]
429 repo.ui.log("incoming",
429 repo.ui.log("incoming",
430 "%s incoming changes - new heads: %s\n",
430 "%s incoming changes - new heads: %s\n",
431 len(added),
431 len(added),
432 ', '.join([hex(c[:6]) for c in newheads]))
432 ', '.join([hex(c[:6]) for c in newheads]))
433
433
434 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
434 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
435 lambda tr: repo._afterlock(runhooks))
435 lambda tr: repo._afterlock(runhooks))
436 finally:
436 finally:
437 repo.ui.flush()
437 repo.ui.flush()
438 # never return 0 here:
438 # never return 0 here:
439 if dh < 0:
439 if dh < 0:
440 return dh - 1
440 return dh - 1
441 else:
441 else:
442 return dh + 1
442 return dh + 1
443
443
444 class cg2unpacker(cg1unpacker):
444 class cg2unpacker(cg1unpacker):
445 """Unpacker for cg2 streams.
445 """Unpacker for cg2 streams.
446
446
447 cg2 streams add support for generaldelta, so the delta header
447 cg2 streams add support for generaldelta, so the delta header
448 format is slightly different. All other features about the data
448 format is slightly different. All other features about the data
449 remain the same.
449 remain the same.
450 """
450 """
451 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
451 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
452 deltaheadersize = struct.calcsize(deltaheader)
452 deltaheadersize = struct.calcsize(deltaheader)
453 version = '02'
453 version = '02'
454
454
455 def _deltaheader(self, headertuple, prevnode):
455 def _deltaheader(self, headertuple, prevnode):
456 node, p1, p2, deltabase, cs = headertuple
456 node, p1, p2, deltabase, cs = headertuple
457 flags = 0
457 flags = 0
458 return node, p1, p2, deltabase, cs, flags
458 return node, p1, p2, deltabase, cs, flags
459
459
460 class cg3unpacker(cg2unpacker):
460 class cg3unpacker(cg2unpacker):
461 """Unpacker for cg3 streams.
461 """Unpacker for cg3 streams.
462
462
463 cg3 streams add support for exchanging treemanifests and revlog
463 cg3 streams add support for exchanging treemanifests and revlog
464 flags. It adds the revlog flags to the delta header and an empty chunk
464 flags. It adds the revlog flags to the delta header and an empty chunk
465 separating manifests and files.
465 separating manifests and files.
466 """
466 """
467 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
467 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
468 deltaheadersize = struct.calcsize(deltaheader)
468 deltaheadersize = struct.calcsize(deltaheader)
469 version = '03'
469 version = '03'
470 _grouplistcount = 2 # One list of manifests and one list of files
470 _grouplistcount = 2 # One list of manifests and one list of files
471
471
472 def _deltaheader(self, headertuple, prevnode):
472 def _deltaheader(self, headertuple, prevnode):
473 node, p1, p2, deltabase, cs, flags = headertuple
473 node, p1, p2, deltabase, cs, flags = headertuple
474 return node, p1, p2, deltabase, cs, flags
474 return node, p1, p2, deltabase, cs, flags
475
475
476 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
476 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
477 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
477 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
478 numchanges)
478 numchanges)
479 for chunkdata in iter(self.filelogheader, {}):
479 for chunkdata in iter(self.filelogheader, {}):
480 # If we get here, there are directory manifests in the changegroup
480 # If we get here, there are directory manifests in the changegroup
481 d = chunkdata["filename"]
481 d = chunkdata["filename"]
482 repo.ui.debug("adding %s revisions\n" % d)
482 repo.ui.debug("adding %s revisions\n" % d)
483 dirlog = repo.manifest.dirlog(d)
483 dirlog = repo.manifest.dirlog(d)
484 if not dirlog.addgroup(self, revmap, trp):
484 if not dirlog.addgroup(self, revmap, trp):
485 raise error.Abort(_("received dir revlog group is empty"))
485 raise error.Abort(_("received dir revlog group is empty"))
486
486
487 class headerlessfixup(object):
487 class headerlessfixup(object):
488 def __init__(self, fh, h):
488 def __init__(self, fh, h):
489 self._h = h
489 self._h = h
490 self._fh = fh
490 self._fh = fh
491 def read(self, n):
491 def read(self, n):
492 if self._h:
492 if self._h:
493 d, self._h = self._h[:n], self._h[n:]
493 d, self._h = self._h[:n], self._h[n:]
494 if len(d) < n:
494 if len(d) < n:
495 d += readexactly(self._fh, n - len(d))
495 d += readexactly(self._fh, n - len(d))
496 return d
496 return d
497 return readexactly(self._fh, n)
497 return readexactly(self._fh, n)
498
498
499 class cg1packer(object):
499 class cg1packer(object):
500 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
500 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
501 version = '01'
501 version = '01'
502 def __init__(self, repo, bundlecaps=None):
502 def __init__(self, repo, bundlecaps=None):
503 """Given a source repo, construct a bundler.
503 """Given a source repo, construct a bundler.
504
504
505 bundlecaps is optional and can be used to specify the set of
505 bundlecaps is optional and can be used to specify the set of
506 capabilities which can be used to build the bundle.
506 capabilities which can be used to build the bundle.
507 """
507 """
508 # Set of capabilities we can use to build the bundle.
508 # Set of capabilities we can use to build the bundle.
509 if bundlecaps is None:
509 if bundlecaps is None:
510 bundlecaps = set()
510 bundlecaps = set()
511 self._bundlecaps = bundlecaps
511 self._bundlecaps = bundlecaps
512 # experimental config: bundle.reorder
512 # experimental config: bundle.reorder
513 reorder = repo.ui.config('bundle', 'reorder', 'auto')
513 reorder = repo.ui.config('bundle', 'reorder', 'auto')
514 if reorder == 'auto':
514 if reorder == 'auto':
515 reorder = None
515 reorder = None
516 else:
516 else:
517 reorder = util.parsebool(reorder)
517 reorder = util.parsebool(reorder)
518 self._repo = repo
518 self._repo = repo
519 self._reorder = reorder
519 self._reorder = reorder
520 self._progress = repo.ui.progress
520 self._progress = repo.ui.progress
521 if self._repo.ui.verbose and not self._repo.ui.debugflag:
521 if self._repo.ui.verbose and not self._repo.ui.debugflag:
522 self._verbosenote = self._repo.ui.note
522 self._verbosenote = self._repo.ui.note
523 else:
523 else:
524 self._verbosenote = lambda s: None
524 self._verbosenote = lambda s: None
525
525
526 def close(self):
526 def close(self):
527 return closechunk()
527 return closechunk()
528
528
529 def fileheader(self, fname):
529 def fileheader(self, fname):
530 return chunkheader(len(fname)) + fname
530 return chunkheader(len(fname)) + fname
531
531
532 # Extracted both for clarity and for overriding in extensions.
532 # Extracted both for clarity and for overriding in extensions.
533 def _sortgroup(self, revlog, nodelist, lookup):
533 def _sortgroup(self, revlog, nodelist, lookup):
534 """Sort nodes for change group and turn them into revnums."""
534 """Sort nodes for change group and turn them into revnums."""
535 # for generaldelta revlogs, we linearize the revs; this will both be
535 # for generaldelta revlogs, we linearize the revs; this will both be
536 # much quicker and generate a much smaller bundle
536 # much quicker and generate a much smaller bundle
537 if (revlog._generaldelta and self._reorder is None) or self._reorder:
537 if (revlog._generaldelta and self._reorder is None) or self._reorder:
538 dag = dagutil.revlogdag(revlog)
538 dag = dagutil.revlogdag(revlog)
539 return dag.linearize(set(revlog.rev(n) for n in nodelist))
539 return dag.linearize(set(revlog.rev(n) for n in nodelist))
540 else:
540 else:
541 return sorted([revlog.rev(n) for n in nodelist])
541 return sorted([revlog.rev(n) for n in nodelist])
542
542
543 def group(self, nodelist, revlog, lookup, units=None):
543 def group(self, nodelist, revlog, lookup, units=None):
544 """Calculate a delta group, yielding a sequence of changegroup chunks
544 """Calculate a delta group, yielding a sequence of changegroup chunks
545 (strings).
545 (strings).
546
546
547 Given a list of changeset revs, return a set of deltas and
547 Given a list of changeset revs, return a set of deltas and
548 metadata corresponding to nodes. The first delta is
548 metadata corresponding to nodes. The first delta is
549 first parent(nodelist[0]) -> nodelist[0], the receiver is
549 first parent(nodelist[0]) -> nodelist[0], the receiver is
550 guaranteed to have this parent as it has all history before
550 guaranteed to have this parent as it has all history before
551 these changesets. In the case firstparent is nullrev the
551 these changesets. In the case firstparent is nullrev the
552 changegroup starts with a full revision.
552 changegroup starts with a full revision.
553
553
554 If units is not None, progress detail will be generated, units specifies
554 If units is not None, progress detail will be generated, units specifies
555 the type of revlog that is touched (changelog, manifest, etc.).
555 the type of revlog that is touched (changelog, manifest, etc.).
556 """
556 """
557 # if we don't have any revisions touched by these changesets, bail
557 # if we don't have any revisions touched by these changesets, bail
558 if len(nodelist) == 0:
558 if len(nodelist) == 0:
559 yield self.close()
559 yield self.close()
560 return
560 return
561
561
562 revs = self._sortgroup(revlog, nodelist, lookup)
562 revs = self._sortgroup(revlog, nodelist, lookup)
563
563
564 # add the parent of the first rev
564 # add the parent of the first rev
565 p = revlog.parentrevs(revs[0])[0]
565 p = revlog.parentrevs(revs[0])[0]
566 revs.insert(0, p)
566 revs.insert(0, p)
567
567
568 # build deltas
568 # build deltas
569 total = len(revs) - 1
569 total = len(revs) - 1
570 msgbundling = _('bundling')
570 msgbundling = _('bundling')
571 for r in xrange(len(revs) - 1):
571 for r in xrange(len(revs) - 1):
572 if units is not None:
572 if units is not None:
573 self._progress(msgbundling, r + 1, unit=units, total=total)
573 self._progress(msgbundling, r + 1, unit=units, total=total)
574 prev, curr = revs[r], revs[r + 1]
574 prev, curr = revs[r], revs[r + 1]
575 linknode = lookup(revlog.node(curr))
575 linknode = lookup(revlog.node(curr))
576 for c in self.revchunk(revlog, curr, prev, linknode):
576 for c in self.revchunk(revlog, curr, prev, linknode):
577 yield c
577 yield c
578
578
579 if units is not None:
579 if units is not None:
580 self._progress(msgbundling, None)
580 self._progress(msgbundling, None)
581 yield self.close()
581 yield self.close()
582
582
583 # filter any nodes that claim to be part of the known set
583 # filter any nodes that claim to be part of the known set
584 def prune(self, revlog, missing, commonrevs):
584 def prune(self, revlog, missing, commonrevs):
585 rr, rl = revlog.rev, revlog.linkrev
585 rr, rl = revlog.rev, revlog.linkrev
586 return [n for n in missing if rl(rr(n)) not in commonrevs]
586 return [n for n in missing if rl(rr(n)) not in commonrevs]
587
587
588 def _packmanifests(self, dir, mfnodes, lookuplinknode):
588 def _packmanifests(self, dir, mfnodes, lookuplinknode):
589 """Pack flat manifests into a changegroup stream."""
589 """Pack flat manifests into a changegroup stream."""
590 assert not dir
590 assert not dir
591 for chunk in self.group(mfnodes, self._repo.manifest,
591 for chunk in self.group(mfnodes, self._repo.manifest,
592 lookuplinknode, units=_('manifests')):
592 lookuplinknode, units=_('manifests')):
593 yield chunk
593 yield chunk
594
594
595 def _manifestsdone(self):
595 def _manifestsdone(self):
596 return ''
596 return ''
597
597
598 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
598 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
599 '''yield a sequence of changegroup chunks (strings)'''
599 '''yield a sequence of changegroup chunks (strings)'''
600 repo = self._repo
600 repo = self._repo
601 cl = repo.changelog
601 cl = repo.changelog
602
602
603 clrevorder = {}
603 clrevorder = {}
604 mfs = {} # needed manifests
604 mfs = {} # needed manifests
605 fnodes = {} # needed file nodes
605 fnodes = {} # needed file nodes
606 changedfiles = set()
606 changedfiles = set()
607
607
608 # Callback for the changelog, used to collect changed files and manifest
608 # Callback for the changelog, used to collect changed files and manifest
609 # nodes.
609 # nodes.
610 # Returns the linkrev node (identity in the changelog case).
610 # Returns the linkrev node (identity in the changelog case).
611 def lookupcl(x):
611 def lookupcl(x):
612 c = cl.read(x)
612 c = cl.read(x)
613 clrevorder[x] = len(clrevorder)
613 clrevorder[x] = len(clrevorder)
614 n = c[0]
614 n = c[0]
615 # record the first changeset introducing this manifest version
615 # record the first changeset introducing this manifest version
616 mfs.setdefault(n, x)
616 mfs.setdefault(n, x)
617 # Record a complete list of potentially-changed files in
617 # Record a complete list of potentially-changed files in
618 # this manifest.
618 # this manifest.
619 changedfiles.update(c[3])
619 changedfiles.update(c[3])
620 return x
620 return x
621
621
622 self._verbosenote(_('uncompressed size of bundle content:\n'))
622 self._verbosenote(_('uncompressed size of bundle content:\n'))
623 size = 0
623 size = 0
624 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
624 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
625 size += len(chunk)
625 size += len(chunk)
626 yield chunk
626 yield chunk
627 self._verbosenote(_('%8.i (changelog)\n') % size)
627 self._verbosenote(_('%8.i (changelog)\n') % size)
628
628
629 # We need to make sure that the linkrev in the changegroup refers to
629 # We need to make sure that the linkrev in the changegroup refers to
630 # the first changeset that introduced the manifest or file revision.
630 # the first changeset that introduced the manifest or file revision.
631 # The fastpath is usually safer than the slowpath, because the filelogs
631 # The fastpath is usually safer than the slowpath, because the filelogs
632 # are walked in revlog order.
632 # are walked in revlog order.
633 #
633 #
634 # When taking the slowpath with reorder=None and the manifest revlog
634 # When taking the slowpath with reorder=None and the manifest revlog
635 # uses generaldelta, the manifest may be walked in the "wrong" order.
635 # uses generaldelta, the manifest may be walked in the "wrong" order.
636 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
636 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
637 # cc0ff93d0c0c).
637 # cc0ff93d0c0c).
638 #
638 #
639 # When taking the fastpath, we are only vulnerable to reordering
639 # When taking the fastpath, we are only vulnerable to reordering
640 # of the changelog itself. The changelog never uses generaldelta, so
640 # of the changelog itself. The changelog never uses generaldelta, so
641 # it is only reordered when reorder=True. To handle this case, we
641 # it is only reordered when reorder=True. To handle this case, we
642 # simply take the slowpath, which already has the 'clrevorder' logic.
642 # simply take the slowpath, which already has the 'clrevorder' logic.
643 # This was also fixed in cc0ff93d0c0c.
643 # This was also fixed in cc0ff93d0c0c.
644 fastpathlinkrev = fastpathlinkrev and not self._reorder
644 fastpathlinkrev = fastpathlinkrev and not self._reorder
645 # Treemanifests don't work correctly with fastpathlinkrev
645 # Treemanifests don't work correctly with fastpathlinkrev
646 # either, because we don't discover which directory nodes to
646 # either, because we don't discover which directory nodes to
647 # send along with files. This could probably be fixed.
647 # send along with files. This could probably be fixed.
648 fastpathlinkrev = fastpathlinkrev and (
648 fastpathlinkrev = fastpathlinkrev and (
649 'treemanifest' not in repo.requirements)
649 'treemanifest' not in repo.requirements)
650
650
651 for chunk in self.generatemanifests(commonrevs, clrevorder,
651 for chunk in self.generatemanifests(commonrevs, clrevorder,
652 fastpathlinkrev, mfs, fnodes):
652 fastpathlinkrev, mfs, fnodes):
653 yield chunk
653 yield chunk
654 mfs.clear()
654 mfs.clear()
655 clrevs = set(cl.rev(x) for x in clnodes)
655 clrevs = set(cl.rev(x) for x in clnodes)
656
656
657 if not fastpathlinkrev:
657 if not fastpathlinkrev:
658 def linknodes(unused, fname):
658 def linknodes(unused, fname):
659 return fnodes.get(fname, {})
659 return fnodes.get(fname, {})
660 else:
660 else:
661 cln = cl.node
661 cln = cl.node
662 def linknodes(filerevlog, fname):
662 def linknodes(filerevlog, fname):
663 llr = filerevlog.linkrev
663 llr = filerevlog.linkrev
664 fln = filerevlog.node
664 fln = filerevlog.node
665 revs = ((r, llr(r)) for r in filerevlog)
665 revs = ((r, llr(r)) for r in filerevlog)
666 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
666 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
667
667
668 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
668 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
669 source):
669 source):
670 yield chunk
670 yield chunk
671
671
672 yield self.close()
672 yield self.close()
673
673
674 if clnodes:
674 if clnodes:
675 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
675 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
676
676
677 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
677 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
678 fnodes):
678 fnodes):
679 repo = self._repo
679 repo = self._repo
680 dirlog = repo.manifest.dirlog
680 mfl = repo.manifestlog
681 dirlog = mfl._revlog.dirlog
681 tmfnodes = {'': mfs}
682 tmfnodes = {'': mfs}
682
683
683 # Callback for the manifest, used to collect linkrevs for filelog
684 # Callback for the manifest, used to collect linkrevs for filelog
684 # revisions.
685 # revisions.
685 # Returns the linkrev node (collected in lookupcl).
686 # Returns the linkrev node (collected in lookupcl).
686 def makelookupmflinknode(dir):
687 def makelookupmflinknode(dir):
687 if fastpathlinkrev:
688 if fastpathlinkrev:
688 assert not dir
689 assert not dir
689 return mfs.__getitem__
690 return mfs.__getitem__
690
691
691 def lookupmflinknode(x):
692 def lookupmflinknode(x):
692 """Callback for looking up the linknode for manifests.
693 """Callback for looking up the linknode for manifests.
693
694
694 Returns the linkrev node for the specified manifest.
695 Returns the linkrev node for the specified manifest.
695
696
696 SIDE EFFECT:
697 SIDE EFFECT:
697
698
698 1) fclnodes gets populated with the list of relevant
699 1) fclnodes gets populated with the list of relevant
699 file nodes if we're not using fastpathlinkrev
700 file nodes if we're not using fastpathlinkrev
700 2) When treemanifests are in use, collects treemanifest nodes
701 2) When treemanifests are in use, collects treemanifest nodes
701 to send
702 to send
702
703
703 Note that this means manifests must be completely sent to
704 Note that this means manifests must be completely sent to
704 the client before you can trust the list of files and
705 the client before you can trust the list of files and
705 treemanifests to send.
706 treemanifests to send.
706 """
707 """
707 clnode = tmfnodes[dir][x]
708 clnode = tmfnodes[dir][x]
708 mdata = dirlog(dir).readshallowfast(x)
709 mdata = mfl.get(dir, x).readfast(shallow=True)
709 for p, n, fl in mdata.iterentries():
710 for p, n, fl in mdata.iterentries():
710 if fl == 't': # subdirectory manifest
711 if fl == 't': # subdirectory manifest
711 subdir = dir + p + '/'
712 subdir = dir + p + '/'
712 tmfclnodes = tmfnodes.setdefault(subdir, {})
713 tmfclnodes = tmfnodes.setdefault(subdir, {})
713 tmfclnode = tmfclnodes.setdefault(n, clnode)
714 tmfclnode = tmfclnodes.setdefault(n, clnode)
714 if clrevorder[clnode] < clrevorder[tmfclnode]:
715 if clrevorder[clnode] < clrevorder[tmfclnode]:
715 tmfclnodes[n] = clnode
716 tmfclnodes[n] = clnode
716 else:
717 else:
717 f = dir + p
718 f = dir + p
718 fclnodes = fnodes.setdefault(f, {})
719 fclnodes = fnodes.setdefault(f, {})
719 fclnode = fclnodes.setdefault(n, clnode)
720 fclnode = fclnodes.setdefault(n, clnode)
720 if clrevorder[clnode] < clrevorder[fclnode]:
721 if clrevorder[clnode] < clrevorder[fclnode]:
721 fclnodes[n] = clnode
722 fclnodes[n] = clnode
722 return clnode
723 return clnode
723 return lookupmflinknode
724 return lookupmflinknode
724
725
725 size = 0
726 size = 0
726 while tmfnodes:
727 while tmfnodes:
727 dir = min(tmfnodes)
728 dir = min(tmfnodes)
728 nodes = tmfnodes[dir]
729 nodes = tmfnodes[dir]
729 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
730 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
730 if not dir or prunednodes:
731 if not dir or prunednodes:
731 for x in self._packmanifests(dir, prunednodes,
732 for x in self._packmanifests(dir, prunednodes,
732 makelookupmflinknode(dir)):
733 makelookupmflinknode(dir)):
733 size += len(x)
734 size += len(x)
734 yield x
735 yield x
735 del tmfnodes[dir]
736 del tmfnodes[dir]
736 self._verbosenote(_('%8.i (manifests)\n') % size)
737 self._verbosenote(_('%8.i (manifests)\n') % size)
737 yield self._manifestsdone()
738 yield self._manifestsdone()
738
739
739 # The 'source' parameter is useful for extensions
740 # The 'source' parameter is useful for extensions
740 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
741 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
741 repo = self._repo
742 repo = self._repo
742 progress = self._progress
743 progress = self._progress
743 msgbundling = _('bundling')
744 msgbundling = _('bundling')
744
745
745 total = len(changedfiles)
746 total = len(changedfiles)
746 # for progress output
747 # for progress output
747 msgfiles = _('files')
748 msgfiles = _('files')
748 for i, fname in enumerate(sorted(changedfiles)):
749 for i, fname in enumerate(sorted(changedfiles)):
749 filerevlog = repo.file(fname)
750 filerevlog = repo.file(fname)
750 if not filerevlog:
751 if not filerevlog:
751 raise error.Abort(_("empty or missing revlog for %s") % fname)
752 raise error.Abort(_("empty or missing revlog for %s") % fname)
752
753
753 linkrevnodes = linknodes(filerevlog, fname)
754 linkrevnodes = linknodes(filerevlog, fname)
754 # Lookup for filenodes, we collected the linkrev nodes above in the
755 # Lookup for filenodes, we collected the linkrev nodes above in the
755 # fastpath case and with lookupmf in the slowpath case.
756 # fastpath case and with lookupmf in the slowpath case.
756 def lookupfilelog(x):
757 def lookupfilelog(x):
757 return linkrevnodes[x]
758 return linkrevnodes[x]
758
759
759 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
760 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
760 if filenodes:
761 if filenodes:
761 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
762 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
762 total=total)
763 total=total)
763 h = self.fileheader(fname)
764 h = self.fileheader(fname)
764 size = len(h)
765 size = len(h)
765 yield h
766 yield h
766 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
767 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
767 size += len(chunk)
768 size += len(chunk)
768 yield chunk
769 yield chunk
769 self._verbosenote(_('%8.i %s\n') % (size, fname))
770 self._verbosenote(_('%8.i %s\n') % (size, fname))
770 progress(msgbundling, None)
771 progress(msgbundling, None)
771
772
772 def deltaparent(self, revlog, rev, p1, p2, prev):
773 def deltaparent(self, revlog, rev, p1, p2, prev):
773 return prev
774 return prev
774
775
775 def revchunk(self, revlog, rev, prev, linknode):
776 def revchunk(self, revlog, rev, prev, linknode):
776 node = revlog.node(rev)
777 node = revlog.node(rev)
777 p1, p2 = revlog.parentrevs(rev)
778 p1, p2 = revlog.parentrevs(rev)
778 base = self.deltaparent(revlog, rev, p1, p2, prev)
779 base = self.deltaparent(revlog, rev, p1, p2, prev)
779
780
780 prefix = ''
781 prefix = ''
781 if revlog.iscensored(base) or revlog.iscensored(rev):
782 if revlog.iscensored(base) or revlog.iscensored(rev):
782 try:
783 try:
783 delta = revlog.revision(node)
784 delta = revlog.revision(node)
784 except error.CensoredNodeError as e:
785 except error.CensoredNodeError as e:
785 delta = e.tombstone
786 delta = e.tombstone
786 if base == nullrev:
787 if base == nullrev:
787 prefix = mdiff.trivialdiffheader(len(delta))
788 prefix = mdiff.trivialdiffheader(len(delta))
788 else:
789 else:
789 baselen = revlog.rawsize(base)
790 baselen = revlog.rawsize(base)
790 prefix = mdiff.replacediffheader(baselen, len(delta))
791 prefix = mdiff.replacediffheader(baselen, len(delta))
791 elif base == nullrev:
792 elif base == nullrev:
792 delta = revlog.revision(node)
793 delta = revlog.revision(node)
793 prefix = mdiff.trivialdiffheader(len(delta))
794 prefix = mdiff.trivialdiffheader(len(delta))
794 else:
795 else:
795 delta = revlog.revdiff(base, rev)
796 delta = revlog.revdiff(base, rev)
796 p1n, p2n = revlog.parents(node)
797 p1n, p2n = revlog.parents(node)
797 basenode = revlog.node(base)
798 basenode = revlog.node(base)
798 flags = revlog.flags(rev)
799 flags = revlog.flags(rev)
799 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
800 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
800 meta += prefix
801 meta += prefix
801 l = len(meta) + len(delta)
802 l = len(meta) + len(delta)
802 yield chunkheader(l)
803 yield chunkheader(l)
803 yield meta
804 yield meta
804 yield delta
805 yield delta
805 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
806 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
806 # do nothing with basenode, it is implicitly the previous one in HG10
807 # do nothing with basenode, it is implicitly the previous one in HG10
807 # do nothing with flags, it is implicitly 0 for cg1 and cg2
808 # do nothing with flags, it is implicitly 0 for cg1 and cg2
808 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
809 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
809
810
810 class cg2packer(cg1packer):
811 class cg2packer(cg1packer):
811 version = '02'
812 version = '02'
812 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
813 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
813
814
814 def __init__(self, repo, bundlecaps=None):
815 def __init__(self, repo, bundlecaps=None):
815 super(cg2packer, self).__init__(repo, bundlecaps)
816 super(cg2packer, self).__init__(repo, bundlecaps)
816 if self._reorder is None:
817 if self._reorder is None:
817 # Since generaldelta is directly supported by cg2, reordering
818 # Since generaldelta is directly supported by cg2, reordering
818 # generally doesn't help, so we disable it by default (treating
819 # generally doesn't help, so we disable it by default (treating
819 # bundle.reorder=auto just like bundle.reorder=False).
820 # bundle.reorder=auto just like bundle.reorder=False).
820 self._reorder = False
821 self._reorder = False
821
822
822 def deltaparent(self, revlog, rev, p1, p2, prev):
823 def deltaparent(self, revlog, rev, p1, p2, prev):
823 dp = revlog.deltaparent(rev)
824 dp = revlog.deltaparent(rev)
824 if dp == nullrev and revlog.storedeltachains:
825 if dp == nullrev and revlog.storedeltachains:
825 # Avoid sending full revisions when delta parent is null. Pick prev
826 # Avoid sending full revisions when delta parent is null. Pick prev
826 # in that case. It's tempting to pick p1 in this case, as p1 will
827 # in that case. It's tempting to pick p1 in this case, as p1 will
827 # be smaller in the common case. However, computing a delta against
828 # be smaller in the common case. However, computing a delta against
828 # p1 may require resolving the raw text of p1, which could be
829 # p1 may require resolving the raw text of p1, which could be
829 # expensive. The revlog caches should have prev cached, meaning
830 # expensive. The revlog caches should have prev cached, meaning
830 # less CPU for changegroup generation. There is likely room to add
831 # less CPU for changegroup generation. There is likely room to add
831 # a flag and/or config option to control this behavior.
832 # a flag and/or config option to control this behavior.
832 return prev
833 return prev
833 elif dp == nullrev:
834 elif dp == nullrev:
834 # revlog is configured to use full snapshot for a reason,
835 # revlog is configured to use full snapshot for a reason,
835 # stick to full snapshot.
836 # stick to full snapshot.
836 return nullrev
837 return nullrev
837 elif dp not in (p1, p2, prev):
838 elif dp not in (p1, p2, prev):
838 # Pick prev when we can't be sure remote has the base revision.
839 # Pick prev when we can't be sure remote has the base revision.
839 return prev
840 return prev
840 else:
841 else:
841 return dp
842 return dp
842
843
843 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
844 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
844 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
845 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
845 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
846 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
846
847
847 class cg3packer(cg2packer):
848 class cg3packer(cg2packer):
848 version = '03'
849 version = '03'
849 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
850 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
850
851
851 def _packmanifests(self, dir, mfnodes, lookuplinknode):
852 def _packmanifests(self, dir, mfnodes, lookuplinknode):
852 if dir:
853 if dir:
853 yield self.fileheader(dir)
854 yield self.fileheader(dir)
854 for chunk in self.group(mfnodes, self._repo.manifest.dirlog(dir),
855 for chunk in self.group(mfnodes, self._repo.manifest.dirlog(dir),
855 lookuplinknode, units=_('manifests')):
856 lookuplinknode, units=_('manifests')):
856 yield chunk
857 yield chunk
857
858
858 def _manifestsdone(self):
859 def _manifestsdone(self):
859 return self.close()
860 return self.close()
860
861
861 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
862 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
862 return struct.pack(
863 return struct.pack(
863 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
864 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
864
865
865 _packermap = {'01': (cg1packer, cg1unpacker),
866 _packermap = {'01': (cg1packer, cg1unpacker),
866 # cg2 adds support for exchanging generaldelta
867 # cg2 adds support for exchanging generaldelta
867 '02': (cg2packer, cg2unpacker),
868 '02': (cg2packer, cg2unpacker),
868 # cg3 adds support for exchanging revlog flags and treemanifests
869 # cg3 adds support for exchanging revlog flags and treemanifests
869 '03': (cg3packer, cg3unpacker),
870 '03': (cg3packer, cg3unpacker),
870 }
871 }
871
872
872 def allsupportedversions(ui):
873 def allsupportedversions(ui):
873 versions = set(_packermap.keys())
874 versions = set(_packermap.keys())
874 versions.discard('03')
875 versions.discard('03')
875 if (ui.configbool('experimental', 'changegroup3') or
876 if (ui.configbool('experimental', 'changegroup3') or
876 ui.configbool('experimental', 'treemanifest')):
877 ui.configbool('experimental', 'treemanifest')):
877 versions.add('03')
878 versions.add('03')
878 return versions
879 return versions
879
880
880 # Changegroup versions that can be applied to the repo
881 # Changegroup versions that can be applied to the repo
881 def supportedincomingversions(repo):
882 def supportedincomingversions(repo):
882 versions = allsupportedversions(repo.ui)
883 versions = allsupportedversions(repo.ui)
883 if 'treemanifest' in repo.requirements:
884 if 'treemanifest' in repo.requirements:
884 versions.add('03')
885 versions.add('03')
885 return versions
886 return versions
886
887
887 # Changegroup versions that can be created from the repo
888 # Changegroup versions that can be created from the repo
888 def supportedoutgoingversions(repo):
889 def supportedoutgoingversions(repo):
889 versions = allsupportedversions(repo.ui)
890 versions = allsupportedversions(repo.ui)
890 if 'treemanifest' in repo.requirements:
891 if 'treemanifest' in repo.requirements:
891 # Versions 01 and 02 support only flat manifests and it's just too
892 # Versions 01 and 02 support only flat manifests and it's just too
892 # expensive to convert between the flat manifest and tree manifest on
893 # expensive to convert between the flat manifest and tree manifest on
893 # the fly. Since tree manifests are hashed differently, all of history
894 # the fly. Since tree manifests are hashed differently, all of history
894 # would have to be converted. Instead, we simply don't even pretend to
895 # would have to be converted. Instead, we simply don't even pretend to
895 # support versions 01 and 02.
896 # support versions 01 and 02.
896 versions.discard('01')
897 versions.discard('01')
897 versions.discard('02')
898 versions.discard('02')
898 versions.add('03')
899 versions.add('03')
899 return versions
900 return versions
900
901
901 def safeversion(repo):
902 def safeversion(repo):
902 # Finds the smallest version that it's safe to assume clients of the repo
903 # Finds the smallest version that it's safe to assume clients of the repo
903 # will support. For example, all hg versions that support generaldelta also
904 # will support. For example, all hg versions that support generaldelta also
904 # support changegroup 02.
905 # support changegroup 02.
905 versions = supportedoutgoingversions(repo)
906 versions = supportedoutgoingversions(repo)
906 if 'generaldelta' in repo.requirements:
907 if 'generaldelta' in repo.requirements:
907 versions.discard('01')
908 versions.discard('01')
908 assert versions
909 assert versions
909 return min(versions)
910 return min(versions)
910
911
911 def getbundler(version, repo, bundlecaps=None):
912 def getbundler(version, repo, bundlecaps=None):
912 assert version in supportedoutgoingversions(repo)
913 assert version in supportedoutgoingversions(repo)
913 return _packermap[version][0](repo, bundlecaps)
914 return _packermap[version][0](repo, bundlecaps)
914
915
915 def getunbundler(version, fh, alg, extras=None):
916 def getunbundler(version, fh, alg, extras=None):
916 return _packermap[version][1](fh, alg, extras=extras)
917 return _packermap[version][1](fh, alg, extras=extras)
917
918
918 def _changegroupinfo(repo, nodes, source):
919 def _changegroupinfo(repo, nodes, source):
919 if repo.ui.verbose or source == 'bundle':
920 if repo.ui.verbose or source == 'bundle':
920 repo.ui.status(_("%d changesets found\n") % len(nodes))
921 repo.ui.status(_("%d changesets found\n") % len(nodes))
921 if repo.ui.debugflag:
922 if repo.ui.debugflag:
922 repo.ui.debug("list of changesets:\n")
923 repo.ui.debug("list of changesets:\n")
923 for node in nodes:
924 for node in nodes:
924 repo.ui.debug("%s\n" % hex(node))
925 repo.ui.debug("%s\n" % hex(node))
925
926
926 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
927 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
927 repo = repo.unfiltered()
928 repo = repo.unfiltered()
928 commonrevs = outgoing.common
929 commonrevs = outgoing.common
929 csets = outgoing.missing
930 csets = outgoing.missing
930 heads = outgoing.missingheads
931 heads = outgoing.missingheads
931 # We go through the fast path if we get told to, or if all (unfiltered
932 # We go through the fast path if we get told to, or if all (unfiltered
932 # heads have been requested (since we then know there all linkrevs will
933 # heads have been requested (since we then know there all linkrevs will
933 # be pulled by the client).
934 # be pulled by the client).
934 heads.sort()
935 heads.sort()
935 fastpathlinkrev = fastpath or (
936 fastpathlinkrev = fastpath or (
936 repo.filtername is None and heads == sorted(repo.heads()))
937 repo.filtername is None and heads == sorted(repo.heads()))
937
938
938 repo.hook('preoutgoing', throw=True, source=source)
939 repo.hook('preoutgoing', throw=True, source=source)
939 _changegroupinfo(repo, csets, source)
940 _changegroupinfo(repo, csets, source)
940 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
941 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
941
942
942 def getsubset(repo, outgoing, bundler, source, fastpath=False):
943 def getsubset(repo, outgoing, bundler, source, fastpath=False):
943 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
944 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
944 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
945 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
945 {'clcount': len(outgoing.missing)})
946 {'clcount': len(outgoing.missing)})
946
947
947 def changegroupsubset(repo, roots, heads, source, version='01'):
948 def changegroupsubset(repo, roots, heads, source, version='01'):
948 """Compute a changegroup consisting of all the nodes that are
949 """Compute a changegroup consisting of all the nodes that are
949 descendants of any of the roots and ancestors of any of the heads.
950 descendants of any of the roots and ancestors of any of the heads.
950 Return a chunkbuffer object whose read() method will return
951 Return a chunkbuffer object whose read() method will return
951 successive changegroup chunks.
952 successive changegroup chunks.
952
953
953 It is fairly complex as determining which filenodes and which
954 It is fairly complex as determining which filenodes and which
954 manifest nodes need to be included for the changeset to be complete
955 manifest nodes need to be included for the changeset to be complete
955 is non-trivial.
956 is non-trivial.
956
957
957 Another wrinkle is doing the reverse, figuring out which changeset in
958 Another wrinkle is doing the reverse, figuring out which changeset in
958 the changegroup a particular filenode or manifestnode belongs to.
959 the changegroup a particular filenode or manifestnode belongs to.
959 """
960 """
960 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
961 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
961 bundler = getbundler(version, repo)
962 bundler = getbundler(version, repo)
962 return getsubset(repo, outgoing, bundler, source)
963 return getsubset(repo, outgoing, bundler, source)
963
964
964 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
965 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
965 version='01'):
966 version='01'):
966 """Like getbundle, but taking a discovery.outgoing as an argument.
967 """Like getbundle, but taking a discovery.outgoing as an argument.
967
968
968 This is only implemented for local repos and reuses potentially
969 This is only implemented for local repos and reuses potentially
969 precomputed sets in outgoing. Returns a raw changegroup generator."""
970 precomputed sets in outgoing. Returns a raw changegroup generator."""
970 if not outgoing.missing:
971 if not outgoing.missing:
971 return None
972 return None
972 bundler = getbundler(version, repo, bundlecaps)
973 bundler = getbundler(version, repo, bundlecaps)
973 return getsubsetraw(repo, outgoing, bundler, source)
974 return getsubsetraw(repo, outgoing, bundler, source)
974
975
975 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
976 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
976 version='01'):
977 version='01'):
977 """Like getbundle, but taking a discovery.outgoing as an argument.
978 """Like getbundle, but taking a discovery.outgoing as an argument.
978
979
979 This is only implemented for local repos and reuses potentially
980 This is only implemented for local repos and reuses potentially
980 precomputed sets in outgoing."""
981 precomputed sets in outgoing."""
981 if not outgoing.missing:
982 if not outgoing.missing:
982 return None
983 return None
983 bundler = getbundler(version, repo, bundlecaps)
984 bundler = getbundler(version, repo, bundlecaps)
984 return getsubset(repo, outgoing, bundler, source)
985 return getsubset(repo, outgoing, bundler, source)
985
986
986 def getchangegroup(repo, source, outgoing, bundlecaps=None,
987 def getchangegroup(repo, source, outgoing, bundlecaps=None,
987 version='01'):
988 version='01'):
988 """Like changegroupsubset, but returns the set difference between the
989 """Like changegroupsubset, but returns the set difference between the
989 ancestors of heads and the ancestors common.
990 ancestors of heads and the ancestors common.
990
991
991 If heads is None, use the local heads. If common is None, use [nullid].
992 If heads is None, use the local heads. If common is None, use [nullid].
992
993
993 The nodes in common might not all be known locally due to the way the
994 The nodes in common might not all be known locally due to the way the
994 current discovery protocol works.
995 current discovery protocol works.
995 """
996 """
996 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
997 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
997 version=version)
998 version=version)
998
999
999 def changegroup(repo, basenodes, source):
1000 def changegroup(repo, basenodes, source):
1000 # to avoid a race we use changegroupsubset() (issue1320)
1001 # to avoid a race we use changegroupsubset() (issue1320)
1001 return changegroupsubset(repo, basenodes, repo.heads(), source)
1002 return changegroupsubset(repo, basenodes, repo.heads(), source)
1002
1003
1003 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1004 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1004 revisions = 0
1005 revisions = 0
1005 files = 0
1006 files = 0
1006 for chunkdata in iter(source.filelogheader, {}):
1007 for chunkdata in iter(source.filelogheader, {}):
1007 files += 1
1008 files += 1
1008 f = chunkdata["filename"]
1009 f = chunkdata["filename"]
1009 repo.ui.debug("adding %s revisions\n" % f)
1010 repo.ui.debug("adding %s revisions\n" % f)
1010 repo.ui.progress(_('files'), files, unit=_('files'),
1011 repo.ui.progress(_('files'), files, unit=_('files'),
1011 total=expectedfiles)
1012 total=expectedfiles)
1012 fl = repo.file(f)
1013 fl = repo.file(f)
1013 o = len(fl)
1014 o = len(fl)
1014 try:
1015 try:
1015 if not fl.addgroup(source, revmap, trp):
1016 if not fl.addgroup(source, revmap, trp):
1016 raise error.Abort(_("received file revlog group is empty"))
1017 raise error.Abort(_("received file revlog group is empty"))
1017 except error.CensoredBaseError as e:
1018 except error.CensoredBaseError as e:
1018 raise error.Abort(_("received delta base is censored: %s") % e)
1019 raise error.Abort(_("received delta base is censored: %s") % e)
1019 revisions += len(fl) - o
1020 revisions += len(fl) - o
1020 if f in needfiles:
1021 if f in needfiles:
1021 needs = needfiles[f]
1022 needs = needfiles[f]
1022 for new in xrange(o, len(fl)):
1023 for new in xrange(o, len(fl)):
1023 n = fl.node(new)
1024 n = fl.node(new)
1024 if n in needs:
1025 if n in needs:
1025 needs.remove(n)
1026 needs.remove(n)
1026 else:
1027 else:
1027 raise error.Abort(
1028 raise error.Abort(
1028 _("received spurious file revlog entry"))
1029 _("received spurious file revlog entry"))
1029 if not needs:
1030 if not needs:
1030 del needfiles[f]
1031 del needfiles[f]
1031 repo.ui.progress(_('files'), None)
1032 repo.ui.progress(_('files'), None)
1032
1033
1033 for f, needs in needfiles.iteritems():
1034 for f, needs in needfiles.iteritems():
1034 fl = repo.file(f)
1035 fl = repo.file(f)
1035 for n in needs:
1036 for n in needs:
1036 try:
1037 try:
1037 fl.rev(n)
1038 fl.rev(n)
1038 except error.LookupError:
1039 except error.LookupError:
1039 raise error.Abort(
1040 raise error.Abort(
1040 _('missing file data for %s:%s - run hg verify') %
1041 _('missing file data for %s:%s - run hg verify') %
1041 (f, hex(n)))
1042 (f, hex(n)))
1042
1043
1043 return revisions, files
1044 return revisions, files
@@ -1,1579 +1,1576 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class lazymanifestiter(object):
107 class lazymanifestiter(object):
108 def __init__(self, lm):
108 def __init__(self, lm):
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self):
112 def __iter__(self):
113 return self
113 return self
114
114
115 def next(self):
115 def next(self):
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 self.pos += 1
121 self.pos += 1
122 return data[0]
122 return data[0]
123 self.pos += 1
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
125 return data[pos:zeropos]
126
126
127 class lazymanifestiterentries(object):
127 class lazymanifestiterentries(object):
128 def __init__(self, lm):
128 def __init__(self, lm):
129 self.lm = lm
129 self.lm = lm
130 self.pos = 0
130 self.pos = 0
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return self
133 return self
134
134
135 def next(self):
135 def next(self):
136 try:
136 try:
137 data, pos = self.lm._get(self.pos)
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
138 except IndexError:
139 raise StopIteration
139 raise StopIteration
140 if pos == -1:
140 if pos == -1:
141 self.pos += 1
141 self.pos += 1
142 return data
142 return data
143 zeropos = data.find('\x00', pos)
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
148 return (data[pos:zeropos], hashval, flags)
149
149
150 def unhexlify(data, extra, pos, length):
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
151 s = data[pos:pos + length].decode('hex')
152 if extra:
152 if extra:
153 s += chr(extra & 0xff)
153 s += chr(extra & 0xff)
154 return s
154 return s
155
155
156 def _cmp(a, b):
156 def _cmp(a, b):
157 return (a > b) - (a < b)
157 return (a > b) - (a < b)
158
158
159 class _lazymanifest(object):
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
161 if positions is None:
162 self.positions = self.findlines(data)
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
164 self.data = data
165 self.extradata = []
165 self.extradata = []
166 else:
166 else:
167 self.positions = positions[:]
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
169 self.extradata = extradata[:]
170 self.data = data
170 self.data = data
171
171
172 def findlines(self, data):
172 def findlines(self, data):
173 if not data:
173 if not data:
174 return []
174 return []
175 pos = data.find("\n")
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
178 positions = [0]
179 prev = data[:data.find('\x00')]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
185 prev = nexts
186 pos = data.find("\n", pos + 1)
186 pos = data.find("\n", pos + 1)
187 return positions
187 return positions
188
188
189 def _get(self, index):
189 def _get(self, index):
190 # get the position encoded in pos:
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
192 # negative number is in extrapieces
193 pos = self.positions[index]
193 pos = self.positions[index]
194 if pos >= 0:
194 if pos >= 0:
195 return self.data, pos
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
196 return self.extradata[-pos - 1], -1
197
197
198 def _getkey(self, pos):
198 def _getkey(self, pos):
199 if pos >= 0:
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
201 return self.extradata[-pos - 1][0]
202
202
203 def bsearch(self, key):
203 def bsearch(self, key):
204 first = 0
204 first = 0
205 last = len(self.positions) - 1
205 last = len(self.positions) - 1
206
206
207 while first <= last:
207 while first <= last:
208 midpoint = (first + last)//2
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
211 r = _cmp(key, candidate)
212 if r == 0:
212 if r == 0:
213 return midpoint
213 return midpoint
214 else:
214 else:
215 if r < 0:
215 if r < 0:
216 last = midpoint - 1
216 last = midpoint - 1
217 else:
217 else:
218 first = midpoint + 1
218 first = midpoint + 1
219 return -1
219 return -1
220
220
221 def bsearch2(self, key):
221 def bsearch2(self, key):
222 # same as the above, but will always return the position
222 # same as the above, but will always return the position
223 # done for performance reasons
223 # done for performance reasons
224 first = 0
224 first = 0
225 last = len(self.positions) - 1
225 last = len(self.positions) - 1
226
226
227 while first <= last:
227 while first <= last:
228 midpoint = (first + last)//2
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
231 r = _cmp(key, candidate)
232 if r == 0:
232 if r == 0:
233 return (midpoint, True)
233 return (midpoint, True)
234 else:
234 else:
235 if r < 0:
235 if r < 0:
236 last = midpoint - 1
236 last = midpoint - 1
237 else:
237 else:
238 first = midpoint + 1
238 first = midpoint + 1
239 return (first, False)
239 return (first, False)
240
240
241 def __contains__(self, key):
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
242 return self.bsearch(key) != -1
243
243
244 def _getflags(self, data, needle, pos):
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
245 start = pos + 41
246 end = data.find("\n", start)
246 end = data.find("\n", start)
247 if end == -1:
247 if end == -1:
248 end = len(data) - 1
248 end = len(data) - 1
249 if start == end:
249 if start == end:
250 return ''
250 return ''
251 return self.data[start:end]
251 return self.data[start:end]
252
252
253 def __getitem__(self, key):
253 def __getitem__(self, key):
254 if not isinstance(key, str):
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
256 needle = self.bsearch(key)
257 if needle == -1:
257 if needle == -1:
258 raise KeyError
258 raise KeyError
259 data, pos = self._get(needle)
259 data, pos = self._get(needle)
260 if pos == -1:
260 if pos == -1:
261 return (data[1], data[2])
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
267 return (hashval, flags)
268
268
269 def __delitem__(self, key):
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
270 needle, found = self.bsearch2(key)
271 if not found:
271 if not found:
272 raise KeyError
272 raise KeyError
273 cur = self.positions[needle]
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
278
279 def __setitem__(self, key, value):
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
287 flags = value[1]
288 if len(hashval) == 22:
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
292 needle, found = self.bsearch2(key)
293 if found:
293 if found:
294 # put the item
294 # put the item
295 pos = self.positions[needle]
295 pos = self.positions[needle]
296 if pos < 0:
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
298 else:
299 # just don't bother
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
301 self.positions[needle] = -len(self.extradata)
302 else:
302 else:
303 # not found, put it in with extra positions
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
308 self.extrainfo[needle:])
309
309
310 def copy(self):
310 def copy(self):
311 # XXX call _compact like in C?
311 # XXX call _compact like in C?
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 self.extradata)
313 self.extradata)
314
314
315 def _compact(self):
315 def _compact(self):
316 # hopefully not called TOO often
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
317 if len(self.extradata) == 0:
318 return
318 return
319 l = []
319 l = []
320 last_cut = 0
320 last_cut = 0
321 i = 0
321 i = 0
322 offset = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
326 cur = self.positions[i]
327 last_cut = cur
327 last_cut = cur
328 while True:
328 while True:
329 self.positions[i] = offset
329 self.positions[i] = offset
330 i += 1
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
332 break
333 offset += self.positions[i] - cur
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
336 if end_cut != -1:
337 end_cut += 1
337 end_cut += 1
338 offset += end_cut - cur
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
339 l.append(self.data[last_cut:end_cut])
340 else:
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
344 l.append(self._pack(t))
345 self.positions[i] = offset
345 self.positions[i] = offset
346 if len(t[1]) > 20:
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
348 offset += len(l[-1])
349 i += 1
349 i += 1
350 self.data = ''.join(l)
350 self.data = ''.join(l)
351 self.extradata = []
351 self.extradata = []
352
352
353 def _pack(self, d):
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
355
356 def text(self):
356 def text(self):
357 self._compact()
357 self._compact()
358 return self.data
358 return self.data
359
359
360 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
361 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
362 # XXX think whether efficiency matters here
363 diff = {}
363 diff = {}
364
364
365 for fn, e1, flags in self.iterentries():
365 for fn, e1, flags in self.iterentries():
366 if fn not in m2:
366 if fn not in m2:
367 diff[fn] = (e1, flags), (None, '')
367 diff[fn] = (e1, flags), (None, '')
368 else:
368 else:
369 e2 = m2[fn]
369 e2 = m2[fn]
370 if (e1, flags) != e2:
370 if (e1, flags) != e2:
371 diff[fn] = (e1, flags), e2
371 diff[fn] = (e1, flags), e2
372 elif clean:
372 elif clean:
373 diff[fn] = None
373 diff[fn] = None
374
374
375 for fn, e2, flags in m2.iterentries():
375 for fn, e2, flags in m2.iterentries():
376 if fn not in self:
376 if fn not in self:
377 diff[fn] = (None, ''), (e2, flags)
377 diff[fn] = (None, ''), (e2, flags)
378
378
379 return diff
379 return diff
380
380
381 def iterentries(self):
381 def iterentries(self):
382 return lazymanifestiterentries(self)
382 return lazymanifestiterentries(self)
383
383
384 def iterkeys(self):
384 def iterkeys(self):
385 return lazymanifestiter(self)
385 return lazymanifestiter(self)
386
386
387 def __iter__(self):
387 def __iter__(self):
388 return lazymanifestiter(self)
388 return lazymanifestiter(self)
389
389
390 def __len__(self):
390 def __len__(self):
391 return len(self.positions)
391 return len(self.positions)
392
392
393 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
394 # XXX should be optimized
395 c = _lazymanifest('')
395 c = _lazymanifest('')
396 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
397 if filterfn(f):
397 if filterfn(f):
398 c[f] = n, fl
398 c[f] = n, fl
399 return c
399 return c
400
400
401 try:
401 try:
402 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
403 except AttributeError:
403 except AttributeError:
404 pass
404 pass
405
405
406 class manifestdict(object):
406 class manifestdict(object):
407 def __init__(self, data=''):
407 def __init__(self, data=''):
408 if data.startswith('\0'):
408 if data.startswith('\0'):
409 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
410 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
411 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
412 self._lm[f] = n, fl
412 self._lm[f] = n, fl
413 else:
413 else:
414 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
415
415
416 def __getitem__(self, key):
416 def __getitem__(self, key):
417 return self._lm[key][0]
417 return self._lm[key][0]
418
418
419 def find(self, key):
419 def find(self, key):
420 return self._lm[key]
420 return self._lm[key]
421
421
422 def __len__(self):
422 def __len__(self):
423 return len(self._lm)
423 return len(self._lm)
424
424
425 def __setitem__(self, key, node):
425 def __setitem__(self, key, node):
426 self._lm[key] = node, self.flags(key, '')
426 self._lm[key] = node, self.flags(key, '')
427
427
428 def __contains__(self, key):
428 def __contains__(self, key):
429 return key in self._lm
429 return key in self._lm
430
430
431 def __delitem__(self, key):
431 def __delitem__(self, key):
432 del self._lm[key]
432 del self._lm[key]
433
433
434 def __iter__(self):
434 def __iter__(self):
435 return self._lm.__iter__()
435 return self._lm.__iter__()
436
436
437 def iterkeys(self):
437 def iterkeys(self):
438 return self._lm.iterkeys()
438 return self._lm.iterkeys()
439
439
440 def keys(self):
440 def keys(self):
441 return list(self.iterkeys())
441 return list(self.iterkeys())
442
442
443 def filesnotin(self, m2):
443 def filesnotin(self, m2):
444 '''Set of files in this manifest that are not in the other'''
444 '''Set of files in this manifest that are not in the other'''
445 diff = self.diff(m2)
445 diff = self.diff(m2)
446 files = set(filepath
446 files = set(filepath
447 for filepath, hashflags in diff.iteritems()
447 for filepath, hashflags in diff.iteritems()
448 if hashflags[1][0] is None)
448 if hashflags[1][0] is None)
449 return files
449 return files
450
450
451 @propertycache
451 @propertycache
452 def _dirs(self):
452 def _dirs(self):
453 return util.dirs(self)
453 return util.dirs(self)
454
454
455 def dirs(self):
455 def dirs(self):
456 return self._dirs
456 return self._dirs
457
457
458 def hasdir(self, dir):
458 def hasdir(self, dir):
459 return dir in self._dirs
459 return dir in self._dirs
460
460
461 def _filesfastpath(self, match):
461 def _filesfastpath(self, match):
462 '''Checks whether we can correctly and quickly iterate over matcher
462 '''Checks whether we can correctly and quickly iterate over matcher
463 files instead of over manifest files.'''
463 files instead of over manifest files.'''
464 files = match.files()
464 files = match.files()
465 return (len(files) < 100 and (match.isexact() or
465 return (len(files) < 100 and (match.isexact() or
466 (match.prefix() and all(fn in self for fn in files))))
466 (match.prefix() and all(fn in self for fn in files))))
467
467
468 def walk(self, match):
468 def walk(self, match):
469 '''Generates matching file names.
469 '''Generates matching file names.
470
470
471 Equivalent to manifest.matches(match).iterkeys(), but without creating
471 Equivalent to manifest.matches(match).iterkeys(), but without creating
472 an entirely new manifest.
472 an entirely new manifest.
473
473
474 It also reports nonexistent files by marking them bad with match.bad().
474 It also reports nonexistent files by marking them bad with match.bad().
475 '''
475 '''
476 if match.always():
476 if match.always():
477 for f in iter(self):
477 for f in iter(self):
478 yield f
478 yield f
479 return
479 return
480
480
481 fset = set(match.files())
481 fset = set(match.files())
482
482
483 # avoid the entire walk if we're only looking for specific files
483 # avoid the entire walk if we're only looking for specific files
484 if self._filesfastpath(match):
484 if self._filesfastpath(match):
485 for fn in sorted(fset):
485 for fn in sorted(fset):
486 yield fn
486 yield fn
487 return
487 return
488
488
489 for fn in self:
489 for fn in self:
490 if fn in fset:
490 if fn in fset:
491 # specified pattern is the exact name
491 # specified pattern is the exact name
492 fset.remove(fn)
492 fset.remove(fn)
493 if match(fn):
493 if match(fn):
494 yield fn
494 yield fn
495
495
496 # for dirstate.walk, files=['.'] means "walk the whole tree".
496 # for dirstate.walk, files=['.'] means "walk the whole tree".
497 # follow that here, too
497 # follow that here, too
498 fset.discard('.')
498 fset.discard('.')
499
499
500 for fn in sorted(fset):
500 for fn in sorted(fset):
501 if not self.hasdir(fn):
501 if not self.hasdir(fn):
502 match.bad(fn, None)
502 match.bad(fn, None)
503
503
504 def matches(self, match):
504 def matches(self, match):
505 '''generate a new manifest filtered by the match argument'''
505 '''generate a new manifest filtered by the match argument'''
506 if match.always():
506 if match.always():
507 return self.copy()
507 return self.copy()
508
508
509 if self._filesfastpath(match):
509 if self._filesfastpath(match):
510 m = manifestdict()
510 m = manifestdict()
511 lm = self._lm
511 lm = self._lm
512 for fn in match.files():
512 for fn in match.files():
513 if fn in lm:
513 if fn in lm:
514 m._lm[fn] = lm[fn]
514 m._lm[fn] = lm[fn]
515 return m
515 return m
516
516
517 m = manifestdict()
517 m = manifestdict()
518 m._lm = self._lm.filtercopy(match)
518 m._lm = self._lm.filtercopy(match)
519 return m
519 return m
520
520
521 def diff(self, m2, clean=False):
521 def diff(self, m2, clean=False):
522 '''Finds changes between the current manifest and m2.
522 '''Finds changes between the current manifest and m2.
523
523
524 Args:
524 Args:
525 m2: the manifest to which this manifest should be compared.
525 m2: the manifest to which this manifest should be compared.
526 clean: if true, include files unchanged between these manifests
526 clean: if true, include files unchanged between these manifests
527 with a None value in the returned dictionary.
527 with a None value in the returned dictionary.
528
528
529 The result is returned as a dict with filename as key and
529 The result is returned as a dict with filename as key and
530 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
530 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
531 nodeid in the current/other manifest and fl1/fl2 is the flag
531 nodeid in the current/other manifest and fl1/fl2 is the flag
532 in the current/other manifest. Where the file does not exist,
532 in the current/other manifest. Where the file does not exist,
533 the nodeid will be None and the flags will be the empty
533 the nodeid will be None and the flags will be the empty
534 string.
534 string.
535 '''
535 '''
536 return self._lm.diff(m2._lm, clean)
536 return self._lm.diff(m2._lm, clean)
537
537
538 def setflag(self, key, flag):
538 def setflag(self, key, flag):
539 self._lm[key] = self[key], flag
539 self._lm[key] = self[key], flag
540
540
541 def get(self, key, default=None):
541 def get(self, key, default=None):
542 try:
542 try:
543 return self._lm[key][0]
543 return self._lm[key][0]
544 except KeyError:
544 except KeyError:
545 return default
545 return default
546
546
547 def flags(self, key, default=''):
547 def flags(self, key, default=''):
548 try:
548 try:
549 return self._lm[key][1]
549 return self._lm[key][1]
550 except KeyError:
550 except KeyError:
551 return default
551 return default
552
552
553 def copy(self):
553 def copy(self):
554 c = manifestdict()
554 c = manifestdict()
555 c._lm = self._lm.copy()
555 c._lm = self._lm.copy()
556 return c
556 return c
557
557
558 def iteritems(self):
558 def iteritems(self):
559 return (x[:2] for x in self._lm.iterentries())
559 return (x[:2] for x in self._lm.iterentries())
560
560
561 def iterentries(self):
561 def iterentries(self):
562 return self._lm.iterentries()
562 return self._lm.iterentries()
563
563
564 def text(self, usemanifestv2=False):
564 def text(self, usemanifestv2=False):
565 if usemanifestv2:
565 if usemanifestv2:
566 return _textv2(self._lm.iterentries())
566 return _textv2(self._lm.iterentries())
567 else:
567 else:
568 # use (probably) native version for v1
568 # use (probably) native version for v1
569 return self._lm.text()
569 return self._lm.text()
570
570
571 def fastdelta(self, base, changes):
571 def fastdelta(self, base, changes):
572 """Given a base manifest text as an array.array and a list of changes
572 """Given a base manifest text as an array.array and a list of changes
573 relative to that text, compute a delta that can be used by revlog.
573 relative to that text, compute a delta that can be used by revlog.
574 """
574 """
575 delta = []
575 delta = []
576 dstart = None
576 dstart = None
577 dend = None
577 dend = None
578 dline = [""]
578 dline = [""]
579 start = 0
579 start = 0
580 # zero copy representation of base as a buffer
580 # zero copy representation of base as a buffer
581 addbuf = util.buffer(base)
581 addbuf = util.buffer(base)
582
582
583 changes = list(changes)
583 changes = list(changes)
584 if len(changes) < 1000:
584 if len(changes) < 1000:
585 # start with a readonly loop that finds the offset of
585 # start with a readonly loop that finds the offset of
586 # each line and creates the deltas
586 # each line and creates the deltas
587 for f, todelete in changes:
587 for f, todelete in changes:
588 # bs will either be the index of the item or the insert point
588 # bs will either be the index of the item or the insert point
589 start, end = _msearch(addbuf, f, start)
589 start, end = _msearch(addbuf, f, start)
590 if not todelete:
590 if not todelete:
591 h, fl = self._lm[f]
591 h, fl = self._lm[f]
592 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
592 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
593 else:
593 else:
594 if start == end:
594 if start == end:
595 # item we want to delete was not found, error out
595 # item we want to delete was not found, error out
596 raise AssertionError(
596 raise AssertionError(
597 _("failed to remove %s from manifest") % f)
597 _("failed to remove %s from manifest") % f)
598 l = ""
598 l = ""
599 if dstart is not None and dstart <= start and dend >= start:
599 if dstart is not None and dstart <= start and dend >= start:
600 if dend < end:
600 if dend < end:
601 dend = end
601 dend = end
602 if l:
602 if l:
603 dline.append(l)
603 dline.append(l)
604 else:
604 else:
605 if dstart is not None:
605 if dstart is not None:
606 delta.append([dstart, dend, "".join(dline)])
606 delta.append([dstart, dend, "".join(dline)])
607 dstart = start
607 dstart = start
608 dend = end
608 dend = end
609 dline = [l]
609 dline = [l]
610
610
611 if dstart is not None:
611 if dstart is not None:
612 delta.append([dstart, dend, "".join(dline)])
612 delta.append([dstart, dend, "".join(dline)])
613 # apply the delta to the base, and get a delta for addrevision
613 # apply the delta to the base, and get a delta for addrevision
614 deltatext, arraytext = _addlistdelta(base, delta)
614 deltatext, arraytext = _addlistdelta(base, delta)
615 else:
615 else:
616 # For large changes, it's much cheaper to just build the text and
616 # For large changes, it's much cheaper to just build the text and
617 # diff it.
617 # diff it.
618 arraytext = array.array('c', self.text())
618 arraytext = array.array('c', self.text())
619 deltatext = mdiff.textdiff(base, arraytext)
619 deltatext = mdiff.textdiff(base, arraytext)
620
620
621 return arraytext, deltatext
621 return arraytext, deltatext
622
622
623 def _msearch(m, s, lo=0, hi=None):
623 def _msearch(m, s, lo=0, hi=None):
624 '''return a tuple (start, end) that says where to find s within m.
624 '''return a tuple (start, end) that says where to find s within m.
625
625
626 If the string is found m[start:end] are the line containing
626 If the string is found m[start:end] are the line containing
627 that string. If start == end the string was not found and
627 that string. If start == end the string was not found and
628 they indicate the proper sorted insertion point.
628 they indicate the proper sorted insertion point.
629
629
630 m should be a buffer or a string
630 m should be a buffer or a string
631 s is a string'''
631 s is a string'''
632 def advance(i, c):
632 def advance(i, c):
633 while i < lenm and m[i] != c:
633 while i < lenm and m[i] != c:
634 i += 1
634 i += 1
635 return i
635 return i
636 if not s:
636 if not s:
637 return (lo, lo)
637 return (lo, lo)
638 lenm = len(m)
638 lenm = len(m)
639 if not hi:
639 if not hi:
640 hi = lenm
640 hi = lenm
641 while lo < hi:
641 while lo < hi:
642 mid = (lo + hi) // 2
642 mid = (lo + hi) // 2
643 start = mid
643 start = mid
644 while start > 0 and m[start - 1] != '\n':
644 while start > 0 and m[start - 1] != '\n':
645 start -= 1
645 start -= 1
646 end = advance(start, '\0')
646 end = advance(start, '\0')
647 if m[start:end] < s:
647 if m[start:end] < s:
648 # we know that after the null there are 40 bytes of sha1
648 # we know that after the null there are 40 bytes of sha1
649 # this translates to the bisect lo = mid + 1
649 # this translates to the bisect lo = mid + 1
650 lo = advance(end + 40, '\n') + 1
650 lo = advance(end + 40, '\n') + 1
651 else:
651 else:
652 # this translates to the bisect hi = mid
652 # this translates to the bisect hi = mid
653 hi = start
653 hi = start
654 end = advance(lo, '\0')
654 end = advance(lo, '\0')
655 found = m[lo:end]
655 found = m[lo:end]
656 if s == found:
656 if s == found:
657 # we know that after the null there are 40 bytes of sha1
657 # we know that after the null there are 40 bytes of sha1
658 end = advance(end + 40, '\n')
658 end = advance(end + 40, '\n')
659 return (lo, end + 1)
659 return (lo, end + 1)
660 else:
660 else:
661 return (lo, lo)
661 return (lo, lo)
662
662
663 def _checkforbidden(l):
663 def _checkforbidden(l):
664 """Check filenames for illegal characters."""
664 """Check filenames for illegal characters."""
665 for f in l:
665 for f in l:
666 if '\n' in f or '\r' in f:
666 if '\n' in f or '\r' in f:
667 raise error.RevlogError(
667 raise error.RevlogError(
668 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
668 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
669
669
670
670
671 # apply the changes collected during the bisect loop to our addlist
671 # apply the changes collected during the bisect loop to our addlist
672 # return a delta suitable for addrevision
672 # return a delta suitable for addrevision
673 def _addlistdelta(addlist, x):
673 def _addlistdelta(addlist, x):
674 # for large addlist arrays, building a new array is cheaper
674 # for large addlist arrays, building a new array is cheaper
675 # than repeatedly modifying the existing one
675 # than repeatedly modifying the existing one
676 currentposition = 0
676 currentposition = 0
677 newaddlist = array.array('c')
677 newaddlist = array.array('c')
678
678
679 for start, end, content in x:
679 for start, end, content in x:
680 newaddlist += addlist[currentposition:start]
680 newaddlist += addlist[currentposition:start]
681 if content:
681 if content:
682 newaddlist += array.array('c', content)
682 newaddlist += array.array('c', content)
683
683
684 currentposition = end
684 currentposition = end
685
685
686 newaddlist += addlist[currentposition:]
686 newaddlist += addlist[currentposition:]
687
687
688 deltatext = "".join(struct.pack(">lll", start, end, len(content))
688 deltatext = "".join(struct.pack(">lll", start, end, len(content))
689 + content for start, end, content in x)
689 + content for start, end, content in x)
690 return deltatext, newaddlist
690 return deltatext, newaddlist
691
691
692 def _splittopdir(f):
692 def _splittopdir(f):
693 if '/' in f:
693 if '/' in f:
694 dir, subpath = f.split('/', 1)
694 dir, subpath = f.split('/', 1)
695 return dir + '/', subpath
695 return dir + '/', subpath
696 else:
696 else:
697 return '', f
697 return '', f
698
698
699 _noop = lambda s: None
699 _noop = lambda s: None
700
700
701 class treemanifest(object):
701 class treemanifest(object):
702 def __init__(self, dir='', text=''):
702 def __init__(self, dir='', text=''):
703 self._dir = dir
703 self._dir = dir
704 self._node = revlog.nullid
704 self._node = revlog.nullid
705 self._loadfunc = _noop
705 self._loadfunc = _noop
706 self._copyfunc = _noop
706 self._copyfunc = _noop
707 self._dirty = False
707 self._dirty = False
708 self._dirs = {}
708 self._dirs = {}
709 # Using _lazymanifest here is a little slower than plain old dicts
709 # Using _lazymanifest here is a little slower than plain old dicts
710 self._files = {}
710 self._files = {}
711 self._flags = {}
711 self._flags = {}
712 if text:
712 if text:
713 def readsubtree(subdir, subm):
713 def readsubtree(subdir, subm):
714 raise AssertionError('treemanifest constructor only accepts '
714 raise AssertionError('treemanifest constructor only accepts '
715 'flat manifests')
715 'flat manifests')
716 self.parse(text, readsubtree)
716 self.parse(text, readsubtree)
717 self._dirty = True # Mark flat manifest dirty after parsing
717 self._dirty = True # Mark flat manifest dirty after parsing
718
718
719 def _subpath(self, path):
719 def _subpath(self, path):
720 return self._dir + path
720 return self._dir + path
721
721
722 def __len__(self):
722 def __len__(self):
723 self._load()
723 self._load()
724 size = len(self._files)
724 size = len(self._files)
725 for m in self._dirs.values():
725 for m in self._dirs.values():
726 size += m.__len__()
726 size += m.__len__()
727 return size
727 return size
728
728
729 def _isempty(self):
729 def _isempty(self):
730 self._load() # for consistency; already loaded by all callers
730 self._load() # for consistency; already loaded by all callers
731 return (not self._files and (not self._dirs or
731 return (not self._files and (not self._dirs or
732 all(m._isempty() for m in self._dirs.values())))
732 all(m._isempty() for m in self._dirs.values())))
733
733
734 def __repr__(self):
734 def __repr__(self):
735 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
735 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
736 (self._dir, revlog.hex(self._node),
736 (self._dir, revlog.hex(self._node),
737 bool(self._loadfunc is _noop),
737 bool(self._loadfunc is _noop),
738 self._dirty, id(self)))
738 self._dirty, id(self)))
739
739
740 def dir(self):
740 def dir(self):
741 '''The directory that this tree manifest represents, including a
741 '''The directory that this tree manifest represents, including a
742 trailing '/'. Empty string for the repo root directory.'''
742 trailing '/'. Empty string for the repo root directory.'''
743 return self._dir
743 return self._dir
744
744
745 def node(self):
745 def node(self):
746 '''This node of this instance. nullid for unsaved instances. Should
746 '''This node of this instance. nullid for unsaved instances. Should
747 be updated when the instance is read or written from a revlog.
747 be updated when the instance is read or written from a revlog.
748 '''
748 '''
749 assert not self._dirty
749 assert not self._dirty
750 return self._node
750 return self._node
751
751
752 def setnode(self, node):
752 def setnode(self, node):
753 self._node = node
753 self._node = node
754 self._dirty = False
754 self._dirty = False
755
755
756 def iterentries(self):
756 def iterentries(self):
757 self._load()
757 self._load()
758 for p, n in sorted(self._dirs.items() + self._files.items()):
758 for p, n in sorted(self._dirs.items() + self._files.items()):
759 if p in self._files:
759 if p in self._files:
760 yield self._subpath(p), n, self._flags.get(p, '')
760 yield self._subpath(p), n, self._flags.get(p, '')
761 else:
761 else:
762 for x in n.iterentries():
762 for x in n.iterentries():
763 yield x
763 yield x
764
764
765 def iteritems(self):
765 def iteritems(self):
766 self._load()
766 self._load()
767 for p, n in sorted(self._dirs.items() + self._files.items()):
767 for p, n in sorted(self._dirs.items() + self._files.items()):
768 if p in self._files:
768 if p in self._files:
769 yield self._subpath(p), n
769 yield self._subpath(p), n
770 else:
770 else:
771 for f, sn in n.iteritems():
771 for f, sn in n.iteritems():
772 yield f, sn
772 yield f, sn
773
773
774 def iterkeys(self):
774 def iterkeys(self):
775 self._load()
775 self._load()
776 for p in sorted(self._dirs.keys() + self._files.keys()):
776 for p in sorted(self._dirs.keys() + self._files.keys()):
777 if p in self._files:
777 if p in self._files:
778 yield self._subpath(p)
778 yield self._subpath(p)
779 else:
779 else:
780 for f in self._dirs[p].iterkeys():
780 for f in self._dirs[p].iterkeys():
781 yield f
781 yield f
782
782
783 def keys(self):
783 def keys(self):
784 return list(self.iterkeys())
784 return list(self.iterkeys())
785
785
786 def __iter__(self):
786 def __iter__(self):
787 return self.iterkeys()
787 return self.iterkeys()
788
788
789 def __contains__(self, f):
789 def __contains__(self, f):
790 if f is None:
790 if f is None:
791 return False
791 return False
792 self._load()
792 self._load()
793 dir, subpath = _splittopdir(f)
793 dir, subpath = _splittopdir(f)
794 if dir:
794 if dir:
795 if dir not in self._dirs:
795 if dir not in self._dirs:
796 return False
796 return False
797 return self._dirs[dir].__contains__(subpath)
797 return self._dirs[dir].__contains__(subpath)
798 else:
798 else:
799 return f in self._files
799 return f in self._files
800
800
801 def get(self, f, default=None):
801 def get(self, f, default=None):
802 self._load()
802 self._load()
803 dir, subpath = _splittopdir(f)
803 dir, subpath = _splittopdir(f)
804 if dir:
804 if dir:
805 if dir not in self._dirs:
805 if dir not in self._dirs:
806 return default
806 return default
807 return self._dirs[dir].get(subpath, default)
807 return self._dirs[dir].get(subpath, default)
808 else:
808 else:
809 return self._files.get(f, default)
809 return self._files.get(f, default)
810
810
811 def __getitem__(self, f):
811 def __getitem__(self, f):
812 self._load()
812 self._load()
813 dir, subpath = _splittopdir(f)
813 dir, subpath = _splittopdir(f)
814 if dir:
814 if dir:
815 return self._dirs[dir].__getitem__(subpath)
815 return self._dirs[dir].__getitem__(subpath)
816 else:
816 else:
817 return self._files[f]
817 return self._files[f]
818
818
819 def flags(self, f):
819 def flags(self, f):
820 self._load()
820 self._load()
821 dir, subpath = _splittopdir(f)
821 dir, subpath = _splittopdir(f)
822 if dir:
822 if dir:
823 if dir not in self._dirs:
823 if dir not in self._dirs:
824 return ''
824 return ''
825 return self._dirs[dir].flags(subpath)
825 return self._dirs[dir].flags(subpath)
826 else:
826 else:
827 if f in self._dirs:
827 if f in self._dirs:
828 return ''
828 return ''
829 return self._flags.get(f, '')
829 return self._flags.get(f, '')
830
830
831 def find(self, f):
831 def find(self, f):
832 self._load()
832 self._load()
833 dir, subpath = _splittopdir(f)
833 dir, subpath = _splittopdir(f)
834 if dir:
834 if dir:
835 return self._dirs[dir].find(subpath)
835 return self._dirs[dir].find(subpath)
836 else:
836 else:
837 return self._files[f], self._flags.get(f, '')
837 return self._files[f], self._flags.get(f, '')
838
838
839 def __delitem__(self, f):
839 def __delitem__(self, f):
840 self._load()
840 self._load()
841 dir, subpath = _splittopdir(f)
841 dir, subpath = _splittopdir(f)
842 if dir:
842 if dir:
843 self._dirs[dir].__delitem__(subpath)
843 self._dirs[dir].__delitem__(subpath)
844 # If the directory is now empty, remove it
844 # If the directory is now empty, remove it
845 if self._dirs[dir]._isempty():
845 if self._dirs[dir]._isempty():
846 del self._dirs[dir]
846 del self._dirs[dir]
847 else:
847 else:
848 del self._files[f]
848 del self._files[f]
849 if f in self._flags:
849 if f in self._flags:
850 del self._flags[f]
850 del self._flags[f]
851 self._dirty = True
851 self._dirty = True
852
852
853 def __setitem__(self, f, n):
853 def __setitem__(self, f, n):
854 assert n is not None
854 assert n is not None
855 self._load()
855 self._load()
856 dir, subpath = _splittopdir(f)
856 dir, subpath = _splittopdir(f)
857 if dir:
857 if dir:
858 if dir not in self._dirs:
858 if dir not in self._dirs:
859 self._dirs[dir] = treemanifest(self._subpath(dir))
859 self._dirs[dir] = treemanifest(self._subpath(dir))
860 self._dirs[dir].__setitem__(subpath, n)
860 self._dirs[dir].__setitem__(subpath, n)
861 else:
861 else:
862 self._files[f] = n[:21] # to match manifestdict's behavior
862 self._files[f] = n[:21] # to match manifestdict's behavior
863 self._dirty = True
863 self._dirty = True
864
864
865 def _load(self):
865 def _load(self):
866 if self._loadfunc is not _noop:
866 if self._loadfunc is not _noop:
867 lf, self._loadfunc = self._loadfunc, _noop
867 lf, self._loadfunc = self._loadfunc, _noop
868 lf(self)
868 lf(self)
869 elif self._copyfunc is not _noop:
869 elif self._copyfunc is not _noop:
870 cf, self._copyfunc = self._copyfunc, _noop
870 cf, self._copyfunc = self._copyfunc, _noop
871 cf(self)
871 cf(self)
872
872
873 def setflag(self, f, flags):
873 def setflag(self, f, flags):
874 """Set the flags (symlink, executable) for path f."""
874 """Set the flags (symlink, executable) for path f."""
875 self._load()
875 self._load()
876 dir, subpath = _splittopdir(f)
876 dir, subpath = _splittopdir(f)
877 if dir:
877 if dir:
878 if dir not in self._dirs:
878 if dir not in self._dirs:
879 self._dirs[dir] = treemanifest(self._subpath(dir))
879 self._dirs[dir] = treemanifest(self._subpath(dir))
880 self._dirs[dir].setflag(subpath, flags)
880 self._dirs[dir].setflag(subpath, flags)
881 else:
881 else:
882 self._flags[f] = flags
882 self._flags[f] = flags
883 self._dirty = True
883 self._dirty = True
884
884
885 def copy(self):
885 def copy(self):
886 copy = treemanifest(self._dir)
886 copy = treemanifest(self._dir)
887 copy._node = self._node
887 copy._node = self._node
888 copy._dirty = self._dirty
888 copy._dirty = self._dirty
889 if self._copyfunc is _noop:
889 if self._copyfunc is _noop:
890 def _copyfunc(s):
890 def _copyfunc(s):
891 self._load()
891 self._load()
892 for d in self._dirs:
892 for d in self._dirs:
893 s._dirs[d] = self._dirs[d].copy()
893 s._dirs[d] = self._dirs[d].copy()
894 s._files = dict.copy(self._files)
894 s._files = dict.copy(self._files)
895 s._flags = dict.copy(self._flags)
895 s._flags = dict.copy(self._flags)
896 if self._loadfunc is _noop:
896 if self._loadfunc is _noop:
897 _copyfunc(copy)
897 _copyfunc(copy)
898 else:
898 else:
899 copy._copyfunc = _copyfunc
899 copy._copyfunc = _copyfunc
900 else:
900 else:
901 copy._copyfunc = self._copyfunc
901 copy._copyfunc = self._copyfunc
902 return copy
902 return copy
903
903
904 def filesnotin(self, m2):
904 def filesnotin(self, m2):
905 '''Set of files in this manifest that are not in the other'''
905 '''Set of files in this manifest that are not in the other'''
906 files = set()
906 files = set()
907 def _filesnotin(t1, t2):
907 def _filesnotin(t1, t2):
908 if t1._node == t2._node and not t1._dirty and not t2._dirty:
908 if t1._node == t2._node and not t1._dirty and not t2._dirty:
909 return
909 return
910 t1._load()
910 t1._load()
911 t2._load()
911 t2._load()
912 for d, m1 in t1._dirs.iteritems():
912 for d, m1 in t1._dirs.iteritems():
913 if d in t2._dirs:
913 if d in t2._dirs:
914 m2 = t2._dirs[d]
914 m2 = t2._dirs[d]
915 _filesnotin(m1, m2)
915 _filesnotin(m1, m2)
916 else:
916 else:
917 files.update(m1.iterkeys())
917 files.update(m1.iterkeys())
918
918
919 for fn in t1._files.iterkeys():
919 for fn in t1._files.iterkeys():
920 if fn not in t2._files:
920 if fn not in t2._files:
921 files.add(t1._subpath(fn))
921 files.add(t1._subpath(fn))
922
922
923 _filesnotin(self, m2)
923 _filesnotin(self, m2)
924 return files
924 return files
925
925
926 @propertycache
926 @propertycache
927 def _alldirs(self):
927 def _alldirs(self):
928 return util.dirs(self)
928 return util.dirs(self)
929
929
930 def dirs(self):
930 def dirs(self):
931 return self._alldirs
931 return self._alldirs
932
932
933 def hasdir(self, dir):
933 def hasdir(self, dir):
934 self._load()
934 self._load()
935 topdir, subdir = _splittopdir(dir)
935 topdir, subdir = _splittopdir(dir)
936 if topdir:
936 if topdir:
937 if topdir in self._dirs:
937 if topdir in self._dirs:
938 return self._dirs[topdir].hasdir(subdir)
938 return self._dirs[topdir].hasdir(subdir)
939 return False
939 return False
940 return (dir + '/') in self._dirs
940 return (dir + '/') in self._dirs
941
941
942 def walk(self, match):
942 def walk(self, match):
943 '''Generates matching file names.
943 '''Generates matching file names.
944
944
945 Equivalent to manifest.matches(match).iterkeys(), but without creating
945 Equivalent to manifest.matches(match).iterkeys(), but without creating
946 an entirely new manifest.
946 an entirely new manifest.
947
947
948 It also reports nonexistent files by marking them bad with match.bad().
948 It also reports nonexistent files by marking them bad with match.bad().
949 '''
949 '''
950 if match.always():
950 if match.always():
951 for f in iter(self):
951 for f in iter(self):
952 yield f
952 yield f
953 return
953 return
954
954
955 fset = set(match.files())
955 fset = set(match.files())
956
956
957 for fn in self._walk(match):
957 for fn in self._walk(match):
958 if fn in fset:
958 if fn in fset:
959 # specified pattern is the exact name
959 # specified pattern is the exact name
960 fset.remove(fn)
960 fset.remove(fn)
961 yield fn
961 yield fn
962
962
963 # for dirstate.walk, files=['.'] means "walk the whole tree".
963 # for dirstate.walk, files=['.'] means "walk the whole tree".
964 # follow that here, too
964 # follow that here, too
965 fset.discard('.')
965 fset.discard('.')
966
966
967 for fn in sorted(fset):
967 for fn in sorted(fset):
968 if not self.hasdir(fn):
968 if not self.hasdir(fn):
969 match.bad(fn, None)
969 match.bad(fn, None)
970
970
971 def _walk(self, match):
971 def _walk(self, match):
972 '''Recursively generates matching file names for walk().'''
972 '''Recursively generates matching file names for walk().'''
973 if not match.visitdir(self._dir[:-1] or '.'):
973 if not match.visitdir(self._dir[:-1] or '.'):
974 return
974 return
975
975
976 # yield this dir's files and walk its submanifests
976 # yield this dir's files and walk its submanifests
977 self._load()
977 self._load()
978 for p in sorted(self._dirs.keys() + self._files.keys()):
978 for p in sorted(self._dirs.keys() + self._files.keys()):
979 if p in self._files:
979 if p in self._files:
980 fullp = self._subpath(p)
980 fullp = self._subpath(p)
981 if match(fullp):
981 if match(fullp):
982 yield fullp
982 yield fullp
983 else:
983 else:
984 for f in self._dirs[p]._walk(match):
984 for f in self._dirs[p]._walk(match):
985 yield f
985 yield f
986
986
987 def matches(self, match):
987 def matches(self, match):
988 '''generate a new manifest filtered by the match argument'''
988 '''generate a new manifest filtered by the match argument'''
989 if match.always():
989 if match.always():
990 return self.copy()
990 return self.copy()
991
991
992 return self._matches(match)
992 return self._matches(match)
993
993
994 def _matches(self, match):
994 def _matches(self, match):
995 '''recursively generate a new manifest filtered by the match argument.
995 '''recursively generate a new manifest filtered by the match argument.
996 '''
996 '''
997
997
998 visit = match.visitdir(self._dir[:-1] or '.')
998 visit = match.visitdir(self._dir[:-1] or '.')
999 if visit == 'all':
999 if visit == 'all':
1000 return self.copy()
1000 return self.copy()
1001 ret = treemanifest(self._dir)
1001 ret = treemanifest(self._dir)
1002 if not visit:
1002 if not visit:
1003 return ret
1003 return ret
1004
1004
1005 self._load()
1005 self._load()
1006 for fn in self._files:
1006 for fn in self._files:
1007 fullp = self._subpath(fn)
1007 fullp = self._subpath(fn)
1008 if not match(fullp):
1008 if not match(fullp):
1009 continue
1009 continue
1010 ret._files[fn] = self._files[fn]
1010 ret._files[fn] = self._files[fn]
1011 if fn in self._flags:
1011 if fn in self._flags:
1012 ret._flags[fn] = self._flags[fn]
1012 ret._flags[fn] = self._flags[fn]
1013
1013
1014 for dir, subm in self._dirs.iteritems():
1014 for dir, subm in self._dirs.iteritems():
1015 m = subm._matches(match)
1015 m = subm._matches(match)
1016 if not m._isempty():
1016 if not m._isempty():
1017 ret._dirs[dir] = m
1017 ret._dirs[dir] = m
1018
1018
1019 if not ret._isempty():
1019 if not ret._isempty():
1020 ret._dirty = True
1020 ret._dirty = True
1021 return ret
1021 return ret
1022
1022
1023 def diff(self, m2, clean=False):
1023 def diff(self, m2, clean=False):
1024 '''Finds changes between the current manifest and m2.
1024 '''Finds changes between the current manifest and m2.
1025
1025
1026 Args:
1026 Args:
1027 m2: the manifest to which this manifest should be compared.
1027 m2: the manifest to which this manifest should be compared.
1028 clean: if true, include files unchanged between these manifests
1028 clean: if true, include files unchanged between these manifests
1029 with a None value in the returned dictionary.
1029 with a None value in the returned dictionary.
1030
1030
1031 The result is returned as a dict with filename as key and
1031 The result is returned as a dict with filename as key and
1032 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1032 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1033 nodeid in the current/other manifest and fl1/fl2 is the flag
1033 nodeid in the current/other manifest and fl1/fl2 is the flag
1034 in the current/other manifest. Where the file does not exist,
1034 in the current/other manifest. Where the file does not exist,
1035 the nodeid will be None and the flags will be the empty
1035 the nodeid will be None and the flags will be the empty
1036 string.
1036 string.
1037 '''
1037 '''
1038 result = {}
1038 result = {}
1039 emptytree = treemanifest()
1039 emptytree = treemanifest()
1040 def _diff(t1, t2):
1040 def _diff(t1, t2):
1041 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1041 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1042 return
1042 return
1043 t1._load()
1043 t1._load()
1044 t2._load()
1044 t2._load()
1045 for d, m1 in t1._dirs.iteritems():
1045 for d, m1 in t1._dirs.iteritems():
1046 m2 = t2._dirs.get(d, emptytree)
1046 m2 = t2._dirs.get(d, emptytree)
1047 _diff(m1, m2)
1047 _diff(m1, m2)
1048
1048
1049 for d, m2 in t2._dirs.iteritems():
1049 for d, m2 in t2._dirs.iteritems():
1050 if d not in t1._dirs:
1050 if d not in t1._dirs:
1051 _diff(emptytree, m2)
1051 _diff(emptytree, m2)
1052
1052
1053 for fn, n1 in t1._files.iteritems():
1053 for fn, n1 in t1._files.iteritems():
1054 fl1 = t1._flags.get(fn, '')
1054 fl1 = t1._flags.get(fn, '')
1055 n2 = t2._files.get(fn, None)
1055 n2 = t2._files.get(fn, None)
1056 fl2 = t2._flags.get(fn, '')
1056 fl2 = t2._flags.get(fn, '')
1057 if n1 != n2 or fl1 != fl2:
1057 if n1 != n2 or fl1 != fl2:
1058 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1058 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1059 elif clean:
1059 elif clean:
1060 result[t1._subpath(fn)] = None
1060 result[t1._subpath(fn)] = None
1061
1061
1062 for fn, n2 in t2._files.iteritems():
1062 for fn, n2 in t2._files.iteritems():
1063 if fn not in t1._files:
1063 if fn not in t1._files:
1064 fl2 = t2._flags.get(fn, '')
1064 fl2 = t2._flags.get(fn, '')
1065 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1065 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1066
1066
1067 _diff(self, m2)
1067 _diff(self, m2)
1068 return result
1068 return result
1069
1069
1070 def unmodifiedsince(self, m2):
1070 def unmodifiedsince(self, m2):
1071 return not self._dirty and not m2._dirty and self._node == m2._node
1071 return not self._dirty and not m2._dirty and self._node == m2._node
1072
1072
1073 def parse(self, text, readsubtree):
1073 def parse(self, text, readsubtree):
1074 for f, n, fl in _parse(text):
1074 for f, n, fl in _parse(text):
1075 if fl == 't':
1075 if fl == 't':
1076 f = f + '/'
1076 f = f + '/'
1077 self._dirs[f] = readsubtree(self._subpath(f), n)
1077 self._dirs[f] = readsubtree(self._subpath(f), n)
1078 elif '/' in f:
1078 elif '/' in f:
1079 # This is a flat manifest, so use __setitem__ and setflag rather
1079 # This is a flat manifest, so use __setitem__ and setflag rather
1080 # than assigning directly to _files and _flags, so we can
1080 # than assigning directly to _files and _flags, so we can
1081 # assign a path in a subdirectory, and to mark dirty (compared
1081 # assign a path in a subdirectory, and to mark dirty (compared
1082 # to nullid).
1082 # to nullid).
1083 self[f] = n
1083 self[f] = n
1084 if fl:
1084 if fl:
1085 self.setflag(f, fl)
1085 self.setflag(f, fl)
1086 else:
1086 else:
1087 # Assigning to _files and _flags avoids marking as dirty,
1087 # Assigning to _files and _flags avoids marking as dirty,
1088 # and should be a little faster.
1088 # and should be a little faster.
1089 self._files[f] = n
1089 self._files[f] = n
1090 if fl:
1090 if fl:
1091 self._flags[f] = fl
1091 self._flags[f] = fl
1092
1092
1093 def text(self, usemanifestv2=False):
1093 def text(self, usemanifestv2=False):
1094 """Get the full data of this manifest as a bytestring."""
1094 """Get the full data of this manifest as a bytestring."""
1095 self._load()
1095 self._load()
1096 return _text(self.iterentries(), usemanifestv2)
1096 return _text(self.iterentries(), usemanifestv2)
1097
1097
1098 def dirtext(self, usemanifestv2=False):
1098 def dirtext(self, usemanifestv2=False):
1099 """Get the full data of this directory as a bytestring. Make sure that
1099 """Get the full data of this directory as a bytestring. Make sure that
1100 any submanifests have been written first, so their nodeids are correct.
1100 any submanifests have been written first, so their nodeids are correct.
1101 """
1101 """
1102 self._load()
1102 self._load()
1103 flags = self.flags
1103 flags = self.flags
1104 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1104 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1105 files = [(f, self._files[f], flags(f)) for f in self._files]
1105 files = [(f, self._files[f], flags(f)) for f in self._files]
1106 return _text(sorted(dirs + files), usemanifestv2)
1106 return _text(sorted(dirs + files), usemanifestv2)
1107
1107
1108 def read(self, gettext, readsubtree):
1108 def read(self, gettext, readsubtree):
1109 def _load_for_read(s):
1109 def _load_for_read(s):
1110 s.parse(gettext(), readsubtree)
1110 s.parse(gettext(), readsubtree)
1111 s._dirty = False
1111 s._dirty = False
1112 self._loadfunc = _load_for_read
1112 self._loadfunc = _load_for_read
1113
1113
1114 def writesubtrees(self, m1, m2, writesubtree):
1114 def writesubtrees(self, m1, m2, writesubtree):
1115 self._load() # for consistency; should never have any effect here
1115 self._load() # for consistency; should never have any effect here
1116 m1._load()
1116 m1._load()
1117 m2._load()
1117 m2._load()
1118 emptytree = treemanifest()
1118 emptytree = treemanifest()
1119 for d, subm in self._dirs.iteritems():
1119 for d, subm in self._dirs.iteritems():
1120 subp1 = m1._dirs.get(d, emptytree)._node
1120 subp1 = m1._dirs.get(d, emptytree)._node
1121 subp2 = m2._dirs.get(d, emptytree)._node
1121 subp2 = m2._dirs.get(d, emptytree)._node
1122 if subp1 == revlog.nullid:
1122 if subp1 == revlog.nullid:
1123 subp1, subp2 = subp2, subp1
1123 subp1, subp2 = subp2, subp1
1124 writesubtree(subm, subp1, subp2)
1124 writesubtree(subm, subp1, subp2)
1125
1125
1126 class manifestrevlog(revlog.revlog):
1126 class manifestrevlog(revlog.revlog):
1127 '''A revlog that stores manifest texts. This is responsible for caching the
1127 '''A revlog that stores manifest texts. This is responsible for caching the
1128 full-text manifest contents.
1128 full-text manifest contents.
1129 '''
1129 '''
1130 def __init__(self, opener, dir='', dirlogcache=None):
1130 def __init__(self, opener, dir='', dirlogcache=None):
1131 # During normal operations, we expect to deal with not more than four
1131 # During normal operations, we expect to deal with not more than four
1132 # revs at a time (such as during commit --amend). When rebasing large
1132 # revs at a time (such as during commit --amend). When rebasing large
1133 # stacks of commits, the number can go up, hence the config knob below.
1133 # stacks of commits, the number can go up, hence the config knob below.
1134 cachesize = 4
1134 cachesize = 4
1135 usetreemanifest = False
1135 usetreemanifest = False
1136 usemanifestv2 = False
1136 usemanifestv2 = False
1137 opts = getattr(opener, 'options', None)
1137 opts = getattr(opener, 'options', None)
1138 if opts is not None:
1138 if opts is not None:
1139 cachesize = opts.get('manifestcachesize', cachesize)
1139 cachesize = opts.get('manifestcachesize', cachesize)
1140 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1140 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1141 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1141 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1142
1142
1143 self._treeondisk = usetreemanifest
1143 self._treeondisk = usetreemanifest
1144 self._usemanifestv2 = usemanifestv2
1144 self._usemanifestv2 = usemanifestv2
1145
1145
1146 self._fulltextcache = util.lrucachedict(cachesize)
1146 self._fulltextcache = util.lrucachedict(cachesize)
1147
1147
1148 indexfile = "00manifest.i"
1148 indexfile = "00manifest.i"
1149 if dir:
1149 if dir:
1150 assert self._treeondisk, 'opts is %r' % opts
1150 assert self._treeondisk, 'opts is %r' % opts
1151 if not dir.endswith('/'):
1151 if not dir.endswith('/'):
1152 dir = dir + '/'
1152 dir = dir + '/'
1153 indexfile = "meta/" + dir + "00manifest.i"
1153 indexfile = "meta/" + dir + "00manifest.i"
1154 self._dir = dir
1154 self._dir = dir
1155 # The dirlogcache is kept on the root manifest log
1155 # The dirlogcache is kept on the root manifest log
1156 if dir:
1156 if dir:
1157 self._dirlogcache = dirlogcache
1157 self._dirlogcache = dirlogcache
1158 else:
1158 else:
1159 self._dirlogcache = {'': self}
1159 self._dirlogcache = {'': self}
1160
1160
1161 super(manifestrevlog, self).__init__(opener, indexfile,
1161 super(manifestrevlog, self).__init__(opener, indexfile,
1162 checkambig=bool(dir))
1162 checkambig=bool(dir))
1163
1163
1164 @property
1164 @property
1165 def fulltextcache(self):
1165 def fulltextcache(self):
1166 return self._fulltextcache
1166 return self._fulltextcache
1167
1167
1168 def clearcaches(self):
1168 def clearcaches(self):
1169 super(manifestrevlog, self).clearcaches()
1169 super(manifestrevlog, self).clearcaches()
1170 self._fulltextcache.clear()
1170 self._fulltextcache.clear()
1171 self._dirlogcache = {'': self}
1171 self._dirlogcache = {'': self}
1172
1172
1173 def dirlog(self, dir):
1173 def dirlog(self, dir):
1174 if dir:
1174 if dir:
1175 assert self._treeondisk
1175 assert self._treeondisk
1176 if dir not in self._dirlogcache:
1176 if dir not in self._dirlogcache:
1177 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1177 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1178 self._dirlogcache)
1178 self._dirlogcache)
1179 return self._dirlogcache[dir]
1179 return self._dirlogcache[dir]
1180
1180
1181 def add(self, m, transaction, link, p1, p2, added, removed):
1181 def add(self, m, transaction, link, p1, p2, added, removed):
1182 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1182 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1183 and not self._usemanifestv2):
1183 and not self._usemanifestv2):
1184 # If our first parent is in the manifest cache, we can
1184 # If our first parent is in the manifest cache, we can
1185 # compute a delta here using properties we know about the
1185 # compute a delta here using properties we know about the
1186 # manifest up-front, which may save time later for the
1186 # manifest up-front, which may save time later for the
1187 # revlog layer.
1187 # revlog layer.
1188
1188
1189 _checkforbidden(added)
1189 _checkforbidden(added)
1190 # combine the changed lists into one sorted iterator
1190 # combine the changed lists into one sorted iterator
1191 work = heapq.merge([(x, False) for x in added],
1191 work = heapq.merge([(x, False) for x in added],
1192 [(x, True) for x in removed])
1192 [(x, True) for x in removed])
1193
1193
1194 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1194 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1195 cachedelta = self.rev(p1), deltatext
1195 cachedelta = self.rev(p1), deltatext
1196 text = util.buffer(arraytext)
1196 text = util.buffer(arraytext)
1197 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1197 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1198 else:
1198 else:
1199 # The first parent manifest isn't already loaded, so we'll
1199 # The first parent manifest isn't already loaded, so we'll
1200 # just encode a fulltext of the manifest and pass that
1200 # just encode a fulltext of the manifest and pass that
1201 # through to the revlog layer, and let it handle the delta
1201 # through to the revlog layer, and let it handle the delta
1202 # process.
1202 # process.
1203 if self._treeondisk:
1203 if self._treeondisk:
1204 m1 = self.read(p1)
1204 m1 = self.read(p1)
1205 m2 = self.read(p2)
1205 m2 = self.read(p2)
1206 n = self._addtree(m, transaction, link, m1, m2)
1206 n = self._addtree(m, transaction, link, m1, m2)
1207 arraytext = None
1207 arraytext = None
1208 else:
1208 else:
1209 text = m.text(self._usemanifestv2)
1209 text = m.text(self._usemanifestv2)
1210 n = self.addrevision(text, transaction, link, p1, p2)
1210 n = self.addrevision(text, transaction, link, p1, p2)
1211 arraytext = array.array('c', text)
1211 arraytext = array.array('c', text)
1212
1212
1213 if arraytext is not None:
1213 if arraytext is not None:
1214 self.fulltextcache[n] = arraytext
1214 self.fulltextcache[n] = arraytext
1215
1215
1216 return n
1216 return n
1217
1217
1218 def _addtree(self, m, transaction, link, m1, m2):
1218 def _addtree(self, m, transaction, link, m1, m2):
1219 # If the manifest is unchanged compared to one parent,
1219 # If the manifest is unchanged compared to one parent,
1220 # don't write a new revision
1220 # don't write a new revision
1221 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1221 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1222 return m.node()
1222 return m.node()
1223 def writesubtree(subm, subp1, subp2):
1223 def writesubtree(subm, subp1, subp2):
1224 sublog = self.dirlog(subm.dir())
1224 sublog = self.dirlog(subm.dir())
1225 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1225 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1226 m.writesubtrees(m1, m2, writesubtree)
1226 m.writesubtrees(m1, m2, writesubtree)
1227 text = m.dirtext(self._usemanifestv2)
1227 text = m.dirtext(self._usemanifestv2)
1228 # Double-check whether contents are unchanged to one parent
1228 # Double-check whether contents are unchanged to one parent
1229 if text == m1.dirtext(self._usemanifestv2):
1229 if text == m1.dirtext(self._usemanifestv2):
1230 n = m1.node()
1230 n = m1.node()
1231 elif text == m2.dirtext(self._usemanifestv2):
1231 elif text == m2.dirtext(self._usemanifestv2):
1232 n = m2.node()
1232 n = m2.node()
1233 else:
1233 else:
1234 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1234 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1235 # Save nodeid so parent manifest can calculate its nodeid
1235 # Save nodeid so parent manifest can calculate its nodeid
1236 m.setnode(n)
1236 m.setnode(n)
1237 return n
1237 return n
1238
1238
1239 class manifestlog(object):
1239 class manifestlog(object):
1240 """A collection class representing the collection of manifest snapshots
1240 """A collection class representing the collection of manifest snapshots
1241 referenced by commits in the repository.
1241 referenced by commits in the repository.
1242
1242
1243 In this situation, 'manifest' refers to the abstract concept of a snapshot
1243 In this situation, 'manifest' refers to the abstract concept of a snapshot
1244 of the list of files in the given commit. Consumers of the output of this
1244 of the list of files in the given commit. Consumers of the output of this
1245 class do not care about the implementation details of the actual manifests
1245 class do not care about the implementation details of the actual manifests
1246 they receive (i.e. tree or flat or lazily loaded, etc)."""
1246 they receive (i.e. tree or flat or lazily loaded, etc)."""
1247 def __init__(self, opener, repo):
1247 def __init__(self, opener, repo):
1248 self._repo = repo
1248 self._repo = repo
1249
1249
1250 usetreemanifest = False
1250 usetreemanifest = False
1251
1251
1252 opts = getattr(opener, 'options', None)
1252 opts = getattr(opener, 'options', None)
1253 if opts is not None:
1253 if opts is not None:
1254 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1254 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1255 self._treeinmem = usetreemanifest
1255 self._treeinmem = usetreemanifest
1256
1256
1257 self._oldmanifest = repo._constructmanifest()
1257 self._oldmanifest = repo._constructmanifest()
1258 self._revlog = self._oldmanifest
1258 self._revlog = self._oldmanifest
1259
1259
1260 # A cache of the manifestctx or treemanifestctx for each directory
1260 # A cache of the manifestctx or treemanifestctx for each directory
1261 self._dirmancache = {}
1261 self._dirmancache = {}
1262
1262
1263 # We'll separate this into it's own cache once oldmanifest is no longer
1263 # We'll separate this into it's own cache once oldmanifest is no longer
1264 # used
1264 # used
1265 self._mancache = self._oldmanifest._mancache
1265 self._mancache = self._oldmanifest._mancache
1266 self._dirmancache[''] = self._mancache
1266 self._dirmancache[''] = self._mancache
1267
1267
1268 # A future patch makes this use the same config value as the existing
1268 # A future patch makes this use the same config value as the existing
1269 # mancache
1269 # mancache
1270 self.cachesize = 4
1270 self.cachesize = 4
1271
1271
1272 def __getitem__(self, node):
1272 def __getitem__(self, node):
1273 """Retrieves the manifest instance for the given node. Throws a
1273 """Retrieves the manifest instance for the given node. Throws a
1274 LookupError if not found.
1274 LookupError if not found.
1275 """
1275 """
1276 return self.get('', node)
1276 return self.get('', node)
1277
1277
1278 def get(self, dir, node):
1278 def get(self, dir, node):
1279 """Retrieves the manifest instance for the given node. Throws a
1279 """Retrieves the manifest instance for the given node. Throws a
1280 LookupError if not found.
1280 LookupError if not found.
1281 """
1281 """
1282 if node in self._dirmancache.get(dir, ()):
1282 if node in self._dirmancache.get(dir, ()):
1283 cachemf = self._dirmancache[dir][node]
1283 cachemf = self._dirmancache[dir][node]
1284 # The old manifest may put non-ctx manifests in the cache, so
1284 # The old manifest may put non-ctx manifests in the cache, so
1285 # skip those since they don't implement the full api.
1285 # skip those since they don't implement the full api.
1286 if (isinstance(cachemf, manifestctx) or
1286 if (isinstance(cachemf, manifestctx) or
1287 isinstance(cachemf, treemanifestctx)):
1287 isinstance(cachemf, treemanifestctx)):
1288 return cachemf
1288 return cachemf
1289
1289
1290 if dir:
1290 if dir:
1291 if self._revlog._treeondisk:
1291 if self._revlog._treeondisk:
1292 dirlog = self._revlog.dirlog(dir)
1292 dirlog = self._revlog.dirlog(dir)
1293 if node not in dirlog.nodemap:
1293 if node not in dirlog.nodemap:
1294 raise LookupError(node, dirlog.indexfile,
1294 raise LookupError(node, dirlog.indexfile,
1295 _('no node'))
1295 _('no node'))
1296 m = treemanifestctx(self._repo, dir, node)
1296 m = treemanifestctx(self._repo, dir, node)
1297 else:
1297 else:
1298 raise error.Abort(
1298 raise error.Abort(
1299 _("cannot ask for manifest directory '%s' in a flat "
1299 _("cannot ask for manifest directory '%s' in a flat "
1300 "manifest") % dir)
1300 "manifest") % dir)
1301 else:
1301 else:
1302 if node not in self._revlog.nodemap:
1302 if node not in self._revlog.nodemap:
1303 raise LookupError(node, self._revlog.indexfile,
1303 raise LookupError(node, self._revlog.indexfile,
1304 _('no node'))
1304 _('no node'))
1305 if self._treeinmem:
1305 if self._treeinmem:
1306 m = treemanifestctx(self._repo, '', node)
1306 m = treemanifestctx(self._repo, '', node)
1307 else:
1307 else:
1308 m = manifestctx(self._repo, node)
1308 m = manifestctx(self._repo, node)
1309
1309
1310 if node != revlog.nullid:
1310 if node != revlog.nullid:
1311 mancache = self._dirmancache.get(dir)
1311 mancache = self._dirmancache.get(dir)
1312 if not mancache:
1312 if not mancache:
1313 mancache = util.lrucachedict(self.cachesize)
1313 mancache = util.lrucachedict(self.cachesize)
1314 self._dirmancache[dir] = mancache
1314 self._dirmancache[dir] = mancache
1315 mancache[node] = m
1315 mancache[node] = m
1316 return m
1316 return m
1317
1317
1318 def add(self, m, transaction, link, p1, p2, added, removed):
1318 def add(self, m, transaction, link, p1, p2, added, removed):
1319 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1319 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1320
1320
1321 class manifestctx(object):
1321 class manifestctx(object):
1322 """A class representing a single revision of a manifest, including its
1322 """A class representing a single revision of a manifest, including its
1323 contents, its parent revs, and its linkrev.
1323 contents, its parent revs, and its linkrev.
1324 """
1324 """
1325 def __init__(self, repo, node):
1325 def __init__(self, repo, node):
1326 self._repo = repo
1326 self._repo = repo
1327 self._data = None
1327 self._data = None
1328
1328
1329 self._node = node
1329 self._node = node
1330
1330
1331 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1331 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1332 # but let's add it later when something needs it and we can load it
1332 # but let's add it later when something needs it and we can load it
1333 # lazily.
1333 # lazily.
1334 #self.p1, self.p2 = revlog.parents(node)
1334 #self.p1, self.p2 = revlog.parents(node)
1335 #rev = revlog.rev(node)
1335 #rev = revlog.rev(node)
1336 #self.linkrev = revlog.linkrev(rev)
1336 #self.linkrev = revlog.linkrev(rev)
1337
1337
1338 def node(self):
1338 def node(self):
1339 return self._node
1339 return self._node
1340
1340
1341 def read(self):
1341 def read(self):
1342 if not self._data:
1342 if not self._data:
1343 if self._node == revlog.nullid:
1343 if self._node == revlog.nullid:
1344 self._data = manifestdict()
1344 self._data = manifestdict()
1345 else:
1345 else:
1346 rl = self._repo.manifestlog._revlog
1346 rl = self._repo.manifestlog._revlog
1347 text = rl.revision(self._node)
1347 text = rl.revision(self._node)
1348 arraytext = array.array('c', text)
1348 arraytext = array.array('c', text)
1349 rl._fulltextcache[self._node] = arraytext
1349 rl._fulltextcache[self._node] = arraytext
1350 self._data = manifestdict(text)
1350 self._data = manifestdict(text)
1351 return self._data
1351 return self._data
1352
1352
1353 def readfast(self, shallow=False):
1353 def readfast(self, shallow=False):
1354 '''Calls either readdelta or read, based on which would be less work.
1355 readdelta is called if the delta is against the p1, and therefore can be
1356 read quickly.
1357
1358 If `shallow` is True, nothing changes since this is a flat manifest.
1359 '''
1354 rl = self._repo.manifestlog._revlog
1360 rl = self._repo.manifestlog._revlog
1355 r = rl.rev(self._node)
1361 r = rl.rev(self._node)
1356 deltaparent = rl.deltaparent(r)
1362 deltaparent = rl.deltaparent(r)
1357 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1363 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1358 return self.readdelta()
1364 return self.readdelta()
1359 return self.read()
1365 return self.read()
1360
1366
1361 def readdelta(self, shallow=False):
1367 def readdelta(self, shallow=False):
1362 revlog = self._repo.manifestlog._revlog
1368 revlog = self._repo.manifestlog._revlog
1363 if revlog._usemanifestv2:
1369 if revlog._usemanifestv2:
1364 # Need to perform a slow delta
1370 # Need to perform a slow delta
1365 r0 = revlog.deltaparent(revlog.rev(self._node))
1371 r0 = revlog.deltaparent(revlog.rev(self._node))
1366 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1372 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1367 m1 = self.read()
1373 m1 = self.read()
1368 md = manifestdict()
1374 md = manifestdict()
1369 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1375 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1370 if n1:
1376 if n1:
1371 md[f] = n1
1377 md[f] = n1
1372 if fl1:
1378 if fl1:
1373 md.setflag(f, fl1)
1379 md.setflag(f, fl1)
1374 return md
1380 return md
1375
1381
1376 r = revlog.rev(self._node)
1382 r = revlog.rev(self._node)
1377 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1383 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1378 return manifestdict(d)
1384 return manifestdict(d)
1379
1385
1380 class treemanifestctx(object):
1386 class treemanifestctx(object):
1381 def __init__(self, repo, dir, node):
1387 def __init__(self, repo, dir, node):
1382 self._repo = repo
1388 self._repo = repo
1383 self._dir = dir
1389 self._dir = dir
1384 self._data = None
1390 self._data = None
1385
1391
1386 self._node = node
1392 self._node = node
1387
1393
1388 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1394 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1389 # we can instantiate treemanifestctx objects for directories we don't
1395 # we can instantiate treemanifestctx objects for directories we don't
1390 # have on disk.
1396 # have on disk.
1391 #self.p1, self.p2 = revlog.parents(node)
1397 #self.p1, self.p2 = revlog.parents(node)
1392 #rev = revlog.rev(node)
1398 #rev = revlog.rev(node)
1393 #self.linkrev = revlog.linkrev(rev)
1399 #self.linkrev = revlog.linkrev(rev)
1394
1400
1395 def _revlog(self):
1401 def _revlog(self):
1396 return self._repo.manifestlog._revlog.dirlog(self._dir)
1402 return self._repo.manifestlog._revlog.dirlog(self._dir)
1397
1403
1398 def read(self):
1404 def read(self):
1399 if not self._data:
1405 if not self._data:
1400 rl = self._revlog()
1406 rl = self._revlog()
1401 if self._node == revlog.nullid:
1407 if self._node == revlog.nullid:
1402 self._data = treemanifest()
1408 self._data = treemanifest()
1403 elif rl._treeondisk:
1409 elif rl._treeondisk:
1404 m = treemanifest(dir=self._dir)
1410 m = treemanifest(dir=self._dir)
1405 def gettext():
1411 def gettext():
1406 return rl.revision(self._node)
1412 return rl.revision(self._node)
1407 def readsubtree(dir, subm):
1413 def readsubtree(dir, subm):
1408 return treemanifestctx(self._repo, dir, subm).read()
1414 return treemanifestctx(self._repo, dir, subm).read()
1409 m.read(gettext, readsubtree)
1415 m.read(gettext, readsubtree)
1410 m.setnode(self._node)
1416 m.setnode(self._node)
1411 self._data = m
1417 self._data = m
1412 else:
1418 else:
1413 text = revlog.revision(self._node)
1419 text = revlog.revision(self._node)
1414 arraytext = array.array('c', text)
1420 arraytext = array.array('c', text)
1415 rl.fulltextcache[self._node] = arraytext
1421 rl.fulltextcache[self._node] = arraytext
1416 self._data = treemanifest(dir=self._dir, text=text)
1422 self._data = treemanifest(dir=self._dir, text=text)
1417
1423
1418 return self._data
1424 return self._data
1419
1425
1420 def node(self):
1426 def node(self):
1421 return self._node
1427 return self._node
1422
1428
1423 def readdelta(self, shallow=False):
1429 def readdelta(self, shallow=False):
1424 revlog = self._revlog()
1430 revlog = self._revlog()
1425 if shallow and not revlog._usemanifestv2:
1431 if shallow and not revlog._usemanifestv2:
1426 r = revlog.rev(self._node)
1432 r = revlog.rev(self._node)
1427 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1433 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1428 return manifestdict(d)
1434 return manifestdict(d)
1429 else:
1435 else:
1430 # Need to perform a slow delta
1436 # Need to perform a slow delta
1431 r0 = revlog.deltaparent(revlog.rev(self._node))
1437 r0 = revlog.deltaparent(revlog.rev(self._node))
1432 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1438 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1433 m1 = self.read()
1439 m1 = self.read()
1434 md = treemanifest(dir=self._dir)
1440 md = treemanifest(dir=self._dir)
1435 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1441 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1436 if n1:
1442 if n1:
1437 md[f] = n1
1443 md[f] = n1
1438 if fl1:
1444 if fl1:
1439 md.setflag(f, fl1)
1445 md.setflag(f, fl1)
1440 return md
1446 return md
1441
1447
1442 def readfast(self, shallow=False):
1448 def readfast(self, shallow=False):
1449 '''Calls either readdelta or read, based on which would be less work.
1450 readdelta is called if the delta is against the p1, and therefore can be
1451 read quickly.
1452
1453 If `shallow` is True, it only returns the entries from this manifest,
1454 and not any submanifests.
1455 '''
1443 rl = self._revlog()
1456 rl = self._revlog()
1444 r = rl.rev(self._node)
1457 r = rl.rev(self._node)
1445 deltaparent = rl.deltaparent(r)
1458 deltaparent = rl.deltaparent(r)
1446 if (deltaparent != revlog.nullrev and
1459 if (deltaparent != revlog.nullrev and
1447 deltaparent in rl.parentrevs(r)):
1460 deltaparent in rl.parentrevs(r)):
1448 return self.readdelta(shallow=shallow)
1461 return self.readdelta(shallow=shallow)
1449
1462
1450 if shallow:
1463 if shallow:
1451 return manifestdict(rl.revision(self._node))
1464 return manifestdict(rl.revision(self._node))
1452 else:
1465 else:
1453 return self.read()
1466 return self.read()
1454
1467
1455 class manifest(manifestrevlog):
1468 class manifest(manifestrevlog):
1456 def __init__(self, opener, dir='', dirlogcache=None):
1469 def __init__(self, opener, dir='', dirlogcache=None):
1457 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1470 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1458 manifest.manifest only. External users should create a root manifest
1471 manifest.manifest only. External users should create a root manifest
1459 log with manifest.manifest(opener) and call dirlog() on it.
1472 log with manifest.manifest(opener) and call dirlog() on it.
1460 '''
1473 '''
1461 # During normal operations, we expect to deal with not more than four
1474 # During normal operations, we expect to deal with not more than four
1462 # revs at a time (such as during commit --amend). When rebasing large
1475 # revs at a time (such as during commit --amend). When rebasing large
1463 # stacks of commits, the number can go up, hence the config knob below.
1476 # stacks of commits, the number can go up, hence the config knob below.
1464 cachesize = 4
1477 cachesize = 4
1465 usetreemanifest = False
1478 usetreemanifest = False
1466 opts = getattr(opener, 'options', None)
1479 opts = getattr(opener, 'options', None)
1467 if opts is not None:
1480 if opts is not None:
1468 cachesize = opts.get('manifestcachesize', cachesize)
1481 cachesize = opts.get('manifestcachesize', cachesize)
1469 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1482 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1470 self._mancache = util.lrucachedict(cachesize)
1483 self._mancache = util.lrucachedict(cachesize)
1471 self._treeinmem = usetreemanifest
1484 self._treeinmem = usetreemanifest
1472 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1485 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1473
1486
1474 def _newmanifest(self, data=''):
1487 def _newmanifest(self, data=''):
1475 if self._treeinmem:
1488 if self._treeinmem:
1476 return treemanifest(self._dir, data)
1489 return treemanifest(self._dir, data)
1477 return manifestdict(data)
1490 return manifestdict(data)
1478
1491
1479 def dirlog(self, dir):
1492 def dirlog(self, dir):
1480 """This overrides the base revlog implementation to allow construction
1493 """This overrides the base revlog implementation to allow construction
1481 'manifest' types instead of manifestrevlog types. This is only needed
1494 'manifest' types instead of manifestrevlog types. This is only needed
1482 until we migrate off the 'manifest' type."""
1495 until we migrate off the 'manifest' type."""
1483 if dir:
1496 if dir:
1484 assert self._treeondisk
1497 assert self._treeondisk
1485 if dir not in self._dirlogcache:
1498 if dir not in self._dirlogcache:
1486 self._dirlogcache[dir] = manifest(self.opener, dir,
1499 self._dirlogcache[dir] = manifest(self.opener, dir,
1487 self._dirlogcache)
1500 self._dirlogcache)
1488 return self._dirlogcache[dir]
1501 return self._dirlogcache[dir]
1489
1502
1490 def _slowreaddelta(self, node):
1503 def _slowreaddelta(self, node):
1491 r0 = self.deltaparent(self.rev(node))
1504 r0 = self.deltaparent(self.rev(node))
1492 m0 = self.read(self.node(r0))
1505 m0 = self.read(self.node(r0))
1493 m1 = self.read(node)
1506 m1 = self.read(node)
1494 md = self._newmanifest()
1507 md = self._newmanifest()
1495 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1508 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1496 if n1:
1509 if n1:
1497 md[f] = n1
1510 md[f] = n1
1498 if fl1:
1511 if fl1:
1499 md.setflag(f, fl1)
1512 md.setflag(f, fl1)
1500 return md
1513 return md
1501
1514
1502 def readdelta(self, node):
1515 def readdelta(self, node):
1503 if self._usemanifestv2 or self._treeondisk:
1516 if self._usemanifestv2 or self._treeondisk:
1504 return self._slowreaddelta(node)
1517 return self._slowreaddelta(node)
1505 r = self.rev(node)
1518 r = self.rev(node)
1506 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1519 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1507 return self._newmanifest(d)
1520 return self._newmanifest(d)
1508
1521
1509 def readshallowdelta(self, node):
1522 def readshallowdelta(self, node):
1510 '''For flat manifests, this is the same as readdelta(). For
1523 '''For flat manifests, this is the same as readdelta(). For
1511 treemanifests, this will read the delta for this revlog's directory,
1524 treemanifests, this will read the delta for this revlog's directory,
1512 without recursively reading subdirectory manifests. Instead, any
1525 without recursively reading subdirectory manifests. Instead, any
1513 subdirectory entry will be reported as it appears in the manifests, i.e.
1526 subdirectory entry will be reported as it appears in the manifests, i.e.
1514 the subdirectory will be reported among files and distinguished only by
1527 the subdirectory will be reported among files and distinguished only by
1515 its 't' flag.'''
1528 its 't' flag.'''
1516 if not self._treeondisk:
1529 if not self._treeondisk:
1517 return self.readdelta(node)
1530 return self.readdelta(node)
1518 if self._usemanifestv2:
1531 if self._usemanifestv2:
1519 raise error.Abort(
1532 raise error.Abort(
1520 _("readshallowdelta() not implemented for manifestv2"))
1533 _("readshallowdelta() not implemented for manifestv2"))
1521 r = self.rev(node)
1534 r = self.rev(node)
1522 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1535 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1523 return manifestdict(d)
1536 return manifestdict(d)
1524
1537
1525 def readshallowfast(self, node):
1526 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1527 '''
1528 r = self.rev(node)
1529 deltaparent = self.deltaparent(r)
1530 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1531 return self.readshallowdelta(node)
1532 return self.readshallow(node)
1533
1534 def read(self, node):
1538 def read(self, node):
1535 if node == revlog.nullid:
1539 if node == revlog.nullid:
1536 return self._newmanifest() # don't upset local cache
1540 return self._newmanifest() # don't upset local cache
1537 if node in self._mancache:
1541 if node in self._mancache:
1538 cached = self._mancache[node]
1542 cached = self._mancache[node]
1539 if (isinstance(cached, manifestctx) or
1543 if (isinstance(cached, manifestctx) or
1540 isinstance(cached, treemanifestctx)):
1544 isinstance(cached, treemanifestctx)):
1541 cached = cached.read()
1545 cached = cached.read()
1542 return cached
1546 return cached
1543 if self._treeondisk:
1547 if self._treeondisk:
1544 def gettext():
1548 def gettext():
1545 return self.revision(node)
1549 return self.revision(node)
1546 def readsubtree(dir, subm):
1550 def readsubtree(dir, subm):
1547 return self.dirlog(dir).read(subm)
1551 return self.dirlog(dir).read(subm)
1548 m = self._newmanifest()
1552 m = self._newmanifest()
1549 m.read(gettext, readsubtree)
1553 m.read(gettext, readsubtree)
1550 m.setnode(node)
1554 m.setnode(node)
1551 arraytext = None
1555 arraytext = None
1552 else:
1556 else:
1553 text = self.revision(node)
1557 text = self.revision(node)
1554 m = self._newmanifest(text)
1558 m = self._newmanifest(text)
1555 arraytext = array.array('c', text)
1559 arraytext = array.array('c', text)
1556 self._mancache[node] = m
1560 self._mancache[node] = m
1557 if arraytext is not None:
1561 if arraytext is not None:
1558 self.fulltextcache[node] = arraytext
1562 self.fulltextcache[node] = arraytext
1559 return m
1563 return m
1560
1564
1561 def readshallow(self, node):
1562 '''Reads the manifest in this directory. When using flat manifests,
1563 this manifest will generally have files in subdirectories in it. Does
1564 not cache the manifest as the callers generally do not read the same
1565 version twice.'''
1566 return manifestdict(self.revision(node))
1567
1568 def find(self, node, f):
1565 def find(self, node, f):
1569 '''look up entry for a single file efficiently.
1566 '''look up entry for a single file efficiently.
1570 return (node, flags) pair if found, (None, None) if not.'''
1567 return (node, flags) pair if found, (None, None) if not.'''
1571 m = self.read(node)
1568 m = self.read(node)
1572 try:
1569 try:
1573 return m.find(f)
1570 return m.find(f)
1574 except KeyError:
1571 except KeyError:
1575 return None, None
1572 return None, None
1576
1573
1577 def clearcaches(self):
1574 def clearcaches(self):
1578 super(manifest, self).clearcaches()
1575 super(manifest, self).clearcaches()
1579 self._mancache.clear()
1576 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now