##// END OF EJS Templates
revlog: drop emitrevisiondeltas() and associated functionality (API)...
Gregory Szorc -
r39902:e23c03dc default
parent child Browse files
Show More
@@ -1,1381 +1,1363 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 weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from .thirdparty import (
23 attr,
24 )
25
26 from . import (
22 from . import (
27 error,
23 error,
28 match as matchmod,
24 match as matchmod,
29 mdiff,
25 mdiff,
30 phases,
26 phases,
31 pycompat,
27 pycompat,
32 repository,
28 repository,
33 revlog,
29 revlog,
34 util,
30 util,
35 )
31 )
36
32
37 from .utils import (
38 interfaceutil,
39 )
40
41 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
33 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
42 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
34 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
43 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
35 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
44
36
45 LFS_REQUIREMENT = 'lfs'
37 LFS_REQUIREMENT = 'lfs'
46
38
47 readexactly = util.readexactly
39 readexactly = util.readexactly
48
40
49 def getchunk(stream):
41 def getchunk(stream):
50 """return the next chunk from stream as a string"""
42 """return the next chunk from stream as a string"""
51 d = readexactly(stream, 4)
43 d = readexactly(stream, 4)
52 l = struct.unpack(">l", d)[0]
44 l = struct.unpack(">l", d)[0]
53 if l <= 4:
45 if l <= 4:
54 if l:
46 if l:
55 raise error.Abort(_("invalid chunk length %d") % l)
47 raise error.Abort(_("invalid chunk length %d") % l)
56 return ""
48 return ""
57 return readexactly(stream, l - 4)
49 return readexactly(stream, l - 4)
58
50
59 def chunkheader(length):
51 def chunkheader(length):
60 """return a changegroup chunk header (string)"""
52 """return a changegroup chunk header (string)"""
61 return struct.pack(">l", length + 4)
53 return struct.pack(">l", length + 4)
62
54
63 def closechunk():
55 def closechunk():
64 """return a changegroup chunk header (string) for a zero-length chunk"""
56 """return a changegroup chunk header (string) for a zero-length chunk"""
65 return struct.pack(">l", 0)
57 return struct.pack(">l", 0)
66
58
67 def _fileheader(path):
59 def _fileheader(path):
68 """Obtain a changegroup chunk header for a named path."""
60 """Obtain a changegroup chunk header for a named path."""
69 return chunkheader(len(path)) + path
61 return chunkheader(len(path)) + path
70
62
71 def writechunks(ui, chunks, filename, vfs=None):
63 def writechunks(ui, chunks, filename, vfs=None):
72 """Write chunks to a file and return its filename.
64 """Write chunks to a file and return its filename.
73
65
74 The stream is assumed to be a bundle file.
66 The stream is assumed to be a bundle file.
75 Existing files will not be overwritten.
67 Existing files will not be overwritten.
76 If no filename is specified, a temporary file is created.
68 If no filename is specified, a temporary file is created.
77 """
69 """
78 fh = None
70 fh = None
79 cleanup = None
71 cleanup = None
80 try:
72 try:
81 if filename:
73 if filename:
82 if vfs:
74 if vfs:
83 fh = vfs.open(filename, "wb")
75 fh = vfs.open(filename, "wb")
84 else:
76 else:
85 # Increase default buffer size because default is usually
77 # Increase default buffer size because default is usually
86 # small (4k is common on Linux).
78 # small (4k is common on Linux).
87 fh = open(filename, "wb", 131072)
79 fh = open(filename, "wb", 131072)
88 else:
80 else:
89 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
81 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
90 fh = os.fdopen(fd, r"wb")
82 fh = os.fdopen(fd, r"wb")
91 cleanup = filename
83 cleanup = filename
92 for c in chunks:
84 for c in chunks:
93 fh.write(c)
85 fh.write(c)
94 cleanup = None
86 cleanup = None
95 return filename
87 return filename
96 finally:
88 finally:
97 if fh is not None:
89 if fh is not None:
98 fh.close()
90 fh.close()
99 if cleanup is not None:
91 if cleanup is not None:
100 if filename and vfs:
92 if filename and vfs:
101 vfs.unlink(cleanup)
93 vfs.unlink(cleanup)
102 else:
94 else:
103 os.unlink(cleanup)
95 os.unlink(cleanup)
104
96
105 class cg1unpacker(object):
97 class cg1unpacker(object):
106 """Unpacker for cg1 changegroup streams.
98 """Unpacker for cg1 changegroup streams.
107
99
108 A changegroup unpacker handles the framing of the revision data in
100 A changegroup unpacker handles the framing of the revision data in
109 the wire format. Most consumers will want to use the apply()
101 the wire format. Most consumers will want to use the apply()
110 method to add the changes from the changegroup to a repository.
102 method to add the changes from the changegroup to a repository.
111
103
112 If you're forwarding a changegroup unmodified to another consumer,
104 If you're forwarding a changegroup unmodified to another consumer,
113 use getchunks(), which returns an iterator of changegroup
105 use getchunks(), which returns an iterator of changegroup
114 chunks. This is mostly useful for cases where you need to know the
106 chunks. This is mostly useful for cases where you need to know the
115 data stream has ended by observing the end of the changegroup.
107 data stream has ended by observing the end of the changegroup.
116
108
117 deltachunk() is useful only if you're applying delta data. Most
109 deltachunk() is useful only if you're applying delta data. Most
118 consumers should prefer apply() instead.
110 consumers should prefer apply() instead.
119
111
120 A few other public methods exist. Those are used only for
112 A few other public methods exist. Those are used only for
121 bundlerepo and some debug commands - their use is discouraged.
113 bundlerepo and some debug commands - their use is discouraged.
122 """
114 """
123 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
115 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
124 deltaheadersize = deltaheader.size
116 deltaheadersize = deltaheader.size
125 version = '01'
117 version = '01'
126 _grouplistcount = 1 # One list of files after the manifests
118 _grouplistcount = 1 # One list of files after the manifests
127
119
128 def __init__(self, fh, alg, extras=None):
120 def __init__(self, fh, alg, extras=None):
129 if alg is None:
121 if alg is None:
130 alg = 'UN'
122 alg = 'UN'
131 if alg not in util.compengines.supportedbundletypes:
123 if alg not in util.compengines.supportedbundletypes:
132 raise error.Abort(_('unknown stream compression type: %s')
124 raise error.Abort(_('unknown stream compression type: %s')
133 % alg)
125 % alg)
134 if alg == 'BZ':
126 if alg == 'BZ':
135 alg = '_truncatedBZ'
127 alg = '_truncatedBZ'
136
128
137 compengine = util.compengines.forbundletype(alg)
129 compengine = util.compengines.forbundletype(alg)
138 self._stream = compengine.decompressorreader(fh)
130 self._stream = compengine.decompressorreader(fh)
139 self._type = alg
131 self._type = alg
140 self.extras = extras or {}
132 self.extras = extras or {}
141 self.callback = None
133 self.callback = None
142
134
143 # These methods (compressed, read, seek, tell) all appear to only
135 # These methods (compressed, read, seek, tell) all appear to only
144 # be used by bundlerepo, but it's a little hard to tell.
136 # be used by bundlerepo, but it's a little hard to tell.
145 def compressed(self):
137 def compressed(self):
146 return self._type is not None and self._type != 'UN'
138 return self._type is not None and self._type != 'UN'
147 def read(self, l):
139 def read(self, l):
148 return self._stream.read(l)
140 return self._stream.read(l)
149 def seek(self, pos):
141 def seek(self, pos):
150 return self._stream.seek(pos)
142 return self._stream.seek(pos)
151 def tell(self):
143 def tell(self):
152 return self._stream.tell()
144 return self._stream.tell()
153 def close(self):
145 def close(self):
154 return self._stream.close()
146 return self._stream.close()
155
147
156 def _chunklength(self):
148 def _chunklength(self):
157 d = readexactly(self._stream, 4)
149 d = readexactly(self._stream, 4)
158 l = struct.unpack(">l", d)[0]
150 l = struct.unpack(">l", d)[0]
159 if l <= 4:
151 if l <= 4:
160 if l:
152 if l:
161 raise error.Abort(_("invalid chunk length %d") % l)
153 raise error.Abort(_("invalid chunk length %d") % l)
162 return 0
154 return 0
163 if self.callback:
155 if self.callback:
164 self.callback()
156 self.callback()
165 return l - 4
157 return l - 4
166
158
167 def changelogheader(self):
159 def changelogheader(self):
168 """v10 does not have a changelog header chunk"""
160 """v10 does not have a changelog header chunk"""
169 return {}
161 return {}
170
162
171 def manifestheader(self):
163 def manifestheader(self):
172 """v10 does not have a manifest header chunk"""
164 """v10 does not have a manifest header chunk"""
173 return {}
165 return {}
174
166
175 def filelogheader(self):
167 def filelogheader(self):
176 """return the header of the filelogs chunk, v10 only has the filename"""
168 """return the header of the filelogs chunk, v10 only has the filename"""
177 l = self._chunklength()
169 l = self._chunklength()
178 if not l:
170 if not l:
179 return {}
171 return {}
180 fname = readexactly(self._stream, l)
172 fname = readexactly(self._stream, l)
181 return {'filename': fname}
173 return {'filename': fname}
182
174
183 def _deltaheader(self, headertuple, prevnode):
175 def _deltaheader(self, headertuple, prevnode):
184 node, p1, p2, cs = headertuple
176 node, p1, p2, cs = headertuple
185 if prevnode is None:
177 if prevnode is None:
186 deltabase = p1
178 deltabase = p1
187 else:
179 else:
188 deltabase = prevnode
180 deltabase = prevnode
189 flags = 0
181 flags = 0
190 return node, p1, p2, deltabase, cs, flags
182 return node, p1, p2, deltabase, cs, flags
191
183
192 def deltachunk(self, prevnode):
184 def deltachunk(self, prevnode):
193 l = self._chunklength()
185 l = self._chunklength()
194 if not l:
186 if not l:
195 return {}
187 return {}
196 headerdata = readexactly(self._stream, self.deltaheadersize)
188 headerdata = readexactly(self._stream, self.deltaheadersize)
197 header = self.deltaheader.unpack(headerdata)
189 header = self.deltaheader.unpack(headerdata)
198 delta = readexactly(self._stream, l - self.deltaheadersize)
190 delta = readexactly(self._stream, l - self.deltaheadersize)
199 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
191 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
200 return (node, p1, p2, cs, deltabase, delta, flags)
192 return (node, p1, p2, cs, deltabase, delta, flags)
201
193
202 def getchunks(self):
194 def getchunks(self):
203 """returns all the chunks contains in the bundle
195 """returns all the chunks contains in the bundle
204
196
205 Used when you need to forward the binary stream to a file or another
197 Used when you need to forward the binary stream to a file or another
206 network API. To do so, it parse the changegroup data, otherwise it will
198 network API. To do so, it parse the changegroup data, otherwise it will
207 block in case of sshrepo because it don't know the end of the stream.
199 block in case of sshrepo because it don't know the end of the stream.
208 """
200 """
209 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
201 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
210 # and a list of filelogs. For changegroup 3, we expect 4 parts:
202 # and a list of filelogs. For changegroup 3, we expect 4 parts:
211 # changelog, manifestlog, a list of tree manifestlogs, and a list of
203 # changelog, manifestlog, a list of tree manifestlogs, and a list of
212 # filelogs.
204 # filelogs.
213 #
205 #
214 # Changelog and manifestlog parts are terminated with empty chunks. The
206 # Changelog and manifestlog parts are terminated with empty chunks. The
215 # tree and file parts are a list of entry sections. Each entry section
207 # tree and file parts are a list of entry sections. Each entry section
216 # is a series of chunks terminating in an empty chunk. The list of these
208 # is a series of chunks terminating in an empty chunk. The list of these
217 # entry sections is terminated in yet another empty chunk, so we know
209 # entry sections is terminated in yet another empty chunk, so we know
218 # we've reached the end of the tree/file list when we reach an empty
210 # we've reached the end of the tree/file list when we reach an empty
219 # chunk that was proceeded by no non-empty chunks.
211 # chunk that was proceeded by no non-empty chunks.
220
212
221 parts = 0
213 parts = 0
222 while parts < 2 + self._grouplistcount:
214 while parts < 2 + self._grouplistcount:
223 noentries = True
215 noentries = True
224 while True:
216 while True:
225 chunk = getchunk(self)
217 chunk = getchunk(self)
226 if not chunk:
218 if not chunk:
227 # The first two empty chunks represent the end of the
219 # The first two empty chunks represent the end of the
228 # changelog and the manifestlog portions. The remaining
220 # changelog and the manifestlog portions. The remaining
229 # empty chunks represent either A) the end of individual
221 # empty chunks represent either A) the end of individual
230 # tree or file entries in the file list, or B) the end of
222 # tree or file entries in the file list, or B) the end of
231 # the entire list. It's the end of the entire list if there
223 # the entire list. It's the end of the entire list if there
232 # were no entries (i.e. noentries is True).
224 # were no entries (i.e. noentries is True).
233 if parts < 2:
225 if parts < 2:
234 parts += 1
226 parts += 1
235 elif noentries:
227 elif noentries:
236 parts += 1
228 parts += 1
237 break
229 break
238 noentries = False
230 noentries = False
239 yield chunkheader(len(chunk))
231 yield chunkheader(len(chunk))
240 pos = 0
232 pos = 0
241 while pos < len(chunk):
233 while pos < len(chunk):
242 next = pos + 2**20
234 next = pos + 2**20
243 yield chunk[pos:next]
235 yield chunk[pos:next]
244 pos = next
236 pos = next
245 yield closechunk()
237 yield closechunk()
246
238
247 def _unpackmanifests(self, repo, revmap, trp, prog):
239 def _unpackmanifests(self, repo, revmap, trp, prog):
248 self.callback = prog.increment
240 self.callback = prog.increment
249 # no need to check for empty manifest group here:
241 # no need to check for empty manifest group here:
250 # if the result of the merge of 1 and 2 is the same in 3 and 4,
242 # if the result of the merge of 1 and 2 is the same in 3 and 4,
251 # no new manifest will be created and the manifest group will
243 # no new manifest will be created and the manifest group will
252 # be empty during the pull
244 # be empty during the pull
253 self.manifestheader()
245 self.manifestheader()
254 deltas = self.deltaiter()
246 deltas = self.deltaiter()
255 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
247 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
256 prog.complete()
248 prog.complete()
257 self.callback = None
249 self.callback = None
258
250
259 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
251 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
260 expectedtotal=None):
252 expectedtotal=None):
261 """Add the changegroup returned by source.read() to this repo.
253 """Add the changegroup returned by source.read() to this repo.
262 srctype is a string like 'push', 'pull', or 'unbundle'. url is
254 srctype is a string like 'push', 'pull', or 'unbundle'. url is
263 the URL of the repo where this changegroup is coming from.
255 the URL of the repo where this changegroup is coming from.
264
256
265 Return an integer summarizing the change to this repo:
257 Return an integer summarizing the change to this repo:
266 - nothing changed or no source: 0
258 - nothing changed or no source: 0
267 - more heads than before: 1+added heads (2..n)
259 - more heads than before: 1+added heads (2..n)
268 - fewer heads than before: -1-removed heads (-2..-n)
260 - fewer heads than before: -1-removed heads (-2..-n)
269 - number of heads stays the same: 1
261 - number of heads stays the same: 1
270 """
262 """
271 repo = repo.unfiltered()
263 repo = repo.unfiltered()
272 def csmap(x):
264 def csmap(x):
273 repo.ui.debug("add changeset %s\n" % short(x))
265 repo.ui.debug("add changeset %s\n" % short(x))
274 return len(cl)
266 return len(cl)
275
267
276 def revmap(x):
268 def revmap(x):
277 return cl.rev(x)
269 return cl.rev(x)
278
270
279 changesets = files = revisions = 0
271 changesets = files = revisions = 0
280
272
281 try:
273 try:
282 # The transaction may already carry source information. In this
274 # The transaction may already carry source information. In this
283 # case we use the top level data. We overwrite the argument
275 # case we use the top level data. We overwrite the argument
284 # because we need to use the top level value (if they exist)
276 # because we need to use the top level value (if they exist)
285 # in this function.
277 # in this function.
286 srctype = tr.hookargs.setdefault('source', srctype)
278 srctype = tr.hookargs.setdefault('source', srctype)
287 url = tr.hookargs.setdefault('url', url)
279 url = tr.hookargs.setdefault('url', url)
288 repo.hook('prechangegroup',
280 repo.hook('prechangegroup',
289 throw=True, **pycompat.strkwargs(tr.hookargs))
281 throw=True, **pycompat.strkwargs(tr.hookargs))
290
282
291 # write changelog data to temp files so concurrent readers
283 # write changelog data to temp files so concurrent readers
292 # will not see an inconsistent view
284 # will not see an inconsistent view
293 cl = repo.changelog
285 cl = repo.changelog
294 cl.delayupdate(tr)
286 cl.delayupdate(tr)
295 oldheads = set(cl.heads())
287 oldheads = set(cl.heads())
296
288
297 trp = weakref.proxy(tr)
289 trp = weakref.proxy(tr)
298 # pull off the changeset group
290 # pull off the changeset group
299 repo.ui.status(_("adding changesets\n"))
291 repo.ui.status(_("adding changesets\n"))
300 clstart = len(cl)
292 clstart = len(cl)
301 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
293 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
302 total=expectedtotal)
294 total=expectedtotal)
303 self.callback = progress.increment
295 self.callback = progress.increment
304
296
305 efiles = set()
297 efiles = set()
306 def onchangelog(cl, node):
298 def onchangelog(cl, node):
307 efiles.update(cl.readfiles(node))
299 efiles.update(cl.readfiles(node))
308
300
309 self.changelogheader()
301 self.changelogheader()
310 deltas = self.deltaiter()
302 deltas = self.deltaiter()
311 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
303 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
312 efiles = len(efiles)
304 efiles = len(efiles)
313
305
314 if not cgnodes:
306 if not cgnodes:
315 repo.ui.develwarn('applied empty changelog from changegroup',
307 repo.ui.develwarn('applied empty changelog from changegroup',
316 config='warn-empty-changegroup')
308 config='warn-empty-changegroup')
317 clend = len(cl)
309 clend = len(cl)
318 changesets = clend - clstart
310 changesets = clend - clstart
319 progress.complete()
311 progress.complete()
320 self.callback = None
312 self.callback = None
321
313
322 # pull off the manifest group
314 # pull off the manifest group
323 repo.ui.status(_("adding manifests\n"))
315 repo.ui.status(_("adding manifests\n"))
324 # We know that we'll never have more manifests than we had
316 # We know that we'll never have more manifests than we had
325 # changesets.
317 # changesets.
326 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
318 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
327 total=changesets)
319 total=changesets)
328 self._unpackmanifests(repo, revmap, trp, progress)
320 self._unpackmanifests(repo, revmap, trp, progress)
329
321
330 needfiles = {}
322 needfiles = {}
331 if repo.ui.configbool('server', 'validate'):
323 if repo.ui.configbool('server', 'validate'):
332 cl = repo.changelog
324 cl = repo.changelog
333 ml = repo.manifestlog
325 ml = repo.manifestlog
334 # validate incoming csets have their manifests
326 # validate incoming csets have their manifests
335 for cset in pycompat.xrange(clstart, clend):
327 for cset in pycompat.xrange(clstart, clend):
336 mfnode = cl.changelogrevision(cset).manifest
328 mfnode = cl.changelogrevision(cset).manifest
337 mfest = ml[mfnode].readdelta()
329 mfest = ml[mfnode].readdelta()
338 # store file cgnodes we must see
330 # store file cgnodes we must see
339 for f, n in mfest.iteritems():
331 for f, n in mfest.iteritems():
340 needfiles.setdefault(f, set()).add(n)
332 needfiles.setdefault(f, set()).add(n)
341
333
342 # process the files
334 # process the files
343 repo.ui.status(_("adding file changes\n"))
335 repo.ui.status(_("adding file changes\n"))
344 newrevs, newfiles = _addchangegroupfiles(
336 newrevs, newfiles = _addchangegroupfiles(
345 repo, self, revmap, trp, efiles, needfiles)
337 repo, self, revmap, trp, efiles, needfiles)
346 revisions += newrevs
338 revisions += newrevs
347 files += newfiles
339 files += newfiles
348
340
349 deltaheads = 0
341 deltaheads = 0
350 if oldheads:
342 if oldheads:
351 heads = cl.heads()
343 heads = cl.heads()
352 deltaheads = len(heads) - len(oldheads)
344 deltaheads = len(heads) - len(oldheads)
353 for h in heads:
345 for h in heads:
354 if h not in oldheads and repo[h].closesbranch():
346 if h not in oldheads and repo[h].closesbranch():
355 deltaheads -= 1
347 deltaheads -= 1
356 htext = ""
348 htext = ""
357 if deltaheads:
349 if deltaheads:
358 htext = _(" (%+d heads)") % deltaheads
350 htext = _(" (%+d heads)") % deltaheads
359
351
360 repo.ui.status(_("added %d changesets"
352 repo.ui.status(_("added %d changesets"
361 " with %d changes to %d files%s\n")
353 " with %d changes to %d files%s\n")
362 % (changesets, revisions, files, htext))
354 % (changesets, revisions, files, htext))
363 repo.invalidatevolatilesets()
355 repo.invalidatevolatilesets()
364
356
365 if changesets > 0:
357 if changesets > 0:
366 if 'node' not in tr.hookargs:
358 if 'node' not in tr.hookargs:
367 tr.hookargs['node'] = hex(cl.node(clstart))
359 tr.hookargs['node'] = hex(cl.node(clstart))
368 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
360 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
369 hookargs = dict(tr.hookargs)
361 hookargs = dict(tr.hookargs)
370 else:
362 else:
371 hookargs = dict(tr.hookargs)
363 hookargs = dict(tr.hookargs)
372 hookargs['node'] = hex(cl.node(clstart))
364 hookargs['node'] = hex(cl.node(clstart))
373 hookargs['node_last'] = hex(cl.node(clend - 1))
365 hookargs['node_last'] = hex(cl.node(clend - 1))
374 repo.hook('pretxnchangegroup',
366 repo.hook('pretxnchangegroup',
375 throw=True, **pycompat.strkwargs(hookargs))
367 throw=True, **pycompat.strkwargs(hookargs))
376
368
377 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
369 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
378 phaseall = None
370 phaseall = None
379 if srctype in ('push', 'serve'):
371 if srctype in ('push', 'serve'):
380 # Old servers can not push the boundary themselves.
372 # Old servers can not push the boundary themselves.
381 # New servers won't push the boundary if changeset already
373 # New servers won't push the boundary if changeset already
382 # exists locally as secret
374 # exists locally as secret
383 #
375 #
384 # We should not use added here but the list of all change in
376 # We should not use added here but the list of all change in
385 # the bundle
377 # the bundle
386 if repo.publishing():
378 if repo.publishing():
387 targetphase = phaseall = phases.public
379 targetphase = phaseall = phases.public
388 else:
380 else:
389 # closer target phase computation
381 # closer target phase computation
390
382
391 # Those changesets have been pushed from the
383 # Those changesets have been pushed from the
392 # outside, their phases are going to be pushed
384 # outside, their phases are going to be pushed
393 # alongside. Therefor `targetphase` is
385 # alongside. Therefor `targetphase` is
394 # ignored.
386 # ignored.
395 targetphase = phaseall = phases.draft
387 targetphase = phaseall = phases.draft
396 if added:
388 if added:
397 phases.registernew(repo, tr, targetphase, added)
389 phases.registernew(repo, tr, targetphase, added)
398 if phaseall is not None:
390 if phaseall is not None:
399 phases.advanceboundary(repo, tr, phaseall, cgnodes)
391 phases.advanceboundary(repo, tr, phaseall, cgnodes)
400
392
401 if changesets > 0:
393 if changesets > 0:
402
394
403 def runhooks():
395 def runhooks():
404 # These hooks run when the lock releases, not when the
396 # These hooks run when the lock releases, not when the
405 # transaction closes. So it's possible for the changelog
397 # transaction closes. So it's possible for the changelog
406 # to have changed since we last saw it.
398 # to have changed since we last saw it.
407 if clstart >= len(repo):
399 if clstart >= len(repo):
408 return
400 return
409
401
410 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
402 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
411
403
412 for n in added:
404 for n in added:
413 args = hookargs.copy()
405 args = hookargs.copy()
414 args['node'] = hex(n)
406 args['node'] = hex(n)
415 del args['node_last']
407 del args['node_last']
416 repo.hook("incoming", **pycompat.strkwargs(args))
408 repo.hook("incoming", **pycompat.strkwargs(args))
417
409
418 newheads = [h for h in repo.heads()
410 newheads = [h for h in repo.heads()
419 if h not in oldheads]
411 if h not in oldheads]
420 repo.ui.log("incoming",
412 repo.ui.log("incoming",
421 "%d incoming changes - new heads: %s\n",
413 "%d incoming changes - new heads: %s\n",
422 len(added),
414 len(added),
423 ', '.join([hex(c[:6]) for c in newheads]))
415 ', '.join([hex(c[:6]) for c in newheads]))
424
416
425 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
417 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
426 lambda tr: repo._afterlock(runhooks))
418 lambda tr: repo._afterlock(runhooks))
427 finally:
419 finally:
428 repo.ui.flush()
420 repo.ui.flush()
429 # never return 0 here:
421 # never return 0 here:
430 if deltaheads < 0:
422 if deltaheads < 0:
431 ret = deltaheads - 1
423 ret = deltaheads - 1
432 else:
424 else:
433 ret = deltaheads + 1
425 ret = deltaheads + 1
434 return ret
426 return ret
435
427
436 def deltaiter(self):
428 def deltaiter(self):
437 """
429 """
438 returns an iterator of the deltas in this changegroup
430 returns an iterator of the deltas in this changegroup
439
431
440 Useful for passing to the underlying storage system to be stored.
432 Useful for passing to the underlying storage system to be stored.
441 """
433 """
442 chain = None
434 chain = None
443 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
435 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
444 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
436 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
445 yield chunkdata
437 yield chunkdata
446 chain = chunkdata[0]
438 chain = chunkdata[0]
447
439
448 class cg2unpacker(cg1unpacker):
440 class cg2unpacker(cg1unpacker):
449 """Unpacker for cg2 streams.
441 """Unpacker for cg2 streams.
450
442
451 cg2 streams add support for generaldelta, so the delta header
443 cg2 streams add support for generaldelta, so the delta header
452 format is slightly different. All other features about the data
444 format is slightly different. All other features about the data
453 remain the same.
445 remain the same.
454 """
446 """
455 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
447 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
456 deltaheadersize = deltaheader.size
448 deltaheadersize = deltaheader.size
457 version = '02'
449 version = '02'
458
450
459 def _deltaheader(self, headertuple, prevnode):
451 def _deltaheader(self, headertuple, prevnode):
460 node, p1, p2, deltabase, cs = headertuple
452 node, p1, p2, deltabase, cs = headertuple
461 flags = 0
453 flags = 0
462 return node, p1, p2, deltabase, cs, flags
454 return node, p1, p2, deltabase, cs, flags
463
455
464 class cg3unpacker(cg2unpacker):
456 class cg3unpacker(cg2unpacker):
465 """Unpacker for cg3 streams.
457 """Unpacker for cg3 streams.
466
458
467 cg3 streams add support for exchanging treemanifests and revlog
459 cg3 streams add support for exchanging treemanifests and revlog
468 flags. It adds the revlog flags to the delta header and an empty chunk
460 flags. It adds the revlog flags to the delta header and an empty chunk
469 separating manifests and files.
461 separating manifests and files.
470 """
462 """
471 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
463 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
472 deltaheadersize = deltaheader.size
464 deltaheadersize = deltaheader.size
473 version = '03'
465 version = '03'
474 _grouplistcount = 2 # One list of manifests and one list of files
466 _grouplistcount = 2 # One list of manifests and one list of files
475
467
476 def _deltaheader(self, headertuple, prevnode):
468 def _deltaheader(self, headertuple, prevnode):
477 node, p1, p2, deltabase, cs, flags = headertuple
469 node, p1, p2, deltabase, cs, flags = headertuple
478 return node, p1, p2, deltabase, cs, flags
470 return node, p1, p2, deltabase, cs, flags
479
471
480 def _unpackmanifests(self, repo, revmap, trp, prog):
472 def _unpackmanifests(self, repo, revmap, trp, prog):
481 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
473 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
482 for chunkdata in iter(self.filelogheader, {}):
474 for chunkdata in iter(self.filelogheader, {}):
483 # If we get here, there are directory manifests in the changegroup
475 # If we get here, there are directory manifests in the changegroup
484 d = chunkdata["filename"]
476 d = chunkdata["filename"]
485 repo.ui.debug("adding %s revisions\n" % d)
477 repo.ui.debug("adding %s revisions\n" % d)
486 deltas = self.deltaiter()
478 deltas = self.deltaiter()
487 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
479 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
488 raise error.Abort(_("received dir revlog group is empty"))
480 raise error.Abort(_("received dir revlog group is empty"))
489
481
490 class headerlessfixup(object):
482 class headerlessfixup(object):
491 def __init__(self, fh, h):
483 def __init__(self, fh, h):
492 self._h = h
484 self._h = h
493 self._fh = fh
485 self._fh = fh
494 def read(self, n):
486 def read(self, n):
495 if self._h:
487 if self._h:
496 d, self._h = self._h[:n], self._h[n:]
488 d, self._h = self._h[:n], self._h[n:]
497 if len(d) < n:
489 if len(d) < n:
498 d += readexactly(self._fh, n - len(d))
490 d += readexactly(self._fh, n - len(d))
499 return d
491 return d
500 return readexactly(self._fh, n)
492 return readexactly(self._fh, n)
501
493
502 @interfaceutil.implementer(repository.irevisiondeltarequest)
503 @attr.s(slots=True, frozen=True)
504 class revisiondeltarequest(object):
505 node = attr.ib()
506 linknode = attr.ib()
507 p1node = attr.ib()
508 p2node = attr.ib()
509 basenode = attr.ib()
510 ellipsis = attr.ib(default=False)
511
512 def _revisiondeltatochunks(delta, headerfn):
494 def _revisiondeltatochunks(delta, headerfn):
513 """Serialize a revisiondelta to changegroup chunks."""
495 """Serialize a revisiondelta to changegroup chunks."""
514
496
515 # The captured revision delta may be encoded as a delta against
497 # The captured revision delta may be encoded as a delta against
516 # a base revision or as a full revision. The changegroup format
498 # a base revision or as a full revision. The changegroup format
517 # requires that everything on the wire be deltas. So for full
499 # requires that everything on the wire be deltas. So for full
518 # revisions, we need to invent a header that says to rewrite
500 # revisions, we need to invent a header that says to rewrite
519 # data.
501 # data.
520
502
521 if delta.delta is not None:
503 if delta.delta is not None:
522 prefix, data = b'', delta.delta
504 prefix, data = b'', delta.delta
523 elif delta.basenode == nullid:
505 elif delta.basenode == nullid:
524 data = delta.revision
506 data = delta.revision
525 prefix = mdiff.trivialdiffheader(len(data))
507 prefix = mdiff.trivialdiffheader(len(data))
526 else:
508 else:
527 data = delta.revision
509 data = delta.revision
528 prefix = mdiff.replacediffheader(delta.baserevisionsize,
510 prefix = mdiff.replacediffheader(delta.baserevisionsize,
529 len(data))
511 len(data))
530
512
531 meta = headerfn(delta)
513 meta = headerfn(delta)
532
514
533 yield chunkheader(len(meta) + len(prefix) + len(data))
515 yield chunkheader(len(meta) + len(prefix) + len(data))
534 yield meta
516 yield meta
535 if prefix:
517 if prefix:
536 yield prefix
518 yield prefix
537 yield data
519 yield data
538
520
539 def _sortnodesellipsis(store, nodes, cl, lookup):
521 def _sortnodesellipsis(store, nodes, cl, lookup):
540 """Sort nodes for changegroup generation."""
522 """Sort nodes for changegroup generation."""
541 # Ellipses serving mode.
523 # Ellipses serving mode.
542 #
524 #
543 # In a perfect world, we'd generate better ellipsis-ified graphs
525 # In a perfect world, we'd generate better ellipsis-ified graphs
544 # for non-changelog revlogs. In practice, we haven't started doing
526 # for non-changelog revlogs. In practice, we haven't started doing
545 # that yet, so the resulting DAGs for the manifestlog and filelogs
527 # that yet, so the resulting DAGs for the manifestlog and filelogs
546 # are actually full of bogus parentage on all the ellipsis
528 # are actually full of bogus parentage on all the ellipsis
547 # nodes. This has the side effect that, while the contents are
529 # nodes. This has the side effect that, while the contents are
548 # correct, the individual DAGs might be completely out of whack in
530 # correct, the individual DAGs might be completely out of whack in
549 # a case like 882681bc3166 and its ancestors (back about 10
531 # a case like 882681bc3166 and its ancestors (back about 10
550 # revisions or so) in the main hg repo.
532 # revisions or so) in the main hg repo.
551 #
533 #
552 # The one invariant we *know* holds is that the new (potentially
534 # The one invariant we *know* holds is that the new (potentially
553 # bogus) DAG shape will be valid if we order the nodes in the
535 # bogus) DAG shape will be valid if we order the nodes in the
554 # order that they're introduced in dramatis personae by the
536 # order that they're introduced in dramatis personae by the
555 # changelog, so what we do is we sort the non-changelog histories
537 # changelog, so what we do is we sort the non-changelog histories
556 # by the order in which they are used by the changelog.
538 # by the order in which they are used by the changelog.
557 key = lambda n: cl.rev(lookup(n))
539 key = lambda n: cl.rev(lookup(n))
558 return sorted(nodes, key=key)
540 return sorted(nodes, key=key)
559
541
560 def _resolvenarrowrevisioninfo(cl, store, ischangelog, rev, linkrev,
542 def _resolvenarrowrevisioninfo(cl, store, ischangelog, rev, linkrev,
561 linknode, clrevtolocalrev, fullclnodes,
543 linknode, clrevtolocalrev, fullclnodes,
562 precomputedellipsis):
544 precomputedellipsis):
563 linkparents = precomputedellipsis[linkrev]
545 linkparents = precomputedellipsis[linkrev]
564 def local(clrev):
546 def local(clrev):
565 """Turn a changelog revnum into a local revnum.
547 """Turn a changelog revnum into a local revnum.
566
548
567 The ellipsis dag is stored as revnums on the changelog,
549 The ellipsis dag is stored as revnums on the changelog,
568 but when we're producing ellipsis entries for
550 but when we're producing ellipsis entries for
569 non-changelog revlogs, we need to turn those numbers into
551 non-changelog revlogs, we need to turn those numbers into
570 something local. This does that for us, and during the
552 something local. This does that for us, and during the
571 changelog sending phase will also expand the stored
553 changelog sending phase will also expand the stored
572 mappings as needed.
554 mappings as needed.
573 """
555 """
574 if clrev == nullrev:
556 if clrev == nullrev:
575 return nullrev
557 return nullrev
576
558
577 if ischangelog:
559 if ischangelog:
578 return clrev
560 return clrev
579
561
580 # Walk the ellipsis-ized changelog breadth-first looking for a
562 # Walk the ellipsis-ized changelog breadth-first looking for a
581 # change that has been linked from the current revlog.
563 # change that has been linked from the current revlog.
582 #
564 #
583 # For a flat manifest revlog only a single step should be necessary
565 # For a flat manifest revlog only a single step should be necessary
584 # as all relevant changelog entries are relevant to the flat
566 # as all relevant changelog entries are relevant to the flat
585 # manifest.
567 # manifest.
586 #
568 #
587 # For a filelog or tree manifest dirlog however not every changelog
569 # For a filelog or tree manifest dirlog however not every changelog
588 # entry will have been relevant, so we need to skip some changelog
570 # entry will have been relevant, so we need to skip some changelog
589 # nodes even after ellipsis-izing.
571 # nodes even after ellipsis-izing.
590 walk = [clrev]
572 walk = [clrev]
591 while walk:
573 while walk:
592 p = walk[0]
574 p = walk[0]
593 walk = walk[1:]
575 walk = walk[1:]
594 if p in clrevtolocalrev:
576 if p in clrevtolocalrev:
595 return clrevtolocalrev[p]
577 return clrevtolocalrev[p]
596 elif p in fullclnodes:
578 elif p in fullclnodes:
597 walk.extend([pp for pp in cl.parentrevs(p)
579 walk.extend([pp for pp in cl.parentrevs(p)
598 if pp != nullrev])
580 if pp != nullrev])
599 elif p in precomputedellipsis:
581 elif p in precomputedellipsis:
600 walk.extend([pp for pp in precomputedellipsis[p]
582 walk.extend([pp for pp in precomputedellipsis[p]
601 if pp != nullrev])
583 if pp != nullrev])
602 else:
584 else:
603 # In this case, we've got an ellipsis with parents
585 # In this case, we've got an ellipsis with parents
604 # outside the current bundle (likely an
586 # outside the current bundle (likely an
605 # incremental pull). We "know" that we can use the
587 # incremental pull). We "know" that we can use the
606 # value of this same revlog at whatever revision
588 # value of this same revlog at whatever revision
607 # is pointed to by linknode. "Know" is in scare
589 # is pointed to by linknode. "Know" is in scare
608 # quotes because I haven't done enough examination
590 # quotes because I haven't done enough examination
609 # of edge cases to convince myself this is really
591 # of edge cases to convince myself this is really
610 # a fact - it works for all the (admittedly
592 # a fact - it works for all the (admittedly
611 # thorough) cases in our testsuite, but I would be
593 # thorough) cases in our testsuite, but I would be
612 # somewhat unsurprised to find a case in the wild
594 # somewhat unsurprised to find a case in the wild
613 # where this breaks down a bit. That said, I don't
595 # where this breaks down a bit. That said, I don't
614 # know if it would hurt anything.
596 # know if it would hurt anything.
615 for i in pycompat.xrange(rev, 0, -1):
597 for i in pycompat.xrange(rev, 0, -1):
616 if store.linkrev(i) == clrev:
598 if store.linkrev(i) == clrev:
617 return i
599 return i
618 # We failed to resolve a parent for this node, so
600 # We failed to resolve a parent for this node, so
619 # we crash the changegroup construction.
601 # we crash the changegroup construction.
620 raise error.Abort(
602 raise error.Abort(
621 'unable to resolve parent while packing %r %r'
603 'unable to resolve parent while packing %r %r'
622 ' for changeset %r' % (store.indexfile, rev, clrev))
604 ' for changeset %r' % (store.indexfile, rev, clrev))
623
605
624 return nullrev
606 return nullrev
625
607
626 if not linkparents or (
608 if not linkparents or (
627 store.parentrevs(rev) == (nullrev, nullrev)):
609 store.parentrevs(rev) == (nullrev, nullrev)):
628 p1, p2 = nullrev, nullrev
610 p1, p2 = nullrev, nullrev
629 elif len(linkparents) == 1:
611 elif len(linkparents) == 1:
630 p1, = sorted(local(p) for p in linkparents)
612 p1, = sorted(local(p) for p in linkparents)
631 p2 = nullrev
613 p2 = nullrev
632 else:
614 else:
633 p1, p2 = sorted(local(p) for p in linkparents)
615 p1, p2 = sorted(local(p) for p in linkparents)
634
616
635 p1node, p2node = store.node(p1), store.node(p2)
617 p1node, p2node = store.node(p1), store.node(p2)
636
618
637 return p1node, p2node, linknode
619 return p1node, p2node, linknode
638
620
639 def deltagroup(repo, store, nodes, ischangelog, lookup, forcedeltaparentprev,
621 def deltagroup(repo, store, nodes, ischangelog, lookup, forcedeltaparentprev,
640 topic=None,
622 topic=None,
641 ellipses=False, clrevtolocalrev=None, fullclnodes=None,
623 ellipses=False, clrevtolocalrev=None, fullclnodes=None,
642 precomputedellipsis=None):
624 precomputedellipsis=None):
643 """Calculate deltas for a set of revisions.
625 """Calculate deltas for a set of revisions.
644
626
645 Is a generator of ``revisiondelta`` instances.
627 Is a generator of ``revisiondelta`` instances.
646
628
647 If topic is not None, progress detail will be generated using this
629 If topic is not None, progress detail will be generated using this
648 topic name (e.g. changesets, manifests, etc).
630 topic name (e.g. changesets, manifests, etc).
649 """
631 """
650 if not nodes:
632 if not nodes:
651 return
633 return
652
634
653 cl = repo.changelog
635 cl = repo.changelog
654
636
655 if ischangelog:
637 if ischangelog:
656 # `hg log` shows changesets in storage order. To preserve order
638 # `hg log` shows changesets in storage order. To preserve order
657 # across clones, send out changesets in storage order.
639 # across clones, send out changesets in storage order.
658 nodesorder = 'storage'
640 nodesorder = 'storage'
659 elif ellipses:
641 elif ellipses:
660 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
642 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
661 nodesorder = 'nodes'
643 nodesorder = 'nodes'
662 else:
644 else:
663 nodesorder = None
645 nodesorder = None
664
646
665 # Perform ellipses filtering and revision massaging. We do this before
647 # Perform ellipses filtering and revision massaging. We do this before
666 # emitrevisions() because a) filtering out revisions creates less work
648 # emitrevisions() because a) filtering out revisions creates less work
667 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
649 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
668 # assumptions about delta choices and we would possibly send a delta
650 # assumptions about delta choices and we would possibly send a delta
669 # referencing a missing base revision.
651 # referencing a missing base revision.
670 #
652 #
671 # Also, calling lookup() has side-effects with regards to populating
653 # Also, calling lookup() has side-effects with regards to populating
672 # data structures. If we don't call lookup() for each node or if we call
654 # data structures. If we don't call lookup() for each node or if we call
673 # lookup() after the first pass through each node, things can break -
655 # lookup() after the first pass through each node, things can break -
674 # possibly intermittently depending on the python hash seed! For that
656 # possibly intermittently depending on the python hash seed! For that
675 # reason, we store a mapping of all linknodes during the initial node
657 # reason, we store a mapping of all linknodes during the initial node
676 # pass rather than use lookup() on the output side.
658 # pass rather than use lookup() on the output side.
677 if ellipses:
659 if ellipses:
678 filtered = []
660 filtered = []
679 adjustedparents = {}
661 adjustedparents = {}
680 linknodes = {}
662 linknodes = {}
681
663
682 for node in nodes:
664 for node in nodes:
683 rev = store.rev(node)
665 rev = store.rev(node)
684 linknode = lookup(node)
666 linknode = lookup(node)
685 linkrev = cl.rev(linknode)
667 linkrev = cl.rev(linknode)
686 clrevtolocalrev[linkrev] = rev
668 clrevtolocalrev[linkrev] = rev
687
669
688 # If linknode is in fullclnodes, it means the corresponding
670 # If linknode is in fullclnodes, it means the corresponding
689 # changeset was a full changeset and is being sent unaltered.
671 # changeset was a full changeset and is being sent unaltered.
690 if linknode in fullclnodes:
672 if linknode in fullclnodes:
691 linknodes[node] = linknode
673 linknodes[node] = linknode
692
674
693 # If the corresponding changeset wasn't in the set computed
675 # If the corresponding changeset wasn't in the set computed
694 # as relevant to us, it should be dropped outright.
676 # as relevant to us, it should be dropped outright.
695 elif linkrev not in precomputedellipsis:
677 elif linkrev not in precomputedellipsis:
696 continue
678 continue
697
679
698 else:
680 else:
699 # We could probably do this later and avoid the dict
681 # We could probably do this later and avoid the dict
700 # holding state. But it likely doesn't matter.
682 # holding state. But it likely doesn't matter.
701 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
683 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
702 cl, store, ischangelog, rev, linkrev, linknode,
684 cl, store, ischangelog, rev, linkrev, linknode,
703 clrevtolocalrev, fullclnodes, precomputedellipsis)
685 clrevtolocalrev, fullclnodes, precomputedellipsis)
704
686
705 adjustedparents[node] = (p1node, p2node)
687 adjustedparents[node] = (p1node, p2node)
706 linknodes[node] = linknode
688 linknodes[node] = linknode
707
689
708 filtered.append(node)
690 filtered.append(node)
709
691
710 nodes = filtered
692 nodes = filtered
711
693
712 # We expect the first pass to be fast, so we only engage the progress
694 # We expect the first pass to be fast, so we only engage the progress
713 # meter for constructing the revision deltas.
695 # meter for constructing the revision deltas.
714 progress = None
696 progress = None
715 if topic is not None:
697 if topic is not None:
716 progress = repo.ui.makeprogress(topic, unit=_('chunks'),
698 progress = repo.ui.makeprogress(topic, unit=_('chunks'),
717 total=len(nodes))
699 total=len(nodes))
718
700
719 revisions = store.emitrevisions(
701 revisions = store.emitrevisions(
720 nodes,
702 nodes,
721 nodesorder=nodesorder,
703 nodesorder=nodesorder,
722 revisiondata=True,
704 revisiondata=True,
723 assumehaveparentrevisions=not ellipses,
705 assumehaveparentrevisions=not ellipses,
724 deltaprevious=forcedeltaparentprev)
706 deltaprevious=forcedeltaparentprev)
725
707
726 for i, revision in enumerate(revisions):
708 for i, revision in enumerate(revisions):
727 if progress:
709 if progress:
728 progress.update(i + 1)
710 progress.update(i + 1)
729
711
730 if ellipses:
712 if ellipses:
731 linknode = linknodes[revision.node]
713 linknode = linknodes[revision.node]
732
714
733 if revision.node in adjustedparents:
715 if revision.node in adjustedparents:
734 p1node, p2node = adjustedparents[revision.node]
716 p1node, p2node = adjustedparents[revision.node]
735 revision.p1node = p1node
717 revision.p1node = p1node
736 revision.p2node = p2node
718 revision.p2node = p2node
737 revision.flags |= revlog.REVIDX_ELLIPSIS
719 revision.flags |= revlog.REVIDX_ELLIPSIS
738
720
739 else:
721 else:
740 linknode = lookup(revision.node)
722 linknode = lookup(revision.node)
741
723
742 revision.linknode = linknode
724 revision.linknode = linknode
743 yield revision
725 yield revision
744
726
745 if progress:
727 if progress:
746 progress.complete()
728 progress.complete()
747
729
748 class cgpacker(object):
730 class cgpacker(object):
749 def __init__(self, repo, filematcher, version,
731 def __init__(self, repo, filematcher, version,
750 builddeltaheader, manifestsend,
732 builddeltaheader, manifestsend,
751 forcedeltaparentprev=False,
733 forcedeltaparentprev=False,
752 bundlecaps=None, ellipses=False,
734 bundlecaps=None, ellipses=False,
753 shallow=False, ellipsisroots=None, fullnodes=None):
735 shallow=False, ellipsisroots=None, fullnodes=None):
754 """Given a source repo, construct a bundler.
736 """Given a source repo, construct a bundler.
755
737
756 filematcher is a matcher that matches on files to include in the
738 filematcher is a matcher that matches on files to include in the
757 changegroup. Used to facilitate sparse changegroups.
739 changegroup. Used to facilitate sparse changegroups.
758
740
759 forcedeltaparentprev indicates whether delta parents must be against
741 forcedeltaparentprev indicates whether delta parents must be against
760 the previous revision in a delta group. This should only be used for
742 the previous revision in a delta group. This should only be used for
761 compatibility with changegroup version 1.
743 compatibility with changegroup version 1.
762
744
763 builddeltaheader is a callable that constructs the header for a group
745 builddeltaheader is a callable that constructs the header for a group
764 delta.
746 delta.
765
747
766 manifestsend is a chunk to send after manifests have been fully emitted.
748 manifestsend is a chunk to send after manifests have been fully emitted.
767
749
768 ellipses indicates whether ellipsis serving mode is enabled.
750 ellipses indicates whether ellipsis serving mode is enabled.
769
751
770 bundlecaps is optional and can be used to specify the set of
752 bundlecaps is optional and can be used to specify the set of
771 capabilities which can be used to build the bundle. While bundlecaps is
753 capabilities which can be used to build the bundle. While bundlecaps is
772 unused in core Mercurial, extensions rely on this feature to communicate
754 unused in core Mercurial, extensions rely on this feature to communicate
773 capabilities to customize the changegroup packer.
755 capabilities to customize the changegroup packer.
774
756
775 shallow indicates whether shallow data might be sent. The packer may
757 shallow indicates whether shallow data might be sent. The packer may
776 need to pack file contents not introduced by the changes being packed.
758 need to pack file contents not introduced by the changes being packed.
777
759
778 fullnodes is the set of changelog nodes which should not be ellipsis
760 fullnodes is the set of changelog nodes which should not be ellipsis
779 nodes. We store this rather than the set of nodes that should be
761 nodes. We store this rather than the set of nodes that should be
780 ellipsis because for very large histories we expect this to be
762 ellipsis because for very large histories we expect this to be
781 significantly smaller.
763 significantly smaller.
782 """
764 """
783 assert filematcher
765 assert filematcher
784 self._filematcher = filematcher
766 self._filematcher = filematcher
785
767
786 self.version = version
768 self.version = version
787 self._forcedeltaparentprev = forcedeltaparentprev
769 self._forcedeltaparentprev = forcedeltaparentprev
788 self._builddeltaheader = builddeltaheader
770 self._builddeltaheader = builddeltaheader
789 self._manifestsend = manifestsend
771 self._manifestsend = manifestsend
790 self._ellipses = ellipses
772 self._ellipses = ellipses
791
773
792 # Set of capabilities we can use to build the bundle.
774 # Set of capabilities we can use to build the bundle.
793 if bundlecaps is None:
775 if bundlecaps is None:
794 bundlecaps = set()
776 bundlecaps = set()
795 self._bundlecaps = bundlecaps
777 self._bundlecaps = bundlecaps
796 self._isshallow = shallow
778 self._isshallow = shallow
797 self._fullclnodes = fullnodes
779 self._fullclnodes = fullnodes
798
780
799 # Maps ellipsis revs to their roots at the changelog level.
781 # Maps ellipsis revs to their roots at the changelog level.
800 self._precomputedellipsis = ellipsisroots
782 self._precomputedellipsis = ellipsisroots
801
783
802 self._repo = repo
784 self._repo = repo
803
785
804 if self._repo.ui.verbose and not self._repo.ui.debugflag:
786 if self._repo.ui.verbose and not self._repo.ui.debugflag:
805 self._verbosenote = self._repo.ui.note
787 self._verbosenote = self._repo.ui.note
806 else:
788 else:
807 self._verbosenote = lambda s: None
789 self._verbosenote = lambda s: None
808
790
809 def generate(self, commonrevs, clnodes, fastpathlinkrev, source,
791 def generate(self, commonrevs, clnodes, fastpathlinkrev, source,
810 changelog=True):
792 changelog=True):
811 """Yield a sequence of changegroup byte chunks.
793 """Yield a sequence of changegroup byte chunks.
812 If changelog is False, changelog data won't be added to changegroup
794 If changelog is False, changelog data won't be added to changegroup
813 """
795 """
814
796
815 repo = self._repo
797 repo = self._repo
816 cl = repo.changelog
798 cl = repo.changelog
817
799
818 self._verbosenote(_('uncompressed size of bundle content:\n'))
800 self._verbosenote(_('uncompressed size of bundle content:\n'))
819 size = 0
801 size = 0
820
802
821 clstate, deltas = self._generatechangelog(cl, clnodes)
803 clstate, deltas = self._generatechangelog(cl, clnodes)
822 for delta in deltas:
804 for delta in deltas:
823 if changelog:
805 if changelog:
824 for chunk in _revisiondeltatochunks(delta,
806 for chunk in _revisiondeltatochunks(delta,
825 self._builddeltaheader):
807 self._builddeltaheader):
826 size += len(chunk)
808 size += len(chunk)
827 yield chunk
809 yield chunk
828
810
829 close = closechunk()
811 close = closechunk()
830 size += len(close)
812 size += len(close)
831 yield closechunk()
813 yield closechunk()
832
814
833 self._verbosenote(_('%8.i (changelog)\n') % size)
815 self._verbosenote(_('%8.i (changelog)\n') % size)
834
816
835 clrevorder = clstate['clrevorder']
817 clrevorder = clstate['clrevorder']
836 manifests = clstate['manifests']
818 manifests = clstate['manifests']
837 changedfiles = clstate['changedfiles']
819 changedfiles = clstate['changedfiles']
838
820
839 # We need to make sure that the linkrev in the changegroup refers to
821 # We need to make sure that the linkrev in the changegroup refers to
840 # the first changeset that introduced the manifest or file revision.
822 # the first changeset that introduced the manifest or file revision.
841 # The fastpath is usually safer than the slowpath, because the filelogs
823 # The fastpath is usually safer than the slowpath, because the filelogs
842 # are walked in revlog order.
824 # are walked in revlog order.
843 #
825 #
844 # When taking the slowpath when the manifest revlog uses generaldelta,
826 # When taking the slowpath when the manifest revlog uses generaldelta,
845 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
827 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
846 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
828 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
847 #
829 #
848 # When taking the fastpath, we are only vulnerable to reordering
830 # When taking the fastpath, we are only vulnerable to reordering
849 # of the changelog itself. The changelog never uses generaldelta and is
831 # of the changelog itself. The changelog never uses generaldelta and is
850 # never reordered. To handle this case, we simply take the slowpath,
832 # never reordered. To handle this case, we simply take the slowpath,
851 # which already has the 'clrevorder' logic. This was also fixed in
833 # which already has the 'clrevorder' logic. This was also fixed in
852 # cc0ff93d0c0c.
834 # cc0ff93d0c0c.
853
835
854 # Treemanifests don't work correctly with fastpathlinkrev
836 # Treemanifests don't work correctly with fastpathlinkrev
855 # either, because we don't discover which directory nodes to
837 # either, because we don't discover which directory nodes to
856 # send along with files. This could probably be fixed.
838 # send along with files. This could probably be fixed.
857 fastpathlinkrev = fastpathlinkrev and (
839 fastpathlinkrev = fastpathlinkrev and (
858 'treemanifest' not in repo.requirements)
840 'treemanifest' not in repo.requirements)
859
841
860 fnodes = {} # needed file nodes
842 fnodes = {} # needed file nodes
861
843
862 size = 0
844 size = 0
863 it = self.generatemanifests(
845 it = self.generatemanifests(
864 commonrevs, clrevorder, fastpathlinkrev, manifests, fnodes, source,
846 commonrevs, clrevorder, fastpathlinkrev, manifests, fnodes, source,
865 clstate['clrevtomanifestrev'])
847 clstate['clrevtomanifestrev'])
866
848
867 for tree, deltas in it:
849 for tree, deltas in it:
868 if tree:
850 if tree:
869 assert self.version == b'03'
851 assert self.version == b'03'
870 chunk = _fileheader(tree)
852 chunk = _fileheader(tree)
871 size += len(chunk)
853 size += len(chunk)
872 yield chunk
854 yield chunk
873
855
874 for delta in deltas:
856 for delta in deltas:
875 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
857 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
876 for chunk in chunks:
858 for chunk in chunks:
877 size += len(chunk)
859 size += len(chunk)
878 yield chunk
860 yield chunk
879
861
880 close = closechunk()
862 close = closechunk()
881 size += len(close)
863 size += len(close)
882 yield close
864 yield close
883
865
884 self._verbosenote(_('%8.i (manifests)\n') % size)
866 self._verbosenote(_('%8.i (manifests)\n') % size)
885 yield self._manifestsend
867 yield self._manifestsend
886
868
887 mfdicts = None
869 mfdicts = None
888 if self._ellipses and self._isshallow:
870 if self._ellipses and self._isshallow:
889 mfdicts = [(self._repo.manifestlog[n].read(), lr)
871 mfdicts = [(self._repo.manifestlog[n].read(), lr)
890 for (n, lr) in manifests.iteritems()]
872 for (n, lr) in manifests.iteritems()]
891
873
892 manifests.clear()
874 manifests.clear()
893 clrevs = set(cl.rev(x) for x in clnodes)
875 clrevs = set(cl.rev(x) for x in clnodes)
894
876
895 it = self.generatefiles(changedfiles, commonrevs,
877 it = self.generatefiles(changedfiles, commonrevs,
896 source, mfdicts, fastpathlinkrev,
878 source, mfdicts, fastpathlinkrev,
897 fnodes, clrevs)
879 fnodes, clrevs)
898
880
899 for path, deltas in it:
881 for path, deltas in it:
900 h = _fileheader(path)
882 h = _fileheader(path)
901 size = len(h)
883 size = len(h)
902 yield h
884 yield h
903
885
904 for delta in deltas:
886 for delta in deltas:
905 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
887 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
906 for chunk in chunks:
888 for chunk in chunks:
907 size += len(chunk)
889 size += len(chunk)
908 yield chunk
890 yield chunk
909
891
910 close = closechunk()
892 close = closechunk()
911 size += len(close)
893 size += len(close)
912 yield close
894 yield close
913
895
914 self._verbosenote(_('%8.i %s\n') % (size, path))
896 self._verbosenote(_('%8.i %s\n') % (size, path))
915
897
916 yield closechunk()
898 yield closechunk()
917
899
918 if clnodes:
900 if clnodes:
919 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
901 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
920
902
921 def _generatechangelog(self, cl, nodes):
903 def _generatechangelog(self, cl, nodes):
922 """Generate data for changelog chunks.
904 """Generate data for changelog chunks.
923
905
924 Returns a 2-tuple of a dict containing state and an iterable of
906 Returns a 2-tuple of a dict containing state and an iterable of
925 byte chunks. The state will not be fully populated until the
907 byte chunks. The state will not be fully populated until the
926 chunk stream has been fully consumed.
908 chunk stream has been fully consumed.
927 """
909 """
928 clrevorder = {}
910 clrevorder = {}
929 manifests = {}
911 manifests = {}
930 mfl = self._repo.manifestlog
912 mfl = self._repo.manifestlog
931 changedfiles = set()
913 changedfiles = set()
932 clrevtomanifestrev = {}
914 clrevtomanifestrev = {}
933
915
934 # Callback for the changelog, used to collect changed files and
916 # Callback for the changelog, used to collect changed files and
935 # manifest nodes.
917 # manifest nodes.
936 # Returns the linkrev node (identity in the changelog case).
918 # Returns the linkrev node (identity in the changelog case).
937 def lookupcl(x):
919 def lookupcl(x):
938 c = cl.changelogrevision(x)
920 c = cl.changelogrevision(x)
939 clrevorder[x] = len(clrevorder)
921 clrevorder[x] = len(clrevorder)
940
922
941 if self._ellipses:
923 if self._ellipses:
942 # Only update manifests if x is going to be sent. Otherwise we
924 # Only update manifests if x is going to be sent. Otherwise we
943 # end up with bogus linkrevs specified for manifests and
925 # end up with bogus linkrevs specified for manifests and
944 # we skip some manifest nodes that we should otherwise
926 # we skip some manifest nodes that we should otherwise
945 # have sent.
927 # have sent.
946 if (x in self._fullclnodes
928 if (x in self._fullclnodes
947 or cl.rev(x) in self._precomputedellipsis):
929 or cl.rev(x) in self._precomputedellipsis):
948
930
949 manifestnode = c.manifest
931 manifestnode = c.manifest
950 # Record the first changeset introducing this manifest
932 # Record the first changeset introducing this manifest
951 # version.
933 # version.
952 manifests.setdefault(manifestnode, x)
934 manifests.setdefault(manifestnode, x)
953 # Set this narrow-specific dict so we have the lowest
935 # Set this narrow-specific dict so we have the lowest
954 # manifest revnum to look up for this cl revnum. (Part of
936 # manifest revnum to look up for this cl revnum. (Part of
955 # mapping changelog ellipsis parents to manifest ellipsis
937 # mapping changelog ellipsis parents to manifest ellipsis
956 # parents)
938 # parents)
957 clrevtomanifestrev.setdefault(
939 clrevtomanifestrev.setdefault(
958 cl.rev(x), mfl.rev(manifestnode))
940 cl.rev(x), mfl.rev(manifestnode))
959 # We can't trust the changed files list in the changeset if the
941 # We can't trust the changed files list in the changeset if the
960 # client requested a shallow clone.
942 # client requested a shallow clone.
961 if self._isshallow:
943 if self._isshallow:
962 changedfiles.update(mfl[c.manifest].read().keys())
944 changedfiles.update(mfl[c.manifest].read().keys())
963 else:
945 else:
964 changedfiles.update(c.files)
946 changedfiles.update(c.files)
965 else:
947 else:
966 # record the first changeset introducing this manifest version
948 # record the first changeset introducing this manifest version
967 manifests.setdefault(c.manifest, x)
949 manifests.setdefault(c.manifest, x)
968 # Record a complete list of potentially-changed files in
950 # Record a complete list of potentially-changed files in
969 # this manifest.
951 # this manifest.
970 changedfiles.update(c.files)
952 changedfiles.update(c.files)
971
953
972 return x
954 return x
973
955
974 state = {
956 state = {
975 'clrevorder': clrevorder,
957 'clrevorder': clrevorder,
976 'manifests': manifests,
958 'manifests': manifests,
977 'changedfiles': changedfiles,
959 'changedfiles': changedfiles,
978 'clrevtomanifestrev': clrevtomanifestrev,
960 'clrevtomanifestrev': clrevtomanifestrev,
979 }
961 }
980
962
981 gen = deltagroup(
963 gen = deltagroup(
982 self._repo, cl, nodes, True, lookupcl,
964 self._repo, cl, nodes, True, lookupcl,
983 self._forcedeltaparentprev,
965 self._forcedeltaparentprev,
984 ellipses=self._ellipses,
966 ellipses=self._ellipses,
985 topic=_('changesets'),
967 topic=_('changesets'),
986 clrevtolocalrev={},
968 clrevtolocalrev={},
987 fullclnodes=self._fullclnodes,
969 fullclnodes=self._fullclnodes,
988 precomputedellipsis=self._precomputedellipsis)
970 precomputedellipsis=self._precomputedellipsis)
989
971
990 return state, gen
972 return state, gen
991
973
992 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev,
974 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev,
993 manifests, fnodes, source, clrevtolocalrev):
975 manifests, fnodes, source, clrevtolocalrev):
994 """Returns an iterator of changegroup chunks containing manifests.
976 """Returns an iterator of changegroup chunks containing manifests.
995
977
996 `source` is unused here, but is used by extensions like remotefilelog to
978 `source` is unused here, but is used by extensions like remotefilelog to
997 change what is sent based in pulls vs pushes, etc.
979 change what is sent based in pulls vs pushes, etc.
998 """
980 """
999 repo = self._repo
981 repo = self._repo
1000 mfl = repo.manifestlog
982 mfl = repo.manifestlog
1001 tmfnodes = {'': manifests}
983 tmfnodes = {'': manifests}
1002
984
1003 # Callback for the manifest, used to collect linkrevs for filelog
985 # Callback for the manifest, used to collect linkrevs for filelog
1004 # revisions.
986 # revisions.
1005 # Returns the linkrev node (collected in lookupcl).
987 # Returns the linkrev node (collected in lookupcl).
1006 def makelookupmflinknode(tree, nodes):
988 def makelookupmflinknode(tree, nodes):
1007 if fastpathlinkrev:
989 if fastpathlinkrev:
1008 assert not tree
990 assert not tree
1009 return manifests.__getitem__
991 return manifests.__getitem__
1010
992
1011 def lookupmflinknode(x):
993 def lookupmflinknode(x):
1012 """Callback for looking up the linknode for manifests.
994 """Callback for looking up the linknode for manifests.
1013
995
1014 Returns the linkrev node for the specified manifest.
996 Returns the linkrev node for the specified manifest.
1015
997
1016 SIDE EFFECT:
998 SIDE EFFECT:
1017
999
1018 1) fclnodes gets populated with the list of relevant
1000 1) fclnodes gets populated with the list of relevant
1019 file nodes if we're not using fastpathlinkrev
1001 file nodes if we're not using fastpathlinkrev
1020 2) When treemanifests are in use, collects treemanifest nodes
1002 2) When treemanifests are in use, collects treemanifest nodes
1021 to send
1003 to send
1022
1004
1023 Note that this means manifests must be completely sent to
1005 Note that this means manifests must be completely sent to
1024 the client before you can trust the list of files and
1006 the client before you can trust the list of files and
1025 treemanifests to send.
1007 treemanifests to send.
1026 """
1008 """
1027 clnode = nodes[x]
1009 clnode = nodes[x]
1028 mdata = mfl.get(tree, x).readfast(shallow=True)
1010 mdata = mfl.get(tree, x).readfast(shallow=True)
1029 for p, n, fl in mdata.iterentries():
1011 for p, n, fl in mdata.iterentries():
1030 if fl == 't': # subdirectory manifest
1012 if fl == 't': # subdirectory manifest
1031 subtree = tree + p + '/'
1013 subtree = tree + p + '/'
1032 tmfclnodes = tmfnodes.setdefault(subtree, {})
1014 tmfclnodes = tmfnodes.setdefault(subtree, {})
1033 tmfclnode = tmfclnodes.setdefault(n, clnode)
1015 tmfclnode = tmfclnodes.setdefault(n, clnode)
1034 if clrevorder[clnode] < clrevorder[tmfclnode]:
1016 if clrevorder[clnode] < clrevorder[tmfclnode]:
1035 tmfclnodes[n] = clnode
1017 tmfclnodes[n] = clnode
1036 else:
1018 else:
1037 f = tree + p
1019 f = tree + p
1038 fclnodes = fnodes.setdefault(f, {})
1020 fclnodes = fnodes.setdefault(f, {})
1039 fclnode = fclnodes.setdefault(n, clnode)
1021 fclnode = fclnodes.setdefault(n, clnode)
1040 if clrevorder[clnode] < clrevorder[fclnode]:
1022 if clrevorder[clnode] < clrevorder[fclnode]:
1041 fclnodes[n] = clnode
1023 fclnodes[n] = clnode
1042 return clnode
1024 return clnode
1043 return lookupmflinknode
1025 return lookupmflinknode
1044
1026
1045 while tmfnodes:
1027 while tmfnodes:
1046 tree, nodes = tmfnodes.popitem()
1028 tree, nodes = tmfnodes.popitem()
1047 store = mfl.getstorage(tree)
1029 store = mfl.getstorage(tree)
1048
1030
1049 if not self._filematcher.visitdir(store.tree[:-1] or '.'):
1031 if not self._filematcher.visitdir(store.tree[:-1] or '.'):
1050 # No nodes to send because this directory is out of
1032 # No nodes to send because this directory is out of
1051 # the client's view of the repository (probably
1033 # the client's view of the repository (probably
1052 # because of narrow clones).
1034 # because of narrow clones).
1053 prunednodes = []
1035 prunednodes = []
1054 else:
1036 else:
1055 # Avoid sending any manifest nodes we can prove the
1037 # Avoid sending any manifest nodes we can prove the
1056 # client already has by checking linkrevs. See the
1038 # client already has by checking linkrevs. See the
1057 # related comment in generatefiles().
1039 # related comment in generatefiles().
1058 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1040 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1059 if tree and not prunednodes:
1041 if tree and not prunednodes:
1060 continue
1042 continue
1061
1043
1062 lookupfn = makelookupmflinknode(tree, nodes)
1044 lookupfn = makelookupmflinknode(tree, nodes)
1063
1045
1064 deltas = deltagroup(
1046 deltas = deltagroup(
1065 self._repo, store, prunednodes, False, lookupfn,
1047 self._repo, store, prunednodes, False, lookupfn,
1066 self._forcedeltaparentprev,
1048 self._forcedeltaparentprev,
1067 ellipses=self._ellipses,
1049 ellipses=self._ellipses,
1068 topic=_('manifests'),
1050 topic=_('manifests'),
1069 clrevtolocalrev=clrevtolocalrev,
1051 clrevtolocalrev=clrevtolocalrev,
1070 fullclnodes=self._fullclnodes,
1052 fullclnodes=self._fullclnodes,
1071 precomputedellipsis=self._precomputedellipsis)
1053 precomputedellipsis=self._precomputedellipsis)
1072
1054
1073 yield tree, deltas
1055 yield tree, deltas
1074
1056
1075 def _prunemanifests(self, store, nodes, commonrevs):
1057 def _prunemanifests(self, store, nodes, commonrevs):
1076 # This is split out as a separate method to allow filtering
1058 # This is split out as a separate method to allow filtering
1077 # commonrevs in extension code.
1059 # commonrevs in extension code.
1078 #
1060 #
1079 # TODO(augie): this shouldn't be required, instead we should
1061 # TODO(augie): this shouldn't be required, instead we should
1080 # make filtering of revisions to send delegated to the store
1062 # make filtering of revisions to send delegated to the store
1081 # layer.
1063 # layer.
1082 frev, flr = store.rev, store.linkrev
1064 frev, flr = store.rev, store.linkrev
1083 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1065 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1084
1066
1085 # The 'source' parameter is useful for extensions
1067 # The 'source' parameter is useful for extensions
1086 def generatefiles(self, changedfiles, commonrevs, source,
1068 def generatefiles(self, changedfiles, commonrevs, source,
1087 mfdicts, fastpathlinkrev, fnodes, clrevs):
1069 mfdicts, fastpathlinkrev, fnodes, clrevs):
1088 changedfiles = list(filter(self._filematcher, changedfiles))
1070 changedfiles = list(filter(self._filematcher, changedfiles))
1089
1071
1090 if not fastpathlinkrev:
1072 if not fastpathlinkrev:
1091 def normallinknodes(unused, fname):
1073 def normallinknodes(unused, fname):
1092 return fnodes.get(fname, {})
1074 return fnodes.get(fname, {})
1093 else:
1075 else:
1094 cln = self._repo.changelog.node
1076 cln = self._repo.changelog.node
1095
1077
1096 def normallinknodes(store, fname):
1078 def normallinknodes(store, fname):
1097 flinkrev = store.linkrev
1079 flinkrev = store.linkrev
1098 fnode = store.node
1080 fnode = store.node
1099 revs = ((r, flinkrev(r)) for r in store)
1081 revs = ((r, flinkrev(r)) for r in store)
1100 return dict((fnode(r), cln(lr))
1082 return dict((fnode(r), cln(lr))
1101 for r, lr in revs if lr in clrevs)
1083 for r, lr in revs if lr in clrevs)
1102
1084
1103 clrevtolocalrev = {}
1085 clrevtolocalrev = {}
1104
1086
1105 if self._isshallow:
1087 if self._isshallow:
1106 # In a shallow clone, the linknodes callback needs to also include
1088 # In a shallow clone, the linknodes callback needs to also include
1107 # those file nodes that are in the manifests we sent but weren't
1089 # those file nodes that are in the manifests we sent but weren't
1108 # introduced by those manifests.
1090 # introduced by those manifests.
1109 commonctxs = [self._repo[c] for c in commonrevs]
1091 commonctxs = [self._repo[c] for c in commonrevs]
1110 clrev = self._repo.changelog.rev
1092 clrev = self._repo.changelog.rev
1111
1093
1112 def linknodes(flog, fname):
1094 def linknodes(flog, fname):
1113 for c in commonctxs:
1095 for c in commonctxs:
1114 try:
1096 try:
1115 fnode = c.filenode(fname)
1097 fnode = c.filenode(fname)
1116 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1098 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1117 except error.ManifestLookupError:
1099 except error.ManifestLookupError:
1118 pass
1100 pass
1119 links = normallinknodes(flog, fname)
1101 links = normallinknodes(flog, fname)
1120 if len(links) != len(mfdicts):
1102 if len(links) != len(mfdicts):
1121 for mf, lr in mfdicts:
1103 for mf, lr in mfdicts:
1122 fnode = mf.get(fname, None)
1104 fnode = mf.get(fname, None)
1123 if fnode in links:
1105 if fnode in links:
1124 links[fnode] = min(links[fnode], lr, key=clrev)
1106 links[fnode] = min(links[fnode], lr, key=clrev)
1125 elif fnode:
1107 elif fnode:
1126 links[fnode] = lr
1108 links[fnode] = lr
1127 return links
1109 return links
1128 else:
1110 else:
1129 linknodes = normallinknodes
1111 linknodes = normallinknodes
1130
1112
1131 repo = self._repo
1113 repo = self._repo
1132 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1114 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1133 total=len(changedfiles))
1115 total=len(changedfiles))
1134 for i, fname in enumerate(sorted(changedfiles)):
1116 for i, fname in enumerate(sorted(changedfiles)):
1135 filerevlog = repo.file(fname)
1117 filerevlog = repo.file(fname)
1136 if not filerevlog:
1118 if not filerevlog:
1137 raise error.Abort(_("empty or missing file data for %s") %
1119 raise error.Abort(_("empty or missing file data for %s") %
1138 fname)
1120 fname)
1139
1121
1140 clrevtolocalrev.clear()
1122 clrevtolocalrev.clear()
1141
1123
1142 linkrevnodes = linknodes(filerevlog, fname)
1124 linkrevnodes = linknodes(filerevlog, fname)
1143 # Lookup for filenodes, we collected the linkrev nodes above in the
1125 # Lookup for filenodes, we collected the linkrev nodes above in the
1144 # fastpath case and with lookupmf in the slowpath case.
1126 # fastpath case and with lookupmf in the slowpath case.
1145 def lookupfilelog(x):
1127 def lookupfilelog(x):
1146 return linkrevnodes[x]
1128 return linkrevnodes[x]
1147
1129
1148 frev, flr = filerevlog.rev, filerevlog.linkrev
1130 frev, flr = filerevlog.rev, filerevlog.linkrev
1149 # Skip sending any filenode we know the client already
1131 # Skip sending any filenode we know the client already
1150 # has. This avoids over-sending files relatively
1132 # has. This avoids over-sending files relatively
1151 # inexpensively, so it's not a problem if we under-filter
1133 # inexpensively, so it's not a problem if we under-filter
1152 # here.
1134 # here.
1153 filenodes = [n for n in linkrevnodes
1135 filenodes = [n for n in linkrevnodes
1154 if flr(frev(n)) not in commonrevs]
1136 if flr(frev(n)) not in commonrevs]
1155
1137
1156 if not filenodes:
1138 if not filenodes:
1157 continue
1139 continue
1158
1140
1159 progress.update(i + 1, item=fname)
1141 progress.update(i + 1, item=fname)
1160
1142
1161 deltas = deltagroup(
1143 deltas = deltagroup(
1162 self._repo, filerevlog, filenodes, False, lookupfilelog,
1144 self._repo, filerevlog, filenodes, False, lookupfilelog,
1163 self._forcedeltaparentprev,
1145 self._forcedeltaparentprev,
1164 ellipses=self._ellipses,
1146 ellipses=self._ellipses,
1165 clrevtolocalrev=clrevtolocalrev,
1147 clrevtolocalrev=clrevtolocalrev,
1166 fullclnodes=self._fullclnodes,
1148 fullclnodes=self._fullclnodes,
1167 precomputedellipsis=self._precomputedellipsis)
1149 precomputedellipsis=self._precomputedellipsis)
1168
1150
1169 yield fname, deltas
1151 yield fname, deltas
1170
1152
1171 progress.complete()
1153 progress.complete()
1172
1154
1173 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
1155 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
1174 shallow=False, ellipsisroots=None, fullnodes=None):
1156 shallow=False, ellipsisroots=None, fullnodes=None):
1175 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1157 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1176 d.node, d.p1node, d.p2node, d.linknode)
1158 d.node, d.p1node, d.p2node, d.linknode)
1177
1159
1178 return cgpacker(repo, filematcher, b'01',
1160 return cgpacker(repo, filematcher, b'01',
1179 builddeltaheader=builddeltaheader,
1161 builddeltaheader=builddeltaheader,
1180 manifestsend=b'',
1162 manifestsend=b'',
1181 forcedeltaparentprev=True,
1163 forcedeltaparentprev=True,
1182 bundlecaps=bundlecaps,
1164 bundlecaps=bundlecaps,
1183 ellipses=ellipses,
1165 ellipses=ellipses,
1184 shallow=shallow,
1166 shallow=shallow,
1185 ellipsisroots=ellipsisroots,
1167 ellipsisroots=ellipsisroots,
1186 fullnodes=fullnodes)
1168 fullnodes=fullnodes)
1187
1169
1188 def _makecg2packer(repo, filematcher, bundlecaps, ellipses=False,
1170 def _makecg2packer(repo, filematcher, bundlecaps, ellipses=False,
1189 shallow=False, ellipsisroots=None, fullnodes=None):
1171 shallow=False, ellipsisroots=None, fullnodes=None):
1190 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1172 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1191 d.node, d.p1node, d.p2node, d.basenode, d.linknode)
1173 d.node, d.p1node, d.p2node, d.basenode, d.linknode)
1192
1174
1193 return cgpacker(repo, filematcher, b'02',
1175 return cgpacker(repo, filematcher, b'02',
1194 builddeltaheader=builddeltaheader,
1176 builddeltaheader=builddeltaheader,
1195 manifestsend=b'',
1177 manifestsend=b'',
1196 bundlecaps=bundlecaps,
1178 bundlecaps=bundlecaps,
1197 ellipses=ellipses,
1179 ellipses=ellipses,
1198 shallow=shallow,
1180 shallow=shallow,
1199 ellipsisroots=ellipsisroots,
1181 ellipsisroots=ellipsisroots,
1200 fullnodes=fullnodes)
1182 fullnodes=fullnodes)
1201
1183
1202 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
1184 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
1203 shallow=False, ellipsisroots=None, fullnodes=None):
1185 shallow=False, ellipsisroots=None, fullnodes=None):
1204 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1186 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1205 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1187 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1206
1188
1207 return cgpacker(repo, filematcher, b'03',
1189 return cgpacker(repo, filematcher, b'03',
1208 builddeltaheader=builddeltaheader,
1190 builddeltaheader=builddeltaheader,
1209 manifestsend=closechunk(),
1191 manifestsend=closechunk(),
1210 bundlecaps=bundlecaps,
1192 bundlecaps=bundlecaps,
1211 ellipses=ellipses,
1193 ellipses=ellipses,
1212 shallow=shallow,
1194 shallow=shallow,
1213 ellipsisroots=ellipsisroots,
1195 ellipsisroots=ellipsisroots,
1214 fullnodes=fullnodes)
1196 fullnodes=fullnodes)
1215
1197
1216 _packermap = {'01': (_makecg1packer, cg1unpacker),
1198 _packermap = {'01': (_makecg1packer, cg1unpacker),
1217 # cg2 adds support for exchanging generaldelta
1199 # cg2 adds support for exchanging generaldelta
1218 '02': (_makecg2packer, cg2unpacker),
1200 '02': (_makecg2packer, cg2unpacker),
1219 # cg3 adds support for exchanging revlog flags and treemanifests
1201 # cg3 adds support for exchanging revlog flags and treemanifests
1220 '03': (_makecg3packer, cg3unpacker),
1202 '03': (_makecg3packer, cg3unpacker),
1221 }
1203 }
1222
1204
1223 def allsupportedversions(repo):
1205 def allsupportedversions(repo):
1224 versions = set(_packermap.keys())
1206 versions = set(_packermap.keys())
1225 if not (repo.ui.configbool('experimental', 'changegroup3') or
1207 if not (repo.ui.configbool('experimental', 'changegroup3') or
1226 repo.ui.configbool('experimental', 'treemanifest') or
1208 repo.ui.configbool('experimental', 'treemanifest') or
1227 'treemanifest' in repo.requirements):
1209 'treemanifest' in repo.requirements):
1228 versions.discard('03')
1210 versions.discard('03')
1229 return versions
1211 return versions
1230
1212
1231 # Changegroup versions that can be applied to the repo
1213 # Changegroup versions that can be applied to the repo
1232 def supportedincomingversions(repo):
1214 def supportedincomingversions(repo):
1233 return allsupportedversions(repo)
1215 return allsupportedversions(repo)
1234
1216
1235 # Changegroup versions that can be created from the repo
1217 # Changegroup versions that can be created from the repo
1236 def supportedoutgoingversions(repo):
1218 def supportedoutgoingversions(repo):
1237 versions = allsupportedversions(repo)
1219 versions = allsupportedversions(repo)
1238 if 'treemanifest' in repo.requirements:
1220 if 'treemanifest' in repo.requirements:
1239 # Versions 01 and 02 support only flat manifests and it's just too
1221 # Versions 01 and 02 support only flat manifests and it's just too
1240 # expensive to convert between the flat manifest and tree manifest on
1222 # expensive to convert between the flat manifest and tree manifest on
1241 # the fly. Since tree manifests are hashed differently, all of history
1223 # the fly. Since tree manifests are hashed differently, all of history
1242 # would have to be converted. Instead, we simply don't even pretend to
1224 # would have to be converted. Instead, we simply don't even pretend to
1243 # support versions 01 and 02.
1225 # support versions 01 and 02.
1244 versions.discard('01')
1226 versions.discard('01')
1245 versions.discard('02')
1227 versions.discard('02')
1246 if repository.NARROW_REQUIREMENT in repo.requirements:
1228 if repository.NARROW_REQUIREMENT in repo.requirements:
1247 # Versions 01 and 02 don't support revlog flags, and we need to
1229 # Versions 01 and 02 don't support revlog flags, and we need to
1248 # support that for stripping and unbundling to work.
1230 # support that for stripping and unbundling to work.
1249 versions.discard('01')
1231 versions.discard('01')
1250 versions.discard('02')
1232 versions.discard('02')
1251 if LFS_REQUIREMENT in repo.requirements:
1233 if LFS_REQUIREMENT in repo.requirements:
1252 # Versions 01 and 02 don't support revlog flags, and we need to
1234 # Versions 01 and 02 don't support revlog flags, and we need to
1253 # mark LFS entries with REVIDX_EXTSTORED.
1235 # mark LFS entries with REVIDX_EXTSTORED.
1254 versions.discard('01')
1236 versions.discard('01')
1255 versions.discard('02')
1237 versions.discard('02')
1256
1238
1257 return versions
1239 return versions
1258
1240
1259 def localversion(repo):
1241 def localversion(repo):
1260 # Finds the best version to use for bundles that are meant to be used
1242 # Finds the best version to use for bundles that are meant to be used
1261 # locally, such as those from strip and shelve, and temporary bundles.
1243 # locally, such as those from strip and shelve, and temporary bundles.
1262 return max(supportedoutgoingversions(repo))
1244 return max(supportedoutgoingversions(repo))
1263
1245
1264 def safeversion(repo):
1246 def safeversion(repo):
1265 # Finds the smallest version that it's safe to assume clients of the repo
1247 # Finds the smallest version that it's safe to assume clients of the repo
1266 # will support. For example, all hg versions that support generaldelta also
1248 # will support. For example, all hg versions that support generaldelta also
1267 # support changegroup 02.
1249 # support changegroup 02.
1268 versions = supportedoutgoingversions(repo)
1250 versions = supportedoutgoingversions(repo)
1269 if 'generaldelta' in repo.requirements:
1251 if 'generaldelta' in repo.requirements:
1270 versions.discard('01')
1252 versions.discard('01')
1271 assert versions
1253 assert versions
1272 return min(versions)
1254 return min(versions)
1273
1255
1274 def getbundler(version, repo, bundlecaps=None, filematcher=None,
1256 def getbundler(version, repo, bundlecaps=None, filematcher=None,
1275 ellipses=False, shallow=False, ellipsisroots=None,
1257 ellipses=False, shallow=False, ellipsisroots=None,
1276 fullnodes=None):
1258 fullnodes=None):
1277 assert version in supportedoutgoingversions(repo)
1259 assert version in supportedoutgoingversions(repo)
1278
1260
1279 if filematcher is None:
1261 if filematcher is None:
1280 filematcher = matchmod.alwaysmatcher(repo.root, '')
1262 filematcher = matchmod.alwaysmatcher(repo.root, '')
1281
1263
1282 if version == '01' and not filematcher.always():
1264 if version == '01' and not filematcher.always():
1283 raise error.ProgrammingError('version 01 changegroups do not support '
1265 raise error.ProgrammingError('version 01 changegroups do not support '
1284 'sparse file matchers')
1266 'sparse file matchers')
1285
1267
1286 if ellipses and version in (b'01', b'02'):
1268 if ellipses and version in (b'01', b'02'):
1287 raise error.Abort(
1269 raise error.Abort(
1288 _('ellipsis nodes require at least cg3 on client and server, '
1270 _('ellipsis nodes require at least cg3 on client and server, '
1289 'but negotiated version %s') % version)
1271 'but negotiated version %s') % version)
1290
1272
1291 # Requested files could include files not in the local store. So
1273 # Requested files could include files not in the local store. So
1292 # filter those out.
1274 # filter those out.
1293 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1275 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1294 filematcher)
1276 filematcher)
1295
1277
1296 fn = _packermap[version][0]
1278 fn = _packermap[version][0]
1297 return fn(repo, filematcher, bundlecaps, ellipses=ellipses,
1279 return fn(repo, filematcher, bundlecaps, ellipses=ellipses,
1298 shallow=shallow, ellipsisroots=ellipsisroots,
1280 shallow=shallow, ellipsisroots=ellipsisroots,
1299 fullnodes=fullnodes)
1281 fullnodes=fullnodes)
1300
1282
1301 def getunbundler(version, fh, alg, extras=None):
1283 def getunbundler(version, fh, alg, extras=None):
1302 return _packermap[version][1](fh, alg, extras=extras)
1284 return _packermap[version][1](fh, alg, extras=extras)
1303
1285
1304 def _changegroupinfo(repo, nodes, source):
1286 def _changegroupinfo(repo, nodes, source):
1305 if repo.ui.verbose or source == 'bundle':
1287 if repo.ui.verbose or source == 'bundle':
1306 repo.ui.status(_("%d changesets found\n") % len(nodes))
1288 repo.ui.status(_("%d changesets found\n") % len(nodes))
1307 if repo.ui.debugflag:
1289 if repo.ui.debugflag:
1308 repo.ui.debug("list of changesets:\n")
1290 repo.ui.debug("list of changesets:\n")
1309 for node in nodes:
1291 for node in nodes:
1310 repo.ui.debug("%s\n" % hex(node))
1292 repo.ui.debug("%s\n" % hex(node))
1311
1293
1312 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1294 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1313 bundlecaps=None):
1295 bundlecaps=None):
1314 cgstream = makestream(repo, outgoing, version, source,
1296 cgstream = makestream(repo, outgoing, version, source,
1315 fastpath=fastpath, bundlecaps=bundlecaps)
1297 fastpath=fastpath, bundlecaps=bundlecaps)
1316 return getunbundler(version, util.chunkbuffer(cgstream), None,
1298 return getunbundler(version, util.chunkbuffer(cgstream), None,
1317 {'clcount': len(outgoing.missing) })
1299 {'clcount': len(outgoing.missing) })
1318
1300
1319 def makestream(repo, outgoing, version, source, fastpath=False,
1301 def makestream(repo, outgoing, version, source, fastpath=False,
1320 bundlecaps=None, filematcher=None):
1302 bundlecaps=None, filematcher=None):
1321 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1303 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1322 filematcher=filematcher)
1304 filematcher=filematcher)
1323
1305
1324 repo = repo.unfiltered()
1306 repo = repo.unfiltered()
1325 commonrevs = outgoing.common
1307 commonrevs = outgoing.common
1326 csets = outgoing.missing
1308 csets = outgoing.missing
1327 heads = outgoing.missingheads
1309 heads = outgoing.missingheads
1328 # We go through the fast path if we get told to, or if all (unfiltered
1310 # We go through the fast path if we get told to, or if all (unfiltered
1329 # heads have been requested (since we then know there all linkrevs will
1311 # heads have been requested (since we then know there all linkrevs will
1330 # be pulled by the client).
1312 # be pulled by the client).
1331 heads.sort()
1313 heads.sort()
1332 fastpathlinkrev = fastpath or (
1314 fastpathlinkrev = fastpath or (
1333 repo.filtername is None and heads == sorted(repo.heads()))
1315 repo.filtername is None and heads == sorted(repo.heads()))
1334
1316
1335 repo.hook('preoutgoing', throw=True, source=source)
1317 repo.hook('preoutgoing', throw=True, source=source)
1336 _changegroupinfo(repo, csets, source)
1318 _changegroupinfo(repo, csets, source)
1337 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1319 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1338
1320
1339 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1321 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1340 revisions = 0
1322 revisions = 0
1341 files = 0
1323 files = 0
1342 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1324 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1343 total=expectedfiles)
1325 total=expectedfiles)
1344 for chunkdata in iter(source.filelogheader, {}):
1326 for chunkdata in iter(source.filelogheader, {}):
1345 files += 1
1327 files += 1
1346 f = chunkdata["filename"]
1328 f = chunkdata["filename"]
1347 repo.ui.debug("adding %s revisions\n" % f)
1329 repo.ui.debug("adding %s revisions\n" % f)
1348 progress.increment()
1330 progress.increment()
1349 fl = repo.file(f)
1331 fl = repo.file(f)
1350 o = len(fl)
1332 o = len(fl)
1351 try:
1333 try:
1352 deltas = source.deltaiter()
1334 deltas = source.deltaiter()
1353 if not fl.addgroup(deltas, revmap, trp):
1335 if not fl.addgroup(deltas, revmap, trp):
1354 raise error.Abort(_("received file revlog group is empty"))
1336 raise error.Abort(_("received file revlog group is empty"))
1355 except error.CensoredBaseError as e:
1337 except error.CensoredBaseError as e:
1356 raise error.Abort(_("received delta base is censored: %s") % e)
1338 raise error.Abort(_("received delta base is censored: %s") % e)
1357 revisions += len(fl) - o
1339 revisions += len(fl) - o
1358 if f in needfiles:
1340 if f in needfiles:
1359 needs = needfiles[f]
1341 needs = needfiles[f]
1360 for new in pycompat.xrange(o, len(fl)):
1342 for new in pycompat.xrange(o, len(fl)):
1361 n = fl.node(new)
1343 n = fl.node(new)
1362 if n in needs:
1344 if n in needs:
1363 needs.remove(n)
1345 needs.remove(n)
1364 else:
1346 else:
1365 raise error.Abort(
1347 raise error.Abort(
1366 _("received spurious file revlog entry"))
1348 _("received spurious file revlog entry"))
1367 if not needs:
1349 if not needs:
1368 del needfiles[f]
1350 del needfiles[f]
1369 progress.complete()
1351 progress.complete()
1370
1352
1371 for f, needs in needfiles.iteritems():
1353 for f, needs in needfiles.iteritems():
1372 fl = repo.file(f)
1354 fl = repo.file(f)
1373 for n in needs:
1355 for n in needs:
1374 try:
1356 try:
1375 fl.rev(n)
1357 fl.rev(n)
1376 except error.LookupError:
1358 except error.LookupError:
1377 raise error.Abort(
1359 raise error.Abort(
1378 _('missing file data for %s:%s - run hg verify') %
1360 _('missing file data for %s:%s - run hg verify') %
1379 (f, hex(n)))
1361 (f, hex(n)))
1380
1362
1381 return revisions, files
1363 return revisions, files
@@ -1,268 +1,265 b''
1 # filelog.py - file history class for mercurial
1 # filelog.py - file history 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 from . import (
10 from . import (
11 error,
11 error,
12 repository,
12 repository,
13 revlog,
13 revlog,
14 )
14 )
15 from .utils import (
15 from .utils import (
16 interfaceutil,
16 interfaceutil,
17 )
17 )
18
18
19 @interfaceutil.implementer(repository.ifilestorage)
19 @interfaceutil.implementer(repository.ifilestorage)
20 class filelog(object):
20 class filelog(object):
21 def __init__(self, opener, path):
21 def __init__(self, opener, path):
22 self._revlog = revlog.revlog(opener,
22 self._revlog = revlog.revlog(opener,
23 '/'.join(('data', path + '.i')),
23 '/'.join(('data', path + '.i')),
24 censorable=True)
24 censorable=True)
25 # Full name of the user visible file, relative to the repository root.
25 # Full name of the user visible file, relative to the repository root.
26 # Used by LFS.
26 # Used by LFS.
27 self._revlog.filename = path
27 self._revlog.filename = path
28 # Used by changegroup generation.
28 # Used by changegroup generation.
29 self._generaldelta = self._revlog._generaldelta
29 self._generaldelta = self._revlog._generaldelta
30
30
31 def __len__(self):
31 def __len__(self):
32 return len(self._revlog)
32 return len(self._revlog)
33
33
34 def __iter__(self):
34 def __iter__(self):
35 return self._revlog.__iter__()
35 return self._revlog.__iter__()
36
36
37 def revs(self, start=0, stop=None):
37 def revs(self, start=0, stop=None):
38 return self._revlog.revs(start=start, stop=stop)
38 return self._revlog.revs(start=start, stop=stop)
39
39
40 def parents(self, node):
40 def parents(self, node):
41 return self._revlog.parents(node)
41 return self._revlog.parents(node)
42
42
43 def parentrevs(self, rev):
43 def parentrevs(self, rev):
44 return self._revlog.parentrevs(rev)
44 return self._revlog.parentrevs(rev)
45
45
46 def rev(self, node):
46 def rev(self, node):
47 return self._revlog.rev(node)
47 return self._revlog.rev(node)
48
48
49 def node(self, rev):
49 def node(self, rev):
50 return self._revlog.node(rev)
50 return self._revlog.node(rev)
51
51
52 def lookup(self, node):
52 def lookup(self, node):
53 return self._revlog.lookup(node)
53 return self._revlog.lookup(node)
54
54
55 def linkrev(self, rev):
55 def linkrev(self, rev):
56 return self._revlog.linkrev(rev)
56 return self._revlog.linkrev(rev)
57
57
58 # Used by verify.
58 # Used by verify.
59 def flags(self, rev):
59 def flags(self, rev):
60 return self._revlog.flags(rev)
60 return self._revlog.flags(rev)
61
61
62 def commonancestorsheads(self, node1, node2):
62 def commonancestorsheads(self, node1, node2):
63 return self._revlog.commonancestorsheads(node1, node2)
63 return self._revlog.commonancestorsheads(node1, node2)
64
64
65 # Used by dagop.blockdescendants().
65 # Used by dagop.blockdescendants().
66 def descendants(self, revs):
66 def descendants(self, revs):
67 return self._revlog.descendants(revs)
67 return self._revlog.descendants(revs)
68
68
69 def heads(self, start=None, stop=None):
69 def heads(self, start=None, stop=None):
70 return self._revlog.heads(start, stop)
70 return self._revlog.heads(start, stop)
71
71
72 # Used by hgweb, children extension.
72 # Used by hgweb, children extension.
73 def children(self, node):
73 def children(self, node):
74 return self._revlog.children(node)
74 return self._revlog.children(node)
75
75
76 def deltaparent(self, rev):
76 def deltaparent(self, rev):
77 return self._revlog.deltaparent(rev)
77 return self._revlog.deltaparent(rev)
78
78
79 def iscensored(self, rev):
79 def iscensored(self, rev):
80 return self._revlog.iscensored(rev)
80 return self._revlog.iscensored(rev)
81
81
82 # Used by repo upgrade, verify.
82 # Used by repo upgrade, verify.
83 def rawsize(self, rev):
83 def rawsize(self, rev):
84 return self._revlog.rawsize(rev)
84 return self._revlog.rawsize(rev)
85
85
86 # Might be unused.
86 # Might be unused.
87 def checkhash(self, text, node, p1=None, p2=None, rev=None):
87 def checkhash(self, text, node, p1=None, p2=None, rev=None):
88 return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev)
88 return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev)
89
89
90 def revision(self, node, _df=None, raw=False):
90 def revision(self, node, _df=None, raw=False):
91 return self._revlog.revision(node, _df=_df, raw=raw)
91 return self._revlog.revision(node, _df=_df, raw=raw)
92
92
93 def revdiff(self, rev1, rev2):
93 def revdiff(self, rev1, rev2):
94 return self._revlog.revdiff(rev1, rev2)
94 return self._revlog.revdiff(rev1, rev2)
95
95
96 def emitrevisiondeltas(self, requests):
97 return self._revlog.emitrevisiondeltas(requests)
98
99 def emitrevisions(self, nodes, nodesorder=None,
96 def emitrevisions(self, nodes, nodesorder=None,
100 revisiondata=False, assumehaveparentrevisions=False,
97 revisiondata=False, assumehaveparentrevisions=False,
101 deltaprevious=False):
98 deltaprevious=False):
102 return self._revlog.emitrevisions(
99 return self._revlog.emitrevisions(
103 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
100 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
104 assumehaveparentrevisions=assumehaveparentrevisions,
101 assumehaveparentrevisions=assumehaveparentrevisions,
105 deltaprevious=deltaprevious)
102 deltaprevious=deltaprevious)
106
103
107 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
104 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
108 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
105 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
109 cachedelta=None):
106 cachedelta=None):
110 return self._revlog.addrevision(revisiondata, transaction, linkrev,
107 return self._revlog.addrevision(revisiondata, transaction, linkrev,
111 p1, p2, node=node, flags=flags,
108 p1, p2, node=node, flags=flags,
112 cachedelta=cachedelta)
109 cachedelta=cachedelta)
113
110
114 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
111 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
115 return self._revlog.addgroup(deltas, linkmapper, transaction,
112 return self._revlog.addgroup(deltas, linkmapper, transaction,
116 addrevisioncb=addrevisioncb)
113 addrevisioncb=addrevisioncb)
117
114
118 def getstrippoint(self, minlink):
115 def getstrippoint(self, minlink):
119 return self._revlog.getstrippoint(minlink)
116 return self._revlog.getstrippoint(minlink)
120
117
121 def strip(self, minlink, transaction):
118 def strip(self, minlink, transaction):
122 return self._revlog.strip(minlink, transaction)
119 return self._revlog.strip(minlink, transaction)
123
120
124 def censorrevision(self, tr, node, tombstone=b''):
121 def censorrevision(self, tr, node, tombstone=b''):
125 return self._revlog.censorrevision(node, tombstone=tombstone)
122 return self._revlog.censorrevision(node, tombstone=tombstone)
126
123
127 def files(self):
124 def files(self):
128 return self._revlog.files()
125 return self._revlog.files()
129
126
130 def read(self, node):
127 def read(self, node):
131 t = self.revision(node)
128 t = self.revision(node)
132 if not t.startswith('\1\n'):
129 if not t.startswith('\1\n'):
133 return t
130 return t
134 s = t.index('\1\n', 2)
131 s = t.index('\1\n', 2)
135 return t[s + 2:]
132 return t[s + 2:]
136
133
137 def add(self, text, meta, transaction, link, p1=None, p2=None):
134 def add(self, text, meta, transaction, link, p1=None, p2=None):
138 if meta or text.startswith('\1\n'):
135 if meta or text.startswith('\1\n'):
139 text = revlog.packmeta(meta, text)
136 text = revlog.packmeta(meta, text)
140 return self.addrevision(text, transaction, link, p1, p2)
137 return self.addrevision(text, transaction, link, p1, p2)
141
138
142 def renamed(self, node):
139 def renamed(self, node):
143 if self.parents(node)[0] != revlog.nullid:
140 if self.parents(node)[0] != revlog.nullid:
144 return False
141 return False
145 t = self.revision(node)
142 t = self.revision(node)
146 m = revlog.parsemeta(t)[0]
143 m = revlog.parsemeta(t)[0]
147 # copy and copyrev occur in pairs. In rare cases due to bugs,
144 # copy and copyrev occur in pairs. In rare cases due to bugs,
148 # one can occur without the other.
145 # one can occur without the other.
149 if m and "copy" in m and "copyrev" in m:
146 if m and "copy" in m and "copyrev" in m:
150 return (m["copy"], revlog.bin(m["copyrev"]))
147 return (m["copy"], revlog.bin(m["copyrev"]))
151 return False
148 return False
152
149
153 def size(self, rev):
150 def size(self, rev):
154 """return the size of a given revision"""
151 """return the size of a given revision"""
155
152
156 # for revisions with renames, we have to go the slow way
153 # for revisions with renames, we have to go the slow way
157 node = self.node(rev)
154 node = self.node(rev)
158 if self.renamed(node):
155 if self.renamed(node):
159 return len(self.read(node))
156 return len(self.read(node))
160 if self.iscensored(rev):
157 if self.iscensored(rev):
161 return 0
158 return 0
162
159
163 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
160 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
164 return self._revlog.size(rev)
161 return self._revlog.size(rev)
165
162
166 def cmp(self, node, text):
163 def cmp(self, node, text):
167 """compare text with a given file revision
164 """compare text with a given file revision
168
165
169 returns True if text is different than what is stored.
166 returns True if text is different than what is stored.
170 """
167 """
171
168
172 t = text
169 t = text
173 if text.startswith('\1\n'):
170 if text.startswith('\1\n'):
174 t = '\1\n\1\n' + text
171 t = '\1\n\1\n' + text
175
172
176 samehashes = not self._revlog.cmp(node, t)
173 samehashes = not self._revlog.cmp(node, t)
177 if samehashes:
174 if samehashes:
178 return False
175 return False
179
176
180 # censored files compare against the empty file
177 # censored files compare against the empty file
181 if self.iscensored(self.rev(node)):
178 if self.iscensored(self.rev(node)):
182 return text != ''
179 return text != ''
183
180
184 # renaming a file produces a different hash, even if the data
181 # renaming a file produces a different hash, even if the data
185 # remains unchanged. Check if it's the case (slow):
182 # remains unchanged. Check if it's the case (slow):
186 if self.renamed(node):
183 if self.renamed(node):
187 t2 = self.read(node)
184 t2 = self.read(node)
188 return t2 != text
185 return t2 != text
189
186
190 return True
187 return True
191
188
192 def verifyintegrity(self, state):
189 def verifyintegrity(self, state):
193 return self._revlog.verifyintegrity(state)
190 return self._revlog.verifyintegrity(state)
194
191
195 # TODO these aren't part of the interface and aren't internal methods.
192 # TODO these aren't part of the interface and aren't internal methods.
196 # Callers should be fixed to not use them.
193 # Callers should be fixed to not use them.
197
194
198 # Used by bundlefilelog, unionfilelog.
195 # Used by bundlefilelog, unionfilelog.
199 @property
196 @property
200 def indexfile(self):
197 def indexfile(self):
201 return self._revlog.indexfile
198 return self._revlog.indexfile
202
199
203 @indexfile.setter
200 @indexfile.setter
204 def indexfile(self, value):
201 def indexfile(self, value):
205 self._revlog.indexfile = value
202 self._revlog.indexfile = value
206
203
207 # Used by repo upgrade.
204 # Used by repo upgrade.
208 @property
205 @property
209 def opener(self):
206 def opener(self):
210 return self._revlog.opener
207 return self._revlog.opener
211
208
212 # Used by repo upgrade.
209 # Used by repo upgrade.
213 def clone(self, tr, destrevlog, **kwargs):
210 def clone(self, tr, destrevlog, **kwargs):
214 if not isinstance(destrevlog, filelog):
211 if not isinstance(destrevlog, filelog):
215 raise error.ProgrammingError('expected filelog to clone()')
212 raise error.ProgrammingError('expected filelog to clone()')
216
213
217 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
214 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
218
215
219 class narrowfilelog(filelog):
216 class narrowfilelog(filelog):
220 """Filelog variation to be used with narrow stores."""
217 """Filelog variation to be used with narrow stores."""
221
218
222 def __init__(self, opener, path, narrowmatch):
219 def __init__(self, opener, path, narrowmatch):
223 super(narrowfilelog, self).__init__(opener, path)
220 super(narrowfilelog, self).__init__(opener, path)
224 self._narrowmatch = narrowmatch
221 self._narrowmatch = narrowmatch
225
222
226 def renamed(self, node):
223 def renamed(self, node):
227 res = super(narrowfilelog, self).renamed(node)
224 res = super(narrowfilelog, self).renamed(node)
228
225
229 # Renames that come from outside the narrowspec are problematic
226 # Renames that come from outside the narrowspec are problematic
230 # because we may lack the base text for the rename. This can result
227 # because we may lack the base text for the rename. This can result
231 # in code attempting to walk the ancestry or compute a diff
228 # in code attempting to walk the ancestry or compute a diff
232 # encountering a missing revision. We address this by silently
229 # encountering a missing revision. We address this by silently
233 # removing rename metadata if the source file is outside the
230 # removing rename metadata if the source file is outside the
234 # narrow spec.
231 # narrow spec.
235 #
232 #
236 # A better solution would be to see if the base revision is available,
233 # A better solution would be to see if the base revision is available,
237 # rather than assuming it isn't.
234 # rather than assuming it isn't.
238 #
235 #
239 # An even better solution would be to teach all consumers of rename
236 # An even better solution would be to teach all consumers of rename
240 # metadata that the base revision may not be available.
237 # metadata that the base revision may not be available.
241 #
238 #
242 # TODO consider better ways of doing this.
239 # TODO consider better ways of doing this.
243 if res and not self._narrowmatch(res[0]):
240 if res and not self._narrowmatch(res[0]):
244 return None
241 return None
245
242
246 return res
243 return res
247
244
248 def size(self, rev):
245 def size(self, rev):
249 # Because we have a custom renamed() that may lie, we need to call
246 # Because we have a custom renamed() that may lie, we need to call
250 # the base renamed() to report accurate results.
247 # the base renamed() to report accurate results.
251 node = self.node(rev)
248 node = self.node(rev)
252 if super(narrowfilelog, self).renamed(node):
249 if super(narrowfilelog, self).renamed(node):
253 return len(self.read(node))
250 return len(self.read(node))
254 else:
251 else:
255 return super(narrowfilelog, self).size(rev)
252 return super(narrowfilelog, self).size(rev)
256
253
257 def cmp(self, node, text):
254 def cmp(self, node, text):
258 different = super(narrowfilelog, self).cmp(node, text)
255 different = super(narrowfilelog, self).cmp(node, text)
259
256
260 # Because renamed() may lie, we may get false positives for
257 # Because renamed() may lie, we may get false positives for
261 # different content. Check for this by comparing against the original
258 # different content. Check for this by comparing against the original
262 # renamed() implementation.
259 # renamed() implementation.
263 if different:
260 if different:
264 if super(narrowfilelog, self).renamed(node):
261 if super(narrowfilelog, self).renamed(node):
265 t2 = self.read(node)
262 t2 = self.read(node)
266 return t2 != text
263 return t2 != text
267
264
268 return different
265 return different
@@ -1,2028 +1,2025 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 heapq
10 import heapq
11 import itertools
11 import itertools
12 import struct
12 import struct
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 bin,
17 bin,
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 )
21 )
22 from . import (
22 from . import (
23 error,
23 error,
24 mdiff,
24 mdiff,
25 policy,
25 policy,
26 pycompat,
26 pycompat,
27 repository,
27 repository,
28 revlog,
28 revlog,
29 util,
29 util,
30 )
30 )
31 from .utils import (
31 from .utils import (
32 interfaceutil,
32 interfaceutil,
33 )
33 )
34
34
35 parsers = policy.importmod(r'parsers')
35 parsers = policy.importmod(r'parsers')
36 propertycache = util.propertycache
36 propertycache = util.propertycache
37
37
38 def _parse(data):
38 def _parse(data):
39 # This method does a little bit of excessive-looking
39 # This method does a little bit of excessive-looking
40 # precondition checking. This is so that the behavior of this
40 # precondition checking. This is so that the behavior of this
41 # class exactly matches its C counterpart to try and help
41 # class exactly matches its C counterpart to try and help
42 # prevent surprise breakage for anyone that develops against
42 # prevent surprise breakage for anyone that develops against
43 # the pure version.
43 # the pure version.
44 if data and data[-1:] != '\n':
44 if data and data[-1:] != '\n':
45 raise ValueError('Manifest did not end in a newline.')
45 raise ValueError('Manifest did not end in a newline.')
46 prev = None
46 prev = None
47 for l in data.splitlines():
47 for l in data.splitlines():
48 if prev is not None and prev > l:
48 if prev is not None and prev > l:
49 raise ValueError('Manifest lines not in sorted order.')
49 raise ValueError('Manifest lines not in sorted order.')
50 prev = l
50 prev = l
51 f, n = l.split('\0')
51 f, n = l.split('\0')
52 if len(n) > 40:
52 if len(n) > 40:
53 yield f, bin(n[:40]), n[40:]
53 yield f, bin(n[:40]), n[40:]
54 else:
54 else:
55 yield f, bin(n), ''
55 yield f, bin(n), ''
56
56
57 def _text(it):
57 def _text(it):
58 files = []
58 files = []
59 lines = []
59 lines = []
60 for f, n, fl in it:
60 for f, n, fl in it:
61 files.append(f)
61 files.append(f)
62 # if this is changed to support newlines in filenames,
62 # if this is changed to support newlines in filenames,
63 # be sure to check the templates/ dir again (especially *-raw.tmpl)
63 # be sure to check the templates/ dir again (especially *-raw.tmpl)
64 lines.append("%s\0%s%s\n" % (f, hex(n), fl))
64 lines.append("%s\0%s%s\n" % (f, hex(n), fl))
65
65
66 _checkforbidden(files)
66 _checkforbidden(files)
67 return ''.join(lines)
67 return ''.join(lines)
68
68
69 class lazymanifestiter(object):
69 class lazymanifestiter(object):
70 def __init__(self, lm):
70 def __init__(self, lm):
71 self.pos = 0
71 self.pos = 0
72 self.lm = lm
72 self.lm = lm
73
73
74 def __iter__(self):
74 def __iter__(self):
75 return self
75 return self
76
76
77 def next(self):
77 def next(self):
78 try:
78 try:
79 data, pos = self.lm._get(self.pos)
79 data, pos = self.lm._get(self.pos)
80 except IndexError:
80 except IndexError:
81 raise StopIteration
81 raise StopIteration
82 if pos == -1:
82 if pos == -1:
83 self.pos += 1
83 self.pos += 1
84 return data[0]
84 return data[0]
85 self.pos += 1
85 self.pos += 1
86 zeropos = data.find('\x00', pos)
86 zeropos = data.find('\x00', pos)
87 return data[pos:zeropos]
87 return data[pos:zeropos]
88
88
89 __next__ = next
89 __next__ = next
90
90
91 class lazymanifestiterentries(object):
91 class lazymanifestiterentries(object):
92 def __init__(self, lm):
92 def __init__(self, lm):
93 self.lm = lm
93 self.lm = lm
94 self.pos = 0
94 self.pos = 0
95
95
96 def __iter__(self):
96 def __iter__(self):
97 return self
97 return self
98
98
99 def next(self):
99 def next(self):
100 try:
100 try:
101 data, pos = self.lm._get(self.pos)
101 data, pos = self.lm._get(self.pos)
102 except IndexError:
102 except IndexError:
103 raise StopIteration
103 raise StopIteration
104 if pos == -1:
104 if pos == -1:
105 self.pos += 1
105 self.pos += 1
106 return data
106 return data
107 zeropos = data.find('\x00', pos)
107 zeropos = data.find('\x00', pos)
108 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
108 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
109 zeropos + 1, 40)
109 zeropos + 1, 40)
110 flags = self.lm._getflags(data, self.pos, zeropos)
110 flags = self.lm._getflags(data, self.pos, zeropos)
111 self.pos += 1
111 self.pos += 1
112 return (data[pos:zeropos], hashval, flags)
112 return (data[pos:zeropos], hashval, flags)
113
113
114 __next__ = next
114 __next__ = next
115
115
116 def unhexlify(data, extra, pos, length):
116 def unhexlify(data, extra, pos, length):
117 s = bin(data[pos:pos + length])
117 s = bin(data[pos:pos + length])
118 if extra:
118 if extra:
119 s += chr(extra & 0xff)
119 s += chr(extra & 0xff)
120 return s
120 return s
121
121
122 def _cmp(a, b):
122 def _cmp(a, b):
123 return (a > b) - (a < b)
123 return (a > b) - (a < b)
124
124
125 class _lazymanifest(object):
125 class _lazymanifest(object):
126 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
126 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
127 if positions is None:
127 if positions is None:
128 self.positions = self.findlines(data)
128 self.positions = self.findlines(data)
129 self.extrainfo = [0] * len(self.positions)
129 self.extrainfo = [0] * len(self.positions)
130 self.data = data
130 self.data = data
131 self.extradata = []
131 self.extradata = []
132 else:
132 else:
133 self.positions = positions[:]
133 self.positions = positions[:]
134 self.extrainfo = extrainfo[:]
134 self.extrainfo = extrainfo[:]
135 self.extradata = extradata[:]
135 self.extradata = extradata[:]
136 self.data = data
136 self.data = data
137
137
138 def findlines(self, data):
138 def findlines(self, data):
139 if not data:
139 if not data:
140 return []
140 return []
141 pos = data.find("\n")
141 pos = data.find("\n")
142 if pos == -1 or data[-1:] != '\n':
142 if pos == -1 or data[-1:] != '\n':
143 raise ValueError("Manifest did not end in a newline.")
143 raise ValueError("Manifest did not end in a newline.")
144 positions = [0]
144 positions = [0]
145 prev = data[:data.find('\x00')]
145 prev = data[:data.find('\x00')]
146 while pos < len(data) - 1 and pos != -1:
146 while pos < len(data) - 1 and pos != -1:
147 positions.append(pos + 1)
147 positions.append(pos + 1)
148 nexts = data[pos + 1:data.find('\x00', pos + 1)]
148 nexts = data[pos + 1:data.find('\x00', pos + 1)]
149 if nexts < prev:
149 if nexts < prev:
150 raise ValueError("Manifest lines not in sorted order.")
150 raise ValueError("Manifest lines not in sorted order.")
151 prev = nexts
151 prev = nexts
152 pos = data.find("\n", pos + 1)
152 pos = data.find("\n", pos + 1)
153 return positions
153 return positions
154
154
155 def _get(self, index):
155 def _get(self, index):
156 # get the position encoded in pos:
156 # get the position encoded in pos:
157 # positive number is an index in 'data'
157 # positive number is an index in 'data'
158 # negative number is in extrapieces
158 # negative number is in extrapieces
159 pos = self.positions[index]
159 pos = self.positions[index]
160 if pos >= 0:
160 if pos >= 0:
161 return self.data, pos
161 return self.data, pos
162 return self.extradata[-pos - 1], -1
162 return self.extradata[-pos - 1], -1
163
163
164 def _getkey(self, pos):
164 def _getkey(self, pos):
165 if pos >= 0:
165 if pos >= 0:
166 return self.data[pos:self.data.find('\x00', pos + 1)]
166 return self.data[pos:self.data.find('\x00', pos + 1)]
167 return self.extradata[-pos - 1][0]
167 return self.extradata[-pos - 1][0]
168
168
169 def bsearch(self, key):
169 def bsearch(self, key):
170 first = 0
170 first = 0
171 last = len(self.positions) - 1
171 last = len(self.positions) - 1
172
172
173 while first <= last:
173 while first <= last:
174 midpoint = (first + last)//2
174 midpoint = (first + last)//2
175 nextpos = self.positions[midpoint]
175 nextpos = self.positions[midpoint]
176 candidate = self._getkey(nextpos)
176 candidate = self._getkey(nextpos)
177 r = _cmp(key, candidate)
177 r = _cmp(key, candidate)
178 if r == 0:
178 if r == 0:
179 return midpoint
179 return midpoint
180 else:
180 else:
181 if r < 0:
181 if r < 0:
182 last = midpoint - 1
182 last = midpoint - 1
183 else:
183 else:
184 first = midpoint + 1
184 first = midpoint + 1
185 return -1
185 return -1
186
186
187 def bsearch2(self, key):
187 def bsearch2(self, key):
188 # same as the above, but will always return the position
188 # same as the above, but will always return the position
189 # done for performance reasons
189 # done for performance reasons
190 first = 0
190 first = 0
191 last = len(self.positions) - 1
191 last = len(self.positions) - 1
192
192
193 while first <= last:
193 while first <= last:
194 midpoint = (first + last)//2
194 midpoint = (first + last)//2
195 nextpos = self.positions[midpoint]
195 nextpos = self.positions[midpoint]
196 candidate = self._getkey(nextpos)
196 candidate = self._getkey(nextpos)
197 r = _cmp(key, candidate)
197 r = _cmp(key, candidate)
198 if r == 0:
198 if r == 0:
199 return (midpoint, True)
199 return (midpoint, True)
200 else:
200 else:
201 if r < 0:
201 if r < 0:
202 last = midpoint - 1
202 last = midpoint - 1
203 else:
203 else:
204 first = midpoint + 1
204 first = midpoint + 1
205 return (first, False)
205 return (first, False)
206
206
207 def __contains__(self, key):
207 def __contains__(self, key):
208 return self.bsearch(key) != -1
208 return self.bsearch(key) != -1
209
209
210 def _getflags(self, data, needle, pos):
210 def _getflags(self, data, needle, pos):
211 start = pos + 41
211 start = pos + 41
212 end = data.find("\n", start)
212 end = data.find("\n", start)
213 if end == -1:
213 if end == -1:
214 end = len(data) - 1
214 end = len(data) - 1
215 if start == end:
215 if start == end:
216 return ''
216 return ''
217 return self.data[start:end]
217 return self.data[start:end]
218
218
219 def __getitem__(self, key):
219 def __getitem__(self, key):
220 if not isinstance(key, bytes):
220 if not isinstance(key, bytes):
221 raise TypeError("getitem: manifest keys must be a bytes.")
221 raise TypeError("getitem: manifest keys must be a bytes.")
222 needle = self.bsearch(key)
222 needle = self.bsearch(key)
223 if needle == -1:
223 if needle == -1:
224 raise KeyError
224 raise KeyError
225 data, pos = self._get(needle)
225 data, pos = self._get(needle)
226 if pos == -1:
226 if pos == -1:
227 return (data[1], data[2])
227 return (data[1], data[2])
228 zeropos = data.find('\x00', pos)
228 zeropos = data.find('\x00', pos)
229 assert 0 <= needle <= len(self.positions)
229 assert 0 <= needle <= len(self.positions)
230 assert len(self.extrainfo) == len(self.positions)
230 assert len(self.extrainfo) == len(self.positions)
231 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
231 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
232 flags = self._getflags(data, needle, zeropos)
232 flags = self._getflags(data, needle, zeropos)
233 return (hashval, flags)
233 return (hashval, flags)
234
234
235 def __delitem__(self, key):
235 def __delitem__(self, key):
236 needle, found = self.bsearch2(key)
236 needle, found = self.bsearch2(key)
237 if not found:
237 if not found:
238 raise KeyError
238 raise KeyError
239 cur = self.positions[needle]
239 cur = self.positions[needle]
240 self.positions = self.positions[:needle] + self.positions[needle + 1:]
240 self.positions = self.positions[:needle] + self.positions[needle + 1:]
241 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
241 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
242 if cur >= 0:
242 if cur >= 0:
243 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
243 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
244
244
245 def __setitem__(self, key, value):
245 def __setitem__(self, key, value):
246 if not isinstance(key, bytes):
246 if not isinstance(key, bytes):
247 raise TypeError("setitem: manifest keys must be a byte string.")
247 raise TypeError("setitem: manifest keys must be a byte string.")
248 if not isinstance(value, tuple) or len(value) != 2:
248 if not isinstance(value, tuple) or len(value) != 2:
249 raise TypeError("Manifest values must be a tuple of (node, flags).")
249 raise TypeError("Manifest values must be a tuple of (node, flags).")
250 hashval = value[0]
250 hashval = value[0]
251 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
251 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
252 raise TypeError("node must be a 20-byte byte string")
252 raise TypeError("node must be a 20-byte byte string")
253 flags = value[1]
253 flags = value[1]
254 if len(hashval) == 22:
254 if len(hashval) == 22:
255 hashval = hashval[:-1]
255 hashval = hashval[:-1]
256 if not isinstance(flags, bytes) or len(flags) > 1:
256 if not isinstance(flags, bytes) or len(flags) > 1:
257 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
257 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
258 needle, found = self.bsearch2(key)
258 needle, found = self.bsearch2(key)
259 if found:
259 if found:
260 # put the item
260 # put the item
261 pos = self.positions[needle]
261 pos = self.positions[needle]
262 if pos < 0:
262 if pos < 0:
263 self.extradata[-pos - 1] = (key, hashval, value[1])
263 self.extradata[-pos - 1] = (key, hashval, value[1])
264 else:
264 else:
265 # just don't bother
265 # just don't bother
266 self.extradata.append((key, hashval, value[1]))
266 self.extradata.append((key, hashval, value[1]))
267 self.positions[needle] = -len(self.extradata)
267 self.positions[needle] = -len(self.extradata)
268 else:
268 else:
269 # not found, put it in with extra positions
269 # not found, put it in with extra positions
270 self.extradata.append((key, hashval, value[1]))
270 self.extradata.append((key, hashval, value[1]))
271 self.positions = (self.positions[:needle] + [-len(self.extradata)]
271 self.positions = (self.positions[:needle] + [-len(self.extradata)]
272 + self.positions[needle:])
272 + self.positions[needle:])
273 self.extrainfo = (self.extrainfo[:needle] + [0] +
273 self.extrainfo = (self.extrainfo[:needle] + [0] +
274 self.extrainfo[needle:])
274 self.extrainfo[needle:])
275
275
276 def copy(self):
276 def copy(self):
277 # XXX call _compact like in C?
277 # XXX call _compact like in C?
278 return _lazymanifest(self.data, self.positions, self.extrainfo,
278 return _lazymanifest(self.data, self.positions, self.extrainfo,
279 self.extradata)
279 self.extradata)
280
280
281 def _compact(self):
281 def _compact(self):
282 # hopefully not called TOO often
282 # hopefully not called TOO often
283 if len(self.extradata) == 0:
283 if len(self.extradata) == 0:
284 return
284 return
285 l = []
285 l = []
286 last_cut = 0
286 last_cut = 0
287 i = 0
287 i = 0
288 offset = 0
288 offset = 0
289 self.extrainfo = [0] * len(self.positions)
289 self.extrainfo = [0] * len(self.positions)
290 while i < len(self.positions):
290 while i < len(self.positions):
291 if self.positions[i] >= 0:
291 if self.positions[i] >= 0:
292 cur = self.positions[i]
292 cur = self.positions[i]
293 last_cut = cur
293 last_cut = cur
294 while True:
294 while True:
295 self.positions[i] = offset
295 self.positions[i] = offset
296 i += 1
296 i += 1
297 if i == len(self.positions) or self.positions[i] < 0:
297 if i == len(self.positions) or self.positions[i] < 0:
298 break
298 break
299 offset += self.positions[i] - cur
299 offset += self.positions[i] - cur
300 cur = self.positions[i]
300 cur = self.positions[i]
301 end_cut = self.data.find('\n', cur)
301 end_cut = self.data.find('\n', cur)
302 if end_cut != -1:
302 if end_cut != -1:
303 end_cut += 1
303 end_cut += 1
304 offset += end_cut - cur
304 offset += end_cut - cur
305 l.append(self.data[last_cut:end_cut])
305 l.append(self.data[last_cut:end_cut])
306 else:
306 else:
307 while i < len(self.positions) and self.positions[i] < 0:
307 while i < len(self.positions) and self.positions[i] < 0:
308 cur = self.positions[i]
308 cur = self.positions[i]
309 t = self.extradata[-cur - 1]
309 t = self.extradata[-cur - 1]
310 l.append(self._pack(t))
310 l.append(self._pack(t))
311 self.positions[i] = offset
311 self.positions[i] = offset
312 if len(t[1]) > 20:
312 if len(t[1]) > 20:
313 self.extrainfo[i] = ord(t[1][21])
313 self.extrainfo[i] = ord(t[1][21])
314 offset += len(l[-1])
314 offset += len(l[-1])
315 i += 1
315 i += 1
316 self.data = ''.join(l)
316 self.data = ''.join(l)
317 self.extradata = []
317 self.extradata = []
318
318
319 def _pack(self, d):
319 def _pack(self, d):
320 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
320 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
321
321
322 def text(self):
322 def text(self):
323 self._compact()
323 self._compact()
324 return self.data
324 return self.data
325
325
326 def diff(self, m2, clean=False):
326 def diff(self, m2, clean=False):
327 '''Finds changes between the current manifest and m2.'''
327 '''Finds changes between the current manifest and m2.'''
328 # XXX think whether efficiency matters here
328 # XXX think whether efficiency matters here
329 diff = {}
329 diff = {}
330
330
331 for fn, e1, flags in self.iterentries():
331 for fn, e1, flags in self.iterentries():
332 if fn not in m2:
332 if fn not in m2:
333 diff[fn] = (e1, flags), (None, '')
333 diff[fn] = (e1, flags), (None, '')
334 else:
334 else:
335 e2 = m2[fn]
335 e2 = m2[fn]
336 if (e1, flags) != e2:
336 if (e1, flags) != e2:
337 diff[fn] = (e1, flags), e2
337 diff[fn] = (e1, flags), e2
338 elif clean:
338 elif clean:
339 diff[fn] = None
339 diff[fn] = None
340
340
341 for fn, e2, flags in m2.iterentries():
341 for fn, e2, flags in m2.iterentries():
342 if fn not in self:
342 if fn not in self:
343 diff[fn] = (None, ''), (e2, flags)
343 diff[fn] = (None, ''), (e2, flags)
344
344
345 return diff
345 return diff
346
346
347 def iterentries(self):
347 def iterentries(self):
348 return lazymanifestiterentries(self)
348 return lazymanifestiterentries(self)
349
349
350 def iterkeys(self):
350 def iterkeys(self):
351 return lazymanifestiter(self)
351 return lazymanifestiter(self)
352
352
353 def __iter__(self):
353 def __iter__(self):
354 return lazymanifestiter(self)
354 return lazymanifestiter(self)
355
355
356 def __len__(self):
356 def __len__(self):
357 return len(self.positions)
357 return len(self.positions)
358
358
359 def filtercopy(self, filterfn):
359 def filtercopy(self, filterfn):
360 # XXX should be optimized
360 # XXX should be optimized
361 c = _lazymanifest('')
361 c = _lazymanifest('')
362 for f, n, fl in self.iterentries():
362 for f, n, fl in self.iterentries():
363 if filterfn(f):
363 if filterfn(f):
364 c[f] = n, fl
364 c[f] = n, fl
365 return c
365 return c
366
366
367 try:
367 try:
368 _lazymanifest = parsers.lazymanifest
368 _lazymanifest = parsers.lazymanifest
369 except AttributeError:
369 except AttributeError:
370 pass
370 pass
371
371
372 @interfaceutil.implementer(repository.imanifestdict)
372 @interfaceutil.implementer(repository.imanifestdict)
373 class manifestdict(object):
373 class manifestdict(object):
374 def __init__(self, data=''):
374 def __init__(self, data=''):
375 self._lm = _lazymanifest(data)
375 self._lm = _lazymanifest(data)
376
376
377 def __getitem__(self, key):
377 def __getitem__(self, key):
378 return self._lm[key][0]
378 return self._lm[key][0]
379
379
380 def find(self, key):
380 def find(self, key):
381 return self._lm[key]
381 return self._lm[key]
382
382
383 def __len__(self):
383 def __len__(self):
384 return len(self._lm)
384 return len(self._lm)
385
385
386 def __nonzero__(self):
386 def __nonzero__(self):
387 # nonzero is covered by the __len__ function, but implementing it here
387 # nonzero is covered by the __len__ function, but implementing it here
388 # makes it easier for extensions to override.
388 # makes it easier for extensions to override.
389 return len(self._lm) != 0
389 return len(self._lm) != 0
390
390
391 __bool__ = __nonzero__
391 __bool__ = __nonzero__
392
392
393 def __setitem__(self, key, node):
393 def __setitem__(self, key, node):
394 self._lm[key] = node, self.flags(key, '')
394 self._lm[key] = node, self.flags(key, '')
395
395
396 def __contains__(self, key):
396 def __contains__(self, key):
397 if key is None:
397 if key is None:
398 return False
398 return False
399 return key in self._lm
399 return key in self._lm
400
400
401 def __delitem__(self, key):
401 def __delitem__(self, key):
402 del self._lm[key]
402 del self._lm[key]
403
403
404 def __iter__(self):
404 def __iter__(self):
405 return self._lm.__iter__()
405 return self._lm.__iter__()
406
406
407 def iterkeys(self):
407 def iterkeys(self):
408 return self._lm.iterkeys()
408 return self._lm.iterkeys()
409
409
410 def keys(self):
410 def keys(self):
411 return list(self.iterkeys())
411 return list(self.iterkeys())
412
412
413 def filesnotin(self, m2, match=None):
413 def filesnotin(self, m2, match=None):
414 '''Set of files in this manifest that are not in the other'''
414 '''Set of files in this manifest that are not in the other'''
415 if match:
415 if match:
416 m1 = self.matches(match)
416 m1 = self.matches(match)
417 m2 = m2.matches(match)
417 m2 = m2.matches(match)
418 return m1.filesnotin(m2)
418 return m1.filesnotin(m2)
419 diff = self.diff(m2)
419 diff = self.diff(m2)
420 files = set(filepath
420 files = set(filepath
421 for filepath, hashflags in diff.iteritems()
421 for filepath, hashflags in diff.iteritems()
422 if hashflags[1][0] is None)
422 if hashflags[1][0] is None)
423 return files
423 return files
424
424
425 @propertycache
425 @propertycache
426 def _dirs(self):
426 def _dirs(self):
427 return util.dirs(self)
427 return util.dirs(self)
428
428
429 def dirs(self):
429 def dirs(self):
430 return self._dirs
430 return self._dirs
431
431
432 def hasdir(self, dir):
432 def hasdir(self, dir):
433 return dir in self._dirs
433 return dir in self._dirs
434
434
435 def _filesfastpath(self, match):
435 def _filesfastpath(self, match):
436 '''Checks whether we can correctly and quickly iterate over matcher
436 '''Checks whether we can correctly and quickly iterate over matcher
437 files instead of over manifest files.'''
437 files instead of over manifest files.'''
438 files = match.files()
438 files = match.files()
439 return (len(files) < 100 and (match.isexact() or
439 return (len(files) < 100 and (match.isexact() or
440 (match.prefix() and all(fn in self for fn in files))))
440 (match.prefix() and all(fn in self for fn in files))))
441
441
442 def walk(self, match):
442 def walk(self, match):
443 '''Generates matching file names.
443 '''Generates matching file names.
444
444
445 Equivalent to manifest.matches(match).iterkeys(), but without creating
445 Equivalent to manifest.matches(match).iterkeys(), but without creating
446 an entirely new manifest.
446 an entirely new manifest.
447
447
448 It also reports nonexistent files by marking them bad with match.bad().
448 It also reports nonexistent files by marking them bad with match.bad().
449 '''
449 '''
450 if match.always():
450 if match.always():
451 for f in iter(self):
451 for f in iter(self):
452 yield f
452 yield f
453 return
453 return
454
454
455 fset = set(match.files())
455 fset = set(match.files())
456
456
457 # avoid the entire walk if we're only looking for specific files
457 # avoid the entire walk if we're only looking for specific files
458 if self._filesfastpath(match):
458 if self._filesfastpath(match):
459 for fn in sorted(fset):
459 for fn in sorted(fset):
460 yield fn
460 yield fn
461 return
461 return
462
462
463 for fn in self:
463 for fn in self:
464 if fn in fset:
464 if fn in fset:
465 # specified pattern is the exact name
465 # specified pattern is the exact name
466 fset.remove(fn)
466 fset.remove(fn)
467 if match(fn):
467 if match(fn):
468 yield fn
468 yield fn
469
469
470 # for dirstate.walk, files=['.'] means "walk the whole tree".
470 # for dirstate.walk, files=['.'] means "walk the whole tree".
471 # follow that here, too
471 # follow that here, too
472 fset.discard('.')
472 fset.discard('.')
473
473
474 for fn in sorted(fset):
474 for fn in sorted(fset):
475 if not self.hasdir(fn):
475 if not self.hasdir(fn):
476 match.bad(fn, None)
476 match.bad(fn, None)
477
477
478 def matches(self, match):
478 def matches(self, match):
479 '''generate a new manifest filtered by the match argument'''
479 '''generate a new manifest filtered by the match argument'''
480 if match.always():
480 if match.always():
481 return self.copy()
481 return self.copy()
482
482
483 if self._filesfastpath(match):
483 if self._filesfastpath(match):
484 m = manifestdict()
484 m = manifestdict()
485 lm = self._lm
485 lm = self._lm
486 for fn in match.files():
486 for fn in match.files():
487 if fn in lm:
487 if fn in lm:
488 m._lm[fn] = lm[fn]
488 m._lm[fn] = lm[fn]
489 return m
489 return m
490
490
491 m = manifestdict()
491 m = manifestdict()
492 m._lm = self._lm.filtercopy(match)
492 m._lm = self._lm.filtercopy(match)
493 return m
493 return m
494
494
495 def diff(self, m2, match=None, clean=False):
495 def diff(self, m2, match=None, clean=False):
496 '''Finds changes between the current manifest and m2.
496 '''Finds changes between the current manifest and m2.
497
497
498 Args:
498 Args:
499 m2: the manifest to which this manifest should be compared.
499 m2: the manifest to which this manifest should be compared.
500 clean: if true, include files unchanged between these manifests
500 clean: if true, include files unchanged between these manifests
501 with a None value in the returned dictionary.
501 with a None value in the returned dictionary.
502
502
503 The result is returned as a dict with filename as key and
503 The result is returned as a dict with filename as key and
504 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
504 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
505 nodeid in the current/other manifest and fl1/fl2 is the flag
505 nodeid in the current/other manifest and fl1/fl2 is the flag
506 in the current/other manifest. Where the file does not exist,
506 in the current/other manifest. Where the file does not exist,
507 the nodeid will be None and the flags will be the empty
507 the nodeid will be None and the flags will be the empty
508 string.
508 string.
509 '''
509 '''
510 if match:
510 if match:
511 m1 = self.matches(match)
511 m1 = self.matches(match)
512 m2 = m2.matches(match)
512 m2 = m2.matches(match)
513 return m1.diff(m2, clean=clean)
513 return m1.diff(m2, clean=clean)
514 return self._lm.diff(m2._lm, clean)
514 return self._lm.diff(m2._lm, clean)
515
515
516 def setflag(self, key, flag):
516 def setflag(self, key, flag):
517 self._lm[key] = self[key], flag
517 self._lm[key] = self[key], flag
518
518
519 def get(self, key, default=None):
519 def get(self, key, default=None):
520 try:
520 try:
521 return self._lm[key][0]
521 return self._lm[key][0]
522 except KeyError:
522 except KeyError:
523 return default
523 return default
524
524
525 def flags(self, key, default=''):
525 def flags(self, key, default=''):
526 try:
526 try:
527 return self._lm[key][1]
527 return self._lm[key][1]
528 except KeyError:
528 except KeyError:
529 return default
529 return default
530
530
531 def copy(self):
531 def copy(self):
532 c = manifestdict()
532 c = manifestdict()
533 c._lm = self._lm.copy()
533 c._lm = self._lm.copy()
534 return c
534 return c
535
535
536 def items(self):
536 def items(self):
537 return (x[:2] for x in self._lm.iterentries())
537 return (x[:2] for x in self._lm.iterentries())
538
538
539 def iteritems(self):
539 def iteritems(self):
540 return (x[:2] for x in self._lm.iterentries())
540 return (x[:2] for x in self._lm.iterentries())
541
541
542 def iterentries(self):
542 def iterentries(self):
543 return self._lm.iterentries()
543 return self._lm.iterentries()
544
544
545 def text(self):
545 def text(self):
546 # most likely uses native version
546 # most likely uses native version
547 return self._lm.text()
547 return self._lm.text()
548
548
549 def fastdelta(self, base, changes):
549 def fastdelta(self, base, changes):
550 """Given a base manifest text as a bytearray and a list of changes
550 """Given a base manifest text as a bytearray and a list of changes
551 relative to that text, compute a delta that can be used by revlog.
551 relative to that text, compute a delta that can be used by revlog.
552 """
552 """
553 delta = []
553 delta = []
554 dstart = None
554 dstart = None
555 dend = None
555 dend = None
556 dline = [""]
556 dline = [""]
557 start = 0
557 start = 0
558 # zero copy representation of base as a buffer
558 # zero copy representation of base as a buffer
559 addbuf = util.buffer(base)
559 addbuf = util.buffer(base)
560
560
561 changes = list(changes)
561 changes = list(changes)
562 if len(changes) < 1000:
562 if len(changes) < 1000:
563 # start with a readonly loop that finds the offset of
563 # start with a readonly loop that finds the offset of
564 # each line and creates the deltas
564 # each line and creates the deltas
565 for f, todelete in changes:
565 for f, todelete in changes:
566 # bs will either be the index of the item or the insert point
566 # bs will either be the index of the item or the insert point
567 start, end = _msearch(addbuf, f, start)
567 start, end = _msearch(addbuf, f, start)
568 if not todelete:
568 if not todelete:
569 h, fl = self._lm[f]
569 h, fl = self._lm[f]
570 l = "%s\0%s%s\n" % (f, hex(h), fl)
570 l = "%s\0%s%s\n" % (f, hex(h), fl)
571 else:
571 else:
572 if start == end:
572 if start == end:
573 # item we want to delete was not found, error out
573 # item we want to delete was not found, error out
574 raise AssertionError(
574 raise AssertionError(
575 _("failed to remove %s from manifest") % f)
575 _("failed to remove %s from manifest") % f)
576 l = ""
576 l = ""
577 if dstart is not None and dstart <= start and dend >= start:
577 if dstart is not None and dstart <= start and dend >= start:
578 if dend < end:
578 if dend < end:
579 dend = end
579 dend = end
580 if l:
580 if l:
581 dline.append(l)
581 dline.append(l)
582 else:
582 else:
583 if dstart is not None:
583 if dstart is not None:
584 delta.append([dstart, dend, "".join(dline)])
584 delta.append([dstart, dend, "".join(dline)])
585 dstart = start
585 dstart = start
586 dend = end
586 dend = end
587 dline = [l]
587 dline = [l]
588
588
589 if dstart is not None:
589 if dstart is not None:
590 delta.append([dstart, dend, "".join(dline)])
590 delta.append([dstart, dend, "".join(dline)])
591 # apply the delta to the base, and get a delta for addrevision
591 # apply the delta to the base, and get a delta for addrevision
592 deltatext, arraytext = _addlistdelta(base, delta)
592 deltatext, arraytext = _addlistdelta(base, delta)
593 else:
593 else:
594 # For large changes, it's much cheaper to just build the text and
594 # For large changes, it's much cheaper to just build the text and
595 # diff it.
595 # diff it.
596 arraytext = bytearray(self.text())
596 arraytext = bytearray(self.text())
597 deltatext = mdiff.textdiff(
597 deltatext = mdiff.textdiff(
598 util.buffer(base), util.buffer(arraytext))
598 util.buffer(base), util.buffer(arraytext))
599
599
600 return arraytext, deltatext
600 return arraytext, deltatext
601
601
602 def _msearch(m, s, lo=0, hi=None):
602 def _msearch(m, s, lo=0, hi=None):
603 '''return a tuple (start, end) that says where to find s within m.
603 '''return a tuple (start, end) that says where to find s within m.
604
604
605 If the string is found m[start:end] are the line containing
605 If the string is found m[start:end] are the line containing
606 that string. If start == end the string was not found and
606 that string. If start == end the string was not found and
607 they indicate the proper sorted insertion point.
607 they indicate the proper sorted insertion point.
608
608
609 m should be a buffer, a memoryview or a byte string.
609 m should be a buffer, a memoryview or a byte string.
610 s is a byte string'''
610 s is a byte string'''
611 def advance(i, c):
611 def advance(i, c):
612 while i < lenm and m[i:i + 1] != c:
612 while i < lenm and m[i:i + 1] != c:
613 i += 1
613 i += 1
614 return i
614 return i
615 if not s:
615 if not s:
616 return (lo, lo)
616 return (lo, lo)
617 lenm = len(m)
617 lenm = len(m)
618 if not hi:
618 if not hi:
619 hi = lenm
619 hi = lenm
620 while lo < hi:
620 while lo < hi:
621 mid = (lo + hi) // 2
621 mid = (lo + hi) // 2
622 start = mid
622 start = mid
623 while start > 0 and m[start - 1:start] != '\n':
623 while start > 0 and m[start - 1:start] != '\n':
624 start -= 1
624 start -= 1
625 end = advance(start, '\0')
625 end = advance(start, '\0')
626 if bytes(m[start:end]) < s:
626 if bytes(m[start:end]) < s:
627 # we know that after the null there are 40 bytes of sha1
627 # we know that after the null there are 40 bytes of sha1
628 # this translates to the bisect lo = mid + 1
628 # this translates to the bisect lo = mid + 1
629 lo = advance(end + 40, '\n') + 1
629 lo = advance(end + 40, '\n') + 1
630 else:
630 else:
631 # this translates to the bisect hi = mid
631 # this translates to the bisect hi = mid
632 hi = start
632 hi = start
633 end = advance(lo, '\0')
633 end = advance(lo, '\0')
634 found = m[lo:end]
634 found = m[lo:end]
635 if s == found:
635 if s == found:
636 # we know that after the null there are 40 bytes of sha1
636 # we know that after the null there are 40 bytes of sha1
637 end = advance(end + 40, '\n')
637 end = advance(end + 40, '\n')
638 return (lo, end + 1)
638 return (lo, end + 1)
639 else:
639 else:
640 return (lo, lo)
640 return (lo, lo)
641
641
642 def _checkforbidden(l):
642 def _checkforbidden(l):
643 """Check filenames for illegal characters."""
643 """Check filenames for illegal characters."""
644 for f in l:
644 for f in l:
645 if '\n' in f or '\r' in f:
645 if '\n' in f or '\r' in f:
646 raise error.StorageError(
646 raise error.StorageError(
647 _("'\\n' and '\\r' disallowed in filenames: %r")
647 _("'\\n' and '\\r' disallowed in filenames: %r")
648 % pycompat.bytestr(f))
648 % pycompat.bytestr(f))
649
649
650
650
651 # apply the changes collected during the bisect loop to our addlist
651 # apply the changes collected during the bisect loop to our addlist
652 # return a delta suitable for addrevision
652 # return a delta suitable for addrevision
653 def _addlistdelta(addlist, x):
653 def _addlistdelta(addlist, x):
654 # for large addlist arrays, building a new array is cheaper
654 # for large addlist arrays, building a new array is cheaper
655 # than repeatedly modifying the existing one
655 # than repeatedly modifying the existing one
656 currentposition = 0
656 currentposition = 0
657 newaddlist = bytearray()
657 newaddlist = bytearray()
658
658
659 for start, end, content in x:
659 for start, end, content in x:
660 newaddlist += addlist[currentposition:start]
660 newaddlist += addlist[currentposition:start]
661 if content:
661 if content:
662 newaddlist += bytearray(content)
662 newaddlist += bytearray(content)
663
663
664 currentposition = end
664 currentposition = end
665
665
666 newaddlist += addlist[currentposition:]
666 newaddlist += addlist[currentposition:]
667
667
668 deltatext = "".join(struct.pack(">lll", start, end, len(content))
668 deltatext = "".join(struct.pack(">lll", start, end, len(content))
669 + content for start, end, content in x)
669 + content for start, end, content in x)
670 return deltatext, newaddlist
670 return deltatext, newaddlist
671
671
672 def _splittopdir(f):
672 def _splittopdir(f):
673 if '/' in f:
673 if '/' in f:
674 dir, subpath = f.split('/', 1)
674 dir, subpath = f.split('/', 1)
675 return dir + '/', subpath
675 return dir + '/', subpath
676 else:
676 else:
677 return '', f
677 return '', f
678
678
679 _noop = lambda s: None
679 _noop = lambda s: None
680
680
681 class treemanifest(object):
681 class treemanifest(object):
682 def __init__(self, dir='', text=''):
682 def __init__(self, dir='', text=''):
683 self._dir = dir
683 self._dir = dir
684 self._node = nullid
684 self._node = nullid
685 self._loadfunc = _noop
685 self._loadfunc = _noop
686 self._copyfunc = _noop
686 self._copyfunc = _noop
687 self._dirty = False
687 self._dirty = False
688 self._dirs = {}
688 self._dirs = {}
689 self._lazydirs = {}
689 self._lazydirs = {}
690 # Using _lazymanifest here is a little slower than plain old dicts
690 # Using _lazymanifest here is a little slower than plain old dicts
691 self._files = {}
691 self._files = {}
692 self._flags = {}
692 self._flags = {}
693 if text:
693 if text:
694 def readsubtree(subdir, subm):
694 def readsubtree(subdir, subm):
695 raise AssertionError('treemanifest constructor only accepts '
695 raise AssertionError('treemanifest constructor only accepts '
696 'flat manifests')
696 'flat manifests')
697 self.parse(text, readsubtree)
697 self.parse(text, readsubtree)
698 self._dirty = True # Mark flat manifest dirty after parsing
698 self._dirty = True # Mark flat manifest dirty after parsing
699
699
700 def _subpath(self, path):
700 def _subpath(self, path):
701 return self._dir + path
701 return self._dir + path
702
702
703 def _loadalllazy(self):
703 def _loadalllazy(self):
704 for k, (path, node, readsubtree) in self._lazydirs.iteritems():
704 for k, (path, node, readsubtree) in self._lazydirs.iteritems():
705 self._dirs[k] = readsubtree(path, node)
705 self._dirs[k] = readsubtree(path, node)
706 self._lazydirs = {}
706 self._lazydirs = {}
707
707
708 def _loadlazy(self, d):
708 def _loadlazy(self, d):
709 path, node, readsubtree = self._lazydirs[d]
709 path, node, readsubtree = self._lazydirs[d]
710 self._dirs[d] = readsubtree(path, node)
710 self._dirs[d] = readsubtree(path, node)
711 del self._lazydirs[d]
711 del self._lazydirs[d]
712
712
713 def _loadchildrensetlazy(self, visit):
713 def _loadchildrensetlazy(self, visit):
714 if not visit:
714 if not visit:
715 return None
715 return None
716 if visit == 'all' or visit == 'this':
716 if visit == 'all' or visit == 'this':
717 self._loadalllazy()
717 self._loadalllazy()
718 return None
718 return None
719
719
720 todel = []
720 todel = []
721 for k in visit:
721 for k in visit:
722 kslash = k + '/'
722 kslash = k + '/'
723 ld = self._lazydirs.get(kslash)
723 ld = self._lazydirs.get(kslash)
724 if ld:
724 if ld:
725 path, node, readsubtree = ld
725 path, node, readsubtree = ld
726 self._dirs[kslash] = readsubtree(path, node)
726 self._dirs[kslash] = readsubtree(path, node)
727 todel.append(kslash)
727 todel.append(kslash)
728 for kslash in todel:
728 for kslash in todel:
729 del self._lazydirs[kslash]
729 del self._lazydirs[kslash]
730 return visit
730 return visit
731
731
732 def __len__(self):
732 def __len__(self):
733 self._load()
733 self._load()
734 size = len(self._files)
734 size = len(self._files)
735 self._loadalllazy()
735 self._loadalllazy()
736 for m in self._dirs.values():
736 for m in self._dirs.values():
737 size += m.__len__()
737 size += m.__len__()
738 return size
738 return size
739
739
740 def __nonzero__(self):
740 def __nonzero__(self):
741 # Faster than "__len() != 0" since it avoids loading sub-manifests
741 # Faster than "__len() != 0" since it avoids loading sub-manifests
742 return not self._isempty()
742 return not self._isempty()
743
743
744 __bool__ = __nonzero__
744 __bool__ = __nonzero__
745
745
746 def _isempty(self):
746 def _isempty(self):
747 self._load() # for consistency; already loaded by all callers
747 self._load() # for consistency; already loaded by all callers
748 # See if we can skip loading everything.
748 # See if we can skip loading everything.
749 if self._files or (self._dirs and
749 if self._files or (self._dirs and
750 any(not m._isempty() for m in self._dirs.values())):
750 any(not m._isempty() for m in self._dirs.values())):
751 return False
751 return False
752 self._loadalllazy()
752 self._loadalllazy()
753 return (not self._dirs or
753 return (not self._dirs or
754 all(m._isempty() for m in self._dirs.values()))
754 all(m._isempty() for m in self._dirs.values()))
755
755
756 def __repr__(self):
756 def __repr__(self):
757 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
757 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
758 (self._dir, hex(self._node),
758 (self._dir, hex(self._node),
759 bool(self._loadfunc is _noop),
759 bool(self._loadfunc is _noop),
760 self._dirty, id(self)))
760 self._dirty, id(self)))
761
761
762 def dir(self):
762 def dir(self):
763 '''The directory that this tree manifest represents, including a
763 '''The directory that this tree manifest represents, including a
764 trailing '/'. Empty string for the repo root directory.'''
764 trailing '/'. Empty string for the repo root directory.'''
765 return self._dir
765 return self._dir
766
766
767 def node(self):
767 def node(self):
768 '''This node of this instance. nullid for unsaved instances. Should
768 '''This node of this instance. nullid for unsaved instances. Should
769 be updated when the instance is read or written from a revlog.
769 be updated when the instance is read or written from a revlog.
770 '''
770 '''
771 assert not self._dirty
771 assert not self._dirty
772 return self._node
772 return self._node
773
773
774 def setnode(self, node):
774 def setnode(self, node):
775 self._node = node
775 self._node = node
776 self._dirty = False
776 self._dirty = False
777
777
778 def iterentries(self):
778 def iterentries(self):
779 self._load()
779 self._load()
780 self._loadalllazy()
780 self._loadalllazy()
781 for p, n in sorted(itertools.chain(self._dirs.items(),
781 for p, n in sorted(itertools.chain(self._dirs.items(),
782 self._files.items())):
782 self._files.items())):
783 if p in self._files:
783 if p in self._files:
784 yield self._subpath(p), n, self._flags.get(p, '')
784 yield self._subpath(p), n, self._flags.get(p, '')
785 else:
785 else:
786 for x in n.iterentries():
786 for x in n.iterentries():
787 yield x
787 yield x
788
788
789 def items(self):
789 def items(self):
790 self._load()
790 self._load()
791 self._loadalllazy()
791 self._loadalllazy()
792 for p, n in sorted(itertools.chain(self._dirs.items(),
792 for p, n in sorted(itertools.chain(self._dirs.items(),
793 self._files.items())):
793 self._files.items())):
794 if p in self._files:
794 if p in self._files:
795 yield self._subpath(p), n
795 yield self._subpath(p), n
796 else:
796 else:
797 for f, sn in n.iteritems():
797 for f, sn in n.iteritems():
798 yield f, sn
798 yield f, sn
799
799
800 iteritems = items
800 iteritems = items
801
801
802 def iterkeys(self):
802 def iterkeys(self):
803 self._load()
803 self._load()
804 self._loadalllazy()
804 self._loadalllazy()
805 for p in sorted(itertools.chain(self._dirs, self._files)):
805 for p in sorted(itertools.chain(self._dirs, self._files)):
806 if p in self._files:
806 if p in self._files:
807 yield self._subpath(p)
807 yield self._subpath(p)
808 else:
808 else:
809 for f in self._dirs[p]:
809 for f in self._dirs[p]:
810 yield f
810 yield f
811
811
812 def keys(self):
812 def keys(self):
813 return list(self.iterkeys())
813 return list(self.iterkeys())
814
814
815 def __iter__(self):
815 def __iter__(self):
816 return self.iterkeys()
816 return self.iterkeys()
817
817
818 def __contains__(self, f):
818 def __contains__(self, f):
819 if f is None:
819 if f is None:
820 return False
820 return False
821 self._load()
821 self._load()
822 dir, subpath = _splittopdir(f)
822 dir, subpath = _splittopdir(f)
823 if dir:
823 if dir:
824 if dir in self._lazydirs:
824 if dir in self._lazydirs:
825 self._loadlazy(dir)
825 self._loadlazy(dir)
826
826
827 if dir not in self._dirs:
827 if dir not in self._dirs:
828 return False
828 return False
829
829
830 return self._dirs[dir].__contains__(subpath)
830 return self._dirs[dir].__contains__(subpath)
831 else:
831 else:
832 return f in self._files
832 return f in self._files
833
833
834 def get(self, f, default=None):
834 def get(self, f, default=None):
835 self._load()
835 self._load()
836 dir, subpath = _splittopdir(f)
836 dir, subpath = _splittopdir(f)
837 if dir:
837 if dir:
838 if dir in self._lazydirs:
838 if dir in self._lazydirs:
839 self._loadlazy(dir)
839 self._loadlazy(dir)
840
840
841 if dir not in self._dirs:
841 if dir not in self._dirs:
842 return default
842 return default
843 return self._dirs[dir].get(subpath, default)
843 return self._dirs[dir].get(subpath, default)
844 else:
844 else:
845 return self._files.get(f, default)
845 return self._files.get(f, default)
846
846
847 def __getitem__(self, f):
847 def __getitem__(self, f):
848 self._load()
848 self._load()
849 dir, subpath = _splittopdir(f)
849 dir, subpath = _splittopdir(f)
850 if dir:
850 if dir:
851 if dir in self._lazydirs:
851 if dir in self._lazydirs:
852 self._loadlazy(dir)
852 self._loadlazy(dir)
853
853
854 return self._dirs[dir].__getitem__(subpath)
854 return self._dirs[dir].__getitem__(subpath)
855 else:
855 else:
856 return self._files[f]
856 return self._files[f]
857
857
858 def flags(self, f):
858 def flags(self, f):
859 self._load()
859 self._load()
860 dir, subpath = _splittopdir(f)
860 dir, subpath = _splittopdir(f)
861 if dir:
861 if dir:
862 if dir in self._lazydirs:
862 if dir in self._lazydirs:
863 self._loadlazy(dir)
863 self._loadlazy(dir)
864
864
865 if dir not in self._dirs:
865 if dir not in self._dirs:
866 return ''
866 return ''
867 return self._dirs[dir].flags(subpath)
867 return self._dirs[dir].flags(subpath)
868 else:
868 else:
869 if f in self._lazydirs or f in self._dirs:
869 if f in self._lazydirs or f in self._dirs:
870 return ''
870 return ''
871 return self._flags.get(f, '')
871 return self._flags.get(f, '')
872
872
873 def find(self, f):
873 def find(self, f):
874 self._load()
874 self._load()
875 dir, subpath = _splittopdir(f)
875 dir, subpath = _splittopdir(f)
876 if dir:
876 if dir:
877 if dir in self._lazydirs:
877 if dir in self._lazydirs:
878 self._loadlazy(dir)
878 self._loadlazy(dir)
879
879
880 return self._dirs[dir].find(subpath)
880 return self._dirs[dir].find(subpath)
881 else:
881 else:
882 return self._files[f], self._flags.get(f, '')
882 return self._files[f], self._flags.get(f, '')
883
883
884 def __delitem__(self, f):
884 def __delitem__(self, f):
885 self._load()
885 self._load()
886 dir, subpath = _splittopdir(f)
886 dir, subpath = _splittopdir(f)
887 if dir:
887 if dir:
888 if dir in self._lazydirs:
888 if dir in self._lazydirs:
889 self._loadlazy(dir)
889 self._loadlazy(dir)
890
890
891 self._dirs[dir].__delitem__(subpath)
891 self._dirs[dir].__delitem__(subpath)
892 # If the directory is now empty, remove it
892 # If the directory is now empty, remove it
893 if self._dirs[dir]._isempty():
893 if self._dirs[dir]._isempty():
894 del self._dirs[dir]
894 del self._dirs[dir]
895 else:
895 else:
896 del self._files[f]
896 del self._files[f]
897 if f in self._flags:
897 if f in self._flags:
898 del self._flags[f]
898 del self._flags[f]
899 self._dirty = True
899 self._dirty = True
900
900
901 def __setitem__(self, f, n):
901 def __setitem__(self, f, n):
902 assert n is not None
902 assert n is not None
903 self._load()
903 self._load()
904 dir, subpath = _splittopdir(f)
904 dir, subpath = _splittopdir(f)
905 if dir:
905 if dir:
906 if dir in self._lazydirs:
906 if dir in self._lazydirs:
907 self._loadlazy(dir)
907 self._loadlazy(dir)
908 if dir not in self._dirs:
908 if dir not in self._dirs:
909 self._dirs[dir] = treemanifest(self._subpath(dir))
909 self._dirs[dir] = treemanifest(self._subpath(dir))
910 self._dirs[dir].__setitem__(subpath, n)
910 self._dirs[dir].__setitem__(subpath, n)
911 else:
911 else:
912 self._files[f] = n[:21] # to match manifestdict's behavior
912 self._files[f] = n[:21] # to match manifestdict's behavior
913 self._dirty = True
913 self._dirty = True
914
914
915 def _load(self):
915 def _load(self):
916 if self._loadfunc is not _noop:
916 if self._loadfunc is not _noop:
917 lf, self._loadfunc = self._loadfunc, _noop
917 lf, self._loadfunc = self._loadfunc, _noop
918 lf(self)
918 lf(self)
919 elif self._copyfunc is not _noop:
919 elif self._copyfunc is not _noop:
920 cf, self._copyfunc = self._copyfunc, _noop
920 cf, self._copyfunc = self._copyfunc, _noop
921 cf(self)
921 cf(self)
922
922
923 def setflag(self, f, flags):
923 def setflag(self, f, flags):
924 """Set the flags (symlink, executable) for path f."""
924 """Set the flags (symlink, executable) for path f."""
925 self._load()
925 self._load()
926 dir, subpath = _splittopdir(f)
926 dir, subpath = _splittopdir(f)
927 if dir:
927 if dir:
928 if dir in self._lazydirs:
928 if dir in self._lazydirs:
929 self._loadlazy(dir)
929 self._loadlazy(dir)
930 if dir not in self._dirs:
930 if dir not in self._dirs:
931 self._dirs[dir] = treemanifest(self._subpath(dir))
931 self._dirs[dir] = treemanifest(self._subpath(dir))
932 self._dirs[dir].setflag(subpath, flags)
932 self._dirs[dir].setflag(subpath, flags)
933 else:
933 else:
934 self._flags[f] = flags
934 self._flags[f] = flags
935 self._dirty = True
935 self._dirty = True
936
936
937 def copy(self):
937 def copy(self):
938 copy = treemanifest(self._dir)
938 copy = treemanifest(self._dir)
939 copy._node = self._node
939 copy._node = self._node
940 copy._dirty = self._dirty
940 copy._dirty = self._dirty
941 if self._copyfunc is _noop:
941 if self._copyfunc is _noop:
942 def _copyfunc(s):
942 def _copyfunc(s):
943 self._load()
943 self._load()
944 # OPT: it'd be nice to not load everything here. Unfortunately
944 # OPT: it'd be nice to not load everything here. Unfortunately
945 # this makes a mess of the "dirty" state tracking if we don't.
945 # this makes a mess of the "dirty" state tracking if we don't.
946 self._loadalllazy()
946 self._loadalllazy()
947 sdirs = s._dirs
947 sdirs = s._dirs
948 for d, v in self._dirs.iteritems():
948 for d, v in self._dirs.iteritems():
949 sdirs[d] = v.copy()
949 sdirs[d] = v.copy()
950 s._files = dict.copy(self._files)
950 s._files = dict.copy(self._files)
951 s._flags = dict.copy(self._flags)
951 s._flags = dict.copy(self._flags)
952 if self._loadfunc is _noop:
952 if self._loadfunc is _noop:
953 _copyfunc(copy)
953 _copyfunc(copy)
954 else:
954 else:
955 copy._copyfunc = _copyfunc
955 copy._copyfunc = _copyfunc
956 else:
956 else:
957 copy._copyfunc = self._copyfunc
957 copy._copyfunc = self._copyfunc
958 return copy
958 return copy
959
959
960 def filesnotin(self, m2, match=None):
960 def filesnotin(self, m2, match=None):
961 '''Set of files in this manifest that are not in the other'''
961 '''Set of files in this manifest that are not in the other'''
962 if match and not match.always():
962 if match and not match.always():
963 m1 = self.matches(match)
963 m1 = self.matches(match)
964 m2 = m2.matches(match)
964 m2 = m2.matches(match)
965 return m1.filesnotin(m2)
965 return m1.filesnotin(m2)
966
966
967 files = set()
967 files = set()
968 def _filesnotin(t1, t2):
968 def _filesnotin(t1, t2):
969 if t1._node == t2._node and not t1._dirty and not t2._dirty:
969 if t1._node == t2._node and not t1._dirty and not t2._dirty:
970 return
970 return
971 t1._load()
971 t1._load()
972 t2._load()
972 t2._load()
973 t1._loadalllazy()
973 t1._loadalllazy()
974 t2._loadalllazy()
974 t2._loadalllazy()
975 for d, m1 in t1._dirs.iteritems():
975 for d, m1 in t1._dirs.iteritems():
976 if d in t2._dirs:
976 if d in t2._dirs:
977 m2 = t2._dirs[d]
977 m2 = t2._dirs[d]
978 _filesnotin(m1, m2)
978 _filesnotin(m1, m2)
979 else:
979 else:
980 files.update(m1.iterkeys())
980 files.update(m1.iterkeys())
981
981
982 for fn in t1._files:
982 for fn in t1._files:
983 if fn not in t2._files:
983 if fn not in t2._files:
984 files.add(t1._subpath(fn))
984 files.add(t1._subpath(fn))
985
985
986 _filesnotin(self, m2)
986 _filesnotin(self, m2)
987 return files
987 return files
988
988
989 @propertycache
989 @propertycache
990 def _alldirs(self):
990 def _alldirs(self):
991 return util.dirs(self)
991 return util.dirs(self)
992
992
993 def dirs(self):
993 def dirs(self):
994 return self._alldirs
994 return self._alldirs
995
995
996 def hasdir(self, dir):
996 def hasdir(self, dir):
997 self._load()
997 self._load()
998 topdir, subdir = _splittopdir(dir)
998 topdir, subdir = _splittopdir(dir)
999 if topdir:
999 if topdir:
1000 if topdir in self._lazydirs:
1000 if topdir in self._lazydirs:
1001 self._loadlazy(topdir)
1001 self._loadlazy(topdir)
1002 if topdir in self._dirs:
1002 if topdir in self._dirs:
1003 return self._dirs[topdir].hasdir(subdir)
1003 return self._dirs[topdir].hasdir(subdir)
1004 return False
1004 return False
1005 dirslash = dir + '/'
1005 dirslash = dir + '/'
1006 return dirslash in self._dirs or dirslash in self._lazydirs
1006 return dirslash in self._dirs or dirslash in self._lazydirs
1007
1007
1008 def walk(self, match):
1008 def walk(self, match):
1009 '''Generates matching file names.
1009 '''Generates matching file names.
1010
1010
1011 Equivalent to manifest.matches(match).iterkeys(), but without creating
1011 Equivalent to manifest.matches(match).iterkeys(), but without creating
1012 an entirely new manifest.
1012 an entirely new manifest.
1013
1013
1014 It also reports nonexistent files by marking them bad with match.bad().
1014 It also reports nonexistent files by marking them bad with match.bad().
1015 '''
1015 '''
1016 if match.always():
1016 if match.always():
1017 for f in iter(self):
1017 for f in iter(self):
1018 yield f
1018 yield f
1019 return
1019 return
1020
1020
1021 fset = set(match.files())
1021 fset = set(match.files())
1022
1022
1023 for fn in self._walk(match):
1023 for fn in self._walk(match):
1024 if fn in fset:
1024 if fn in fset:
1025 # specified pattern is the exact name
1025 # specified pattern is the exact name
1026 fset.remove(fn)
1026 fset.remove(fn)
1027 yield fn
1027 yield fn
1028
1028
1029 # for dirstate.walk, files=['.'] means "walk the whole tree".
1029 # for dirstate.walk, files=['.'] means "walk the whole tree".
1030 # follow that here, too
1030 # follow that here, too
1031 fset.discard('.')
1031 fset.discard('.')
1032
1032
1033 for fn in sorted(fset):
1033 for fn in sorted(fset):
1034 if not self.hasdir(fn):
1034 if not self.hasdir(fn):
1035 match.bad(fn, None)
1035 match.bad(fn, None)
1036
1036
1037 def _walk(self, match):
1037 def _walk(self, match):
1038 '''Recursively generates matching file names for walk().'''
1038 '''Recursively generates matching file names for walk().'''
1039 visit = match.visitchildrenset(self._dir[:-1] or '.')
1039 visit = match.visitchildrenset(self._dir[:-1] or '.')
1040 if not visit:
1040 if not visit:
1041 return
1041 return
1042
1042
1043 # yield this dir's files and walk its submanifests
1043 # yield this dir's files and walk its submanifests
1044 self._load()
1044 self._load()
1045 visit = self._loadchildrensetlazy(visit)
1045 visit = self._loadchildrensetlazy(visit)
1046 for p in sorted(list(self._dirs) + list(self._files)):
1046 for p in sorted(list(self._dirs) + list(self._files)):
1047 if p in self._files:
1047 if p in self._files:
1048 fullp = self._subpath(p)
1048 fullp = self._subpath(p)
1049 if match(fullp):
1049 if match(fullp):
1050 yield fullp
1050 yield fullp
1051 else:
1051 else:
1052 if not visit or p[:-1] in visit:
1052 if not visit or p[:-1] in visit:
1053 for f in self._dirs[p]._walk(match):
1053 for f in self._dirs[p]._walk(match):
1054 yield f
1054 yield f
1055
1055
1056 def matches(self, match):
1056 def matches(self, match):
1057 '''generate a new manifest filtered by the match argument'''
1057 '''generate a new manifest filtered by the match argument'''
1058 if match.always():
1058 if match.always():
1059 return self.copy()
1059 return self.copy()
1060
1060
1061 return self._matches(match)
1061 return self._matches(match)
1062
1062
1063 def _matches(self, match):
1063 def _matches(self, match):
1064 '''recursively generate a new manifest filtered by the match argument.
1064 '''recursively generate a new manifest filtered by the match argument.
1065 '''
1065 '''
1066
1066
1067 visit = match.visitchildrenset(self._dir[:-1] or '.')
1067 visit = match.visitchildrenset(self._dir[:-1] or '.')
1068 if visit == 'all':
1068 if visit == 'all':
1069 return self.copy()
1069 return self.copy()
1070 ret = treemanifest(self._dir)
1070 ret = treemanifest(self._dir)
1071 if not visit:
1071 if not visit:
1072 return ret
1072 return ret
1073
1073
1074 self._load()
1074 self._load()
1075 for fn in self._files:
1075 for fn in self._files:
1076 # While visitchildrenset *usually* lists only subdirs, this is
1076 # While visitchildrenset *usually* lists only subdirs, this is
1077 # actually up to the matcher and may have some files in the set().
1077 # actually up to the matcher and may have some files in the set().
1078 # If visit == 'this', we should obviously look at the files in this
1078 # If visit == 'this', we should obviously look at the files in this
1079 # directory; if visit is a set, and fn is in it, we should inspect
1079 # directory; if visit is a set, and fn is in it, we should inspect
1080 # fn (but no need to inspect things not in the set).
1080 # fn (but no need to inspect things not in the set).
1081 if visit != 'this' and fn not in visit:
1081 if visit != 'this' and fn not in visit:
1082 continue
1082 continue
1083 fullp = self._subpath(fn)
1083 fullp = self._subpath(fn)
1084 # visitchildrenset isn't perfect, we still need to call the regular
1084 # visitchildrenset isn't perfect, we still need to call the regular
1085 # matcher code to further filter results.
1085 # matcher code to further filter results.
1086 if not match(fullp):
1086 if not match(fullp):
1087 continue
1087 continue
1088 ret._files[fn] = self._files[fn]
1088 ret._files[fn] = self._files[fn]
1089 if fn in self._flags:
1089 if fn in self._flags:
1090 ret._flags[fn] = self._flags[fn]
1090 ret._flags[fn] = self._flags[fn]
1091
1091
1092 visit = self._loadchildrensetlazy(visit)
1092 visit = self._loadchildrensetlazy(visit)
1093 for dir, subm in self._dirs.iteritems():
1093 for dir, subm in self._dirs.iteritems():
1094 if visit and dir[:-1] not in visit:
1094 if visit and dir[:-1] not in visit:
1095 continue
1095 continue
1096 m = subm._matches(match)
1096 m = subm._matches(match)
1097 if not m._isempty():
1097 if not m._isempty():
1098 ret._dirs[dir] = m
1098 ret._dirs[dir] = m
1099
1099
1100 if not ret._isempty():
1100 if not ret._isempty():
1101 ret._dirty = True
1101 ret._dirty = True
1102 return ret
1102 return ret
1103
1103
1104 def diff(self, m2, match=None, clean=False):
1104 def diff(self, m2, match=None, clean=False):
1105 '''Finds changes between the current manifest and m2.
1105 '''Finds changes between the current manifest and m2.
1106
1106
1107 Args:
1107 Args:
1108 m2: the manifest to which this manifest should be compared.
1108 m2: the manifest to which this manifest should be compared.
1109 clean: if true, include files unchanged between these manifests
1109 clean: if true, include files unchanged between these manifests
1110 with a None value in the returned dictionary.
1110 with a None value in the returned dictionary.
1111
1111
1112 The result is returned as a dict with filename as key and
1112 The result is returned as a dict with filename as key and
1113 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1113 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1114 nodeid in the current/other manifest and fl1/fl2 is the flag
1114 nodeid in the current/other manifest and fl1/fl2 is the flag
1115 in the current/other manifest. Where the file does not exist,
1115 in the current/other manifest. Where the file does not exist,
1116 the nodeid will be None and the flags will be the empty
1116 the nodeid will be None and the flags will be the empty
1117 string.
1117 string.
1118 '''
1118 '''
1119 if match and not match.always():
1119 if match and not match.always():
1120 m1 = self.matches(match)
1120 m1 = self.matches(match)
1121 m2 = m2.matches(match)
1121 m2 = m2.matches(match)
1122 return m1.diff(m2, clean=clean)
1122 return m1.diff(m2, clean=clean)
1123 result = {}
1123 result = {}
1124 emptytree = treemanifest()
1124 emptytree = treemanifest()
1125 def _diff(t1, t2):
1125 def _diff(t1, t2):
1126 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1126 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1127 return
1127 return
1128 t1._load()
1128 t1._load()
1129 t2._load()
1129 t2._load()
1130 # OPT: do we need to load everything?
1130 # OPT: do we need to load everything?
1131 t1._loadalllazy()
1131 t1._loadalllazy()
1132 t2._loadalllazy()
1132 t2._loadalllazy()
1133 for d, m1 in t1._dirs.iteritems():
1133 for d, m1 in t1._dirs.iteritems():
1134 m2 = t2._dirs.get(d, emptytree)
1134 m2 = t2._dirs.get(d, emptytree)
1135 _diff(m1, m2)
1135 _diff(m1, m2)
1136
1136
1137 for d, m2 in t2._dirs.iteritems():
1137 for d, m2 in t2._dirs.iteritems():
1138 if d not in t1._dirs:
1138 if d not in t1._dirs:
1139 _diff(emptytree, m2)
1139 _diff(emptytree, m2)
1140
1140
1141 for fn, n1 in t1._files.iteritems():
1141 for fn, n1 in t1._files.iteritems():
1142 fl1 = t1._flags.get(fn, '')
1142 fl1 = t1._flags.get(fn, '')
1143 n2 = t2._files.get(fn, None)
1143 n2 = t2._files.get(fn, None)
1144 fl2 = t2._flags.get(fn, '')
1144 fl2 = t2._flags.get(fn, '')
1145 if n1 != n2 or fl1 != fl2:
1145 if n1 != n2 or fl1 != fl2:
1146 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1146 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1147 elif clean:
1147 elif clean:
1148 result[t1._subpath(fn)] = None
1148 result[t1._subpath(fn)] = None
1149
1149
1150 for fn, n2 in t2._files.iteritems():
1150 for fn, n2 in t2._files.iteritems():
1151 if fn not in t1._files:
1151 if fn not in t1._files:
1152 fl2 = t2._flags.get(fn, '')
1152 fl2 = t2._flags.get(fn, '')
1153 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1153 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1154
1154
1155 _diff(self, m2)
1155 _diff(self, m2)
1156 return result
1156 return result
1157
1157
1158 def unmodifiedsince(self, m2):
1158 def unmodifiedsince(self, m2):
1159 return not self._dirty and not m2._dirty and self._node == m2._node
1159 return not self._dirty and not m2._dirty and self._node == m2._node
1160
1160
1161 def parse(self, text, readsubtree):
1161 def parse(self, text, readsubtree):
1162 selflazy = self._lazydirs
1162 selflazy = self._lazydirs
1163 subpath = self._subpath
1163 subpath = self._subpath
1164 for f, n, fl in _parse(text):
1164 for f, n, fl in _parse(text):
1165 if fl == 't':
1165 if fl == 't':
1166 f = f + '/'
1166 f = f + '/'
1167 selflazy[f] = (subpath(f), n, readsubtree)
1167 selflazy[f] = (subpath(f), n, readsubtree)
1168 elif '/' in f:
1168 elif '/' in f:
1169 # This is a flat manifest, so use __setitem__ and setflag rather
1169 # This is a flat manifest, so use __setitem__ and setflag rather
1170 # than assigning directly to _files and _flags, so we can
1170 # than assigning directly to _files and _flags, so we can
1171 # assign a path in a subdirectory, and to mark dirty (compared
1171 # assign a path in a subdirectory, and to mark dirty (compared
1172 # to nullid).
1172 # to nullid).
1173 self[f] = n
1173 self[f] = n
1174 if fl:
1174 if fl:
1175 self.setflag(f, fl)
1175 self.setflag(f, fl)
1176 else:
1176 else:
1177 # Assigning to _files and _flags avoids marking as dirty,
1177 # Assigning to _files and _flags avoids marking as dirty,
1178 # and should be a little faster.
1178 # and should be a little faster.
1179 self._files[f] = n
1179 self._files[f] = n
1180 if fl:
1180 if fl:
1181 self._flags[f] = fl
1181 self._flags[f] = fl
1182
1182
1183 def text(self):
1183 def text(self):
1184 """Get the full data of this manifest as a bytestring."""
1184 """Get the full data of this manifest as a bytestring."""
1185 self._load()
1185 self._load()
1186 return _text(self.iterentries())
1186 return _text(self.iterentries())
1187
1187
1188 def dirtext(self):
1188 def dirtext(self):
1189 """Get the full data of this directory as a bytestring. Make sure that
1189 """Get the full data of this directory as a bytestring. Make sure that
1190 any submanifests have been written first, so their nodeids are correct.
1190 any submanifests have been written first, so their nodeids are correct.
1191 """
1191 """
1192 self._load()
1192 self._load()
1193 flags = self.flags
1193 flags = self.flags
1194 lazydirs = [(d[:-1], node, 't') for
1194 lazydirs = [(d[:-1], node, 't') for
1195 d, (path, node, readsubtree) in self._lazydirs.iteritems()]
1195 d, (path, node, readsubtree) in self._lazydirs.iteritems()]
1196 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1196 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1197 files = [(f, self._files[f], flags(f)) for f in self._files]
1197 files = [(f, self._files[f], flags(f)) for f in self._files]
1198 return _text(sorted(dirs + files + lazydirs))
1198 return _text(sorted(dirs + files + lazydirs))
1199
1199
1200 def read(self, gettext, readsubtree):
1200 def read(self, gettext, readsubtree):
1201 def _load_for_read(s):
1201 def _load_for_read(s):
1202 s.parse(gettext(), readsubtree)
1202 s.parse(gettext(), readsubtree)
1203 s._dirty = False
1203 s._dirty = False
1204 self._loadfunc = _load_for_read
1204 self._loadfunc = _load_for_read
1205
1205
1206 def writesubtrees(self, m1, m2, writesubtree, match):
1206 def writesubtrees(self, m1, m2, writesubtree, match):
1207 self._load() # for consistency; should never have any effect here
1207 self._load() # for consistency; should never have any effect here
1208 m1._load()
1208 m1._load()
1209 m2._load()
1209 m2._load()
1210 emptytree = treemanifest()
1210 emptytree = treemanifest()
1211 def getnode(m, d):
1211 def getnode(m, d):
1212 ld = m._lazydirs.get(d)
1212 ld = m._lazydirs.get(d)
1213 if ld:
1213 if ld:
1214 return ld[1]
1214 return ld[1]
1215 return m._dirs.get(d, emptytree)._node
1215 return m._dirs.get(d, emptytree)._node
1216
1216
1217 # we should have always loaded everything by the time we get here for
1217 # we should have always loaded everything by the time we get here for
1218 # `self`, but possibly not in `m1` or `m2`.
1218 # `self`, but possibly not in `m1` or `m2`.
1219 assert not self._lazydirs
1219 assert not self._lazydirs
1220 # let's skip investigating things that `match` says we do not need.
1220 # let's skip investigating things that `match` says we do not need.
1221 visit = match.visitchildrenset(self._dir[:-1] or '.')
1221 visit = match.visitchildrenset(self._dir[:-1] or '.')
1222 if visit == 'this' or visit == 'all':
1222 if visit == 'this' or visit == 'all':
1223 visit = None
1223 visit = None
1224 for d, subm in self._dirs.iteritems():
1224 for d, subm in self._dirs.iteritems():
1225 if visit and d[:-1] not in visit:
1225 if visit and d[:-1] not in visit:
1226 continue
1226 continue
1227 subp1 = getnode(m1, d)
1227 subp1 = getnode(m1, d)
1228 subp2 = getnode(m2, d)
1228 subp2 = getnode(m2, d)
1229 if subp1 == nullid:
1229 if subp1 == nullid:
1230 subp1, subp2 = subp2, subp1
1230 subp1, subp2 = subp2, subp1
1231 writesubtree(subm, subp1, subp2, match)
1231 writesubtree(subm, subp1, subp2, match)
1232
1232
1233 def walksubtrees(self, matcher=None):
1233 def walksubtrees(self, matcher=None):
1234 """Returns an iterator of the subtrees of this manifest, including this
1234 """Returns an iterator of the subtrees of this manifest, including this
1235 manifest itself.
1235 manifest itself.
1236
1236
1237 If `matcher` is provided, it only returns subtrees that match.
1237 If `matcher` is provided, it only returns subtrees that match.
1238 """
1238 """
1239 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1239 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1240 return
1240 return
1241 if not matcher or matcher(self._dir[:-1]):
1241 if not matcher or matcher(self._dir[:-1]):
1242 yield self
1242 yield self
1243
1243
1244 self._load()
1244 self._load()
1245 # OPT: use visitchildrenset to avoid loading everything.
1245 # OPT: use visitchildrenset to avoid loading everything.
1246 self._loadalllazy()
1246 self._loadalllazy()
1247 for d, subm in self._dirs.iteritems():
1247 for d, subm in self._dirs.iteritems():
1248 for subtree in subm.walksubtrees(matcher=matcher):
1248 for subtree in subm.walksubtrees(matcher=matcher):
1249 yield subtree
1249 yield subtree
1250
1250
1251 class manifestfulltextcache(util.lrucachedict):
1251 class manifestfulltextcache(util.lrucachedict):
1252 """File-backed LRU cache for the manifest cache
1252 """File-backed LRU cache for the manifest cache
1253
1253
1254 File consists of entries, up to EOF:
1254 File consists of entries, up to EOF:
1255
1255
1256 - 20 bytes node, 4 bytes length, <length> manifest data
1256 - 20 bytes node, 4 bytes length, <length> manifest data
1257
1257
1258 These are written in reverse cache order (oldest to newest).
1258 These are written in reverse cache order (oldest to newest).
1259
1259
1260 """
1260 """
1261 def __init__(self, max):
1261 def __init__(self, max):
1262 super(manifestfulltextcache, self).__init__(max)
1262 super(manifestfulltextcache, self).__init__(max)
1263 self._dirty = False
1263 self._dirty = False
1264 self._read = False
1264 self._read = False
1265 self._opener = None
1265 self._opener = None
1266
1266
1267 def read(self):
1267 def read(self):
1268 if self._read or self._opener is None:
1268 if self._read or self._opener is None:
1269 return
1269 return
1270
1270
1271 try:
1271 try:
1272 with self._opener('manifestfulltextcache') as fp:
1272 with self._opener('manifestfulltextcache') as fp:
1273 set = super(manifestfulltextcache, self).__setitem__
1273 set = super(manifestfulltextcache, self).__setitem__
1274 # ignore trailing data, this is a cache, corruption is skipped
1274 # ignore trailing data, this is a cache, corruption is skipped
1275 while True:
1275 while True:
1276 node = fp.read(20)
1276 node = fp.read(20)
1277 if len(node) < 20:
1277 if len(node) < 20:
1278 break
1278 break
1279 try:
1279 try:
1280 size = struct.unpack('>L', fp.read(4))[0]
1280 size = struct.unpack('>L', fp.read(4))[0]
1281 except struct.error:
1281 except struct.error:
1282 break
1282 break
1283 value = bytearray(fp.read(size))
1283 value = bytearray(fp.read(size))
1284 if len(value) != size:
1284 if len(value) != size:
1285 break
1285 break
1286 set(node, value)
1286 set(node, value)
1287 except IOError:
1287 except IOError:
1288 # the file is allowed to be missing
1288 # the file is allowed to be missing
1289 pass
1289 pass
1290
1290
1291 self._read = True
1291 self._read = True
1292 self._dirty = False
1292 self._dirty = False
1293
1293
1294 def write(self):
1294 def write(self):
1295 if not self._dirty or self._opener is None:
1295 if not self._dirty or self._opener is None:
1296 return
1296 return
1297 # rotate backwards to the first used node
1297 # rotate backwards to the first used node
1298 with self._opener(
1298 with self._opener(
1299 'manifestfulltextcache', 'w', atomictemp=True, checkambig=True
1299 'manifestfulltextcache', 'w', atomictemp=True, checkambig=True
1300 ) as fp:
1300 ) as fp:
1301 node = self._head.prev
1301 node = self._head.prev
1302 while True:
1302 while True:
1303 if node.key in self._cache:
1303 if node.key in self._cache:
1304 fp.write(node.key)
1304 fp.write(node.key)
1305 fp.write(struct.pack('>L', len(node.value)))
1305 fp.write(struct.pack('>L', len(node.value)))
1306 fp.write(node.value)
1306 fp.write(node.value)
1307 if node is self._head:
1307 if node is self._head:
1308 break
1308 break
1309 node = node.prev
1309 node = node.prev
1310
1310
1311 def __len__(self):
1311 def __len__(self):
1312 if not self._read:
1312 if not self._read:
1313 self.read()
1313 self.read()
1314 return super(manifestfulltextcache, self).__len__()
1314 return super(manifestfulltextcache, self).__len__()
1315
1315
1316 def __contains__(self, k):
1316 def __contains__(self, k):
1317 if not self._read:
1317 if not self._read:
1318 self.read()
1318 self.read()
1319 return super(manifestfulltextcache, self).__contains__(k)
1319 return super(manifestfulltextcache, self).__contains__(k)
1320
1320
1321 def __iter__(self):
1321 def __iter__(self):
1322 if not self._read:
1322 if not self._read:
1323 self.read()
1323 self.read()
1324 return super(manifestfulltextcache, self).__iter__()
1324 return super(manifestfulltextcache, self).__iter__()
1325
1325
1326 def __getitem__(self, k):
1326 def __getitem__(self, k):
1327 if not self._read:
1327 if not self._read:
1328 self.read()
1328 self.read()
1329 # the cache lru order can change on read
1329 # the cache lru order can change on read
1330 setdirty = self._cache.get(k) is not self._head
1330 setdirty = self._cache.get(k) is not self._head
1331 value = super(manifestfulltextcache, self).__getitem__(k)
1331 value = super(manifestfulltextcache, self).__getitem__(k)
1332 if setdirty:
1332 if setdirty:
1333 self._dirty = True
1333 self._dirty = True
1334 return value
1334 return value
1335
1335
1336 def __setitem__(self, k, v):
1336 def __setitem__(self, k, v):
1337 if not self._read:
1337 if not self._read:
1338 self.read()
1338 self.read()
1339 super(manifestfulltextcache, self).__setitem__(k, v)
1339 super(manifestfulltextcache, self).__setitem__(k, v)
1340 self._dirty = True
1340 self._dirty = True
1341
1341
1342 def __delitem__(self, k):
1342 def __delitem__(self, k):
1343 if not self._read:
1343 if not self._read:
1344 self.read()
1344 self.read()
1345 super(manifestfulltextcache, self).__delitem__(k)
1345 super(manifestfulltextcache, self).__delitem__(k)
1346 self._dirty = True
1346 self._dirty = True
1347
1347
1348 def get(self, k, default=None):
1348 def get(self, k, default=None):
1349 if not self._read:
1349 if not self._read:
1350 self.read()
1350 self.read()
1351 return super(manifestfulltextcache, self).get(k, default=default)
1351 return super(manifestfulltextcache, self).get(k, default=default)
1352
1352
1353 def clear(self, clear_persisted_data=False):
1353 def clear(self, clear_persisted_data=False):
1354 super(manifestfulltextcache, self).clear()
1354 super(manifestfulltextcache, self).clear()
1355 if clear_persisted_data:
1355 if clear_persisted_data:
1356 self._dirty = True
1356 self._dirty = True
1357 self.write()
1357 self.write()
1358 self._read = False
1358 self._read = False
1359
1359
1360 @interfaceutil.implementer(repository.imanifeststorage)
1360 @interfaceutil.implementer(repository.imanifeststorage)
1361 class manifestrevlog(object):
1361 class manifestrevlog(object):
1362 '''A revlog that stores manifest texts. This is responsible for caching the
1362 '''A revlog that stores manifest texts. This is responsible for caching the
1363 full-text manifest contents.
1363 full-text manifest contents.
1364 '''
1364 '''
1365 def __init__(self, opener, tree='', dirlogcache=None, indexfile=None,
1365 def __init__(self, opener, tree='', dirlogcache=None, indexfile=None,
1366 treemanifest=False):
1366 treemanifest=False):
1367 """Constructs a new manifest revlog
1367 """Constructs a new manifest revlog
1368
1368
1369 `indexfile` - used by extensions to have two manifests at once, like
1369 `indexfile` - used by extensions to have two manifests at once, like
1370 when transitioning between flatmanifeset and treemanifests.
1370 when transitioning between flatmanifeset and treemanifests.
1371
1371
1372 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1372 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1373 options can also be used to make this a tree manifest revlog. The opener
1373 options can also be used to make this a tree manifest revlog. The opener
1374 option takes precedence, so if it is set to True, we ignore whatever
1374 option takes precedence, so if it is set to True, we ignore whatever
1375 value is passed in to the constructor.
1375 value is passed in to the constructor.
1376 """
1376 """
1377 # During normal operations, we expect to deal with not more than four
1377 # During normal operations, we expect to deal with not more than four
1378 # revs at a time (such as during commit --amend). When rebasing large
1378 # revs at a time (such as during commit --amend). When rebasing large
1379 # stacks of commits, the number can go up, hence the config knob below.
1379 # stacks of commits, the number can go up, hence the config knob below.
1380 cachesize = 4
1380 cachesize = 4
1381 optiontreemanifest = False
1381 optiontreemanifest = False
1382 opts = getattr(opener, 'options', None)
1382 opts = getattr(opener, 'options', None)
1383 if opts is not None:
1383 if opts is not None:
1384 cachesize = opts.get('manifestcachesize', cachesize)
1384 cachesize = opts.get('manifestcachesize', cachesize)
1385 optiontreemanifest = opts.get('treemanifest', False)
1385 optiontreemanifest = opts.get('treemanifest', False)
1386
1386
1387 self._treeondisk = optiontreemanifest or treemanifest
1387 self._treeondisk = optiontreemanifest or treemanifest
1388
1388
1389 self._fulltextcache = manifestfulltextcache(cachesize)
1389 self._fulltextcache = manifestfulltextcache(cachesize)
1390
1390
1391 if tree:
1391 if tree:
1392 assert self._treeondisk, 'opts is %r' % opts
1392 assert self._treeondisk, 'opts is %r' % opts
1393
1393
1394 if indexfile is None:
1394 if indexfile is None:
1395 indexfile = '00manifest.i'
1395 indexfile = '00manifest.i'
1396 if tree:
1396 if tree:
1397 indexfile = "meta/" + tree + indexfile
1397 indexfile = "meta/" + tree + indexfile
1398
1398
1399 self.tree = tree
1399 self.tree = tree
1400
1400
1401 # The dirlogcache is kept on the root manifest log
1401 # The dirlogcache is kept on the root manifest log
1402 if tree:
1402 if tree:
1403 self._dirlogcache = dirlogcache
1403 self._dirlogcache = dirlogcache
1404 else:
1404 else:
1405 self._dirlogcache = {'': self}
1405 self._dirlogcache = {'': self}
1406
1406
1407 self._revlog = revlog.revlog(opener, indexfile,
1407 self._revlog = revlog.revlog(opener, indexfile,
1408 # only root indexfile is cached
1408 # only root indexfile is cached
1409 checkambig=not bool(tree),
1409 checkambig=not bool(tree),
1410 mmaplargeindex=True)
1410 mmaplargeindex=True)
1411
1411
1412 self.index = self._revlog.index
1412 self.index = self._revlog.index
1413 self.version = self._revlog.version
1413 self.version = self._revlog.version
1414 self._generaldelta = self._revlog._generaldelta
1414 self._generaldelta = self._revlog._generaldelta
1415
1415
1416 def _setupmanifestcachehooks(self, repo):
1416 def _setupmanifestcachehooks(self, repo):
1417 """Persist the manifestfulltextcache on lock release"""
1417 """Persist the manifestfulltextcache on lock release"""
1418 if not util.safehasattr(repo, '_lockref'):
1418 if not util.safehasattr(repo, '_lockref'):
1419 return
1419 return
1420
1420
1421 self._fulltextcache._opener = repo.cachevfs
1421 self._fulltextcache._opener = repo.cachevfs
1422 reporef = weakref.ref(repo)
1422 reporef = weakref.ref(repo)
1423 manifestrevlogref = weakref.ref(self)
1423 manifestrevlogref = weakref.ref(self)
1424
1424
1425 def persistmanifestcache():
1425 def persistmanifestcache():
1426 repo = reporef()
1426 repo = reporef()
1427 self = manifestrevlogref()
1427 self = manifestrevlogref()
1428 if repo is None or self is None:
1428 if repo is None or self is None:
1429 return
1429 return
1430 if repo.manifestlog.getstorage(b'') is not self:
1430 if repo.manifestlog.getstorage(b'') is not self:
1431 # there's a different manifest in play now, abort
1431 # there's a different manifest in play now, abort
1432 return
1432 return
1433 self._fulltextcache.write()
1433 self._fulltextcache.write()
1434
1434
1435 if repo._currentlock(repo._lockref) is not None:
1435 if repo._currentlock(repo._lockref) is not None:
1436 repo._afterlock(persistmanifestcache)
1436 repo._afterlock(persistmanifestcache)
1437
1437
1438 @property
1438 @property
1439 def fulltextcache(self):
1439 def fulltextcache(self):
1440 return self._fulltextcache
1440 return self._fulltextcache
1441
1441
1442 def clearcaches(self, clear_persisted_data=False):
1442 def clearcaches(self, clear_persisted_data=False):
1443 self._revlog.clearcaches()
1443 self._revlog.clearcaches()
1444 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1444 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1445 self._dirlogcache = {self.tree: self}
1445 self._dirlogcache = {self.tree: self}
1446
1446
1447 def dirlog(self, d):
1447 def dirlog(self, d):
1448 if d:
1448 if d:
1449 assert self._treeondisk
1449 assert self._treeondisk
1450 if d not in self._dirlogcache:
1450 if d not in self._dirlogcache:
1451 mfrevlog = manifestrevlog(self.opener, d,
1451 mfrevlog = manifestrevlog(self.opener, d,
1452 self._dirlogcache,
1452 self._dirlogcache,
1453 treemanifest=self._treeondisk)
1453 treemanifest=self._treeondisk)
1454 self._dirlogcache[d] = mfrevlog
1454 self._dirlogcache[d] = mfrevlog
1455 return self._dirlogcache[d]
1455 return self._dirlogcache[d]
1456
1456
1457 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None,
1457 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None,
1458 match=None):
1458 match=None):
1459 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1459 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1460 # If our first parent is in the manifest cache, we can
1460 # If our first parent is in the manifest cache, we can
1461 # compute a delta here using properties we know about the
1461 # compute a delta here using properties we know about the
1462 # manifest up-front, which may save time later for the
1462 # manifest up-front, which may save time later for the
1463 # revlog layer.
1463 # revlog layer.
1464
1464
1465 _checkforbidden(added)
1465 _checkforbidden(added)
1466 # combine the changed lists into one sorted iterator
1466 # combine the changed lists into one sorted iterator
1467 work = heapq.merge([(x, False) for x in added],
1467 work = heapq.merge([(x, False) for x in added],
1468 [(x, True) for x in removed])
1468 [(x, True) for x in removed])
1469
1469
1470 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1470 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1471 cachedelta = self._revlog.rev(p1), deltatext
1471 cachedelta = self._revlog.rev(p1), deltatext
1472 text = util.buffer(arraytext)
1472 text = util.buffer(arraytext)
1473 n = self._revlog.addrevision(text, transaction, link, p1, p2,
1473 n = self._revlog.addrevision(text, transaction, link, p1, p2,
1474 cachedelta)
1474 cachedelta)
1475 else:
1475 else:
1476 # The first parent manifest isn't already loaded, so we'll
1476 # The first parent manifest isn't already loaded, so we'll
1477 # just encode a fulltext of the manifest and pass that
1477 # just encode a fulltext of the manifest and pass that
1478 # through to the revlog layer, and let it handle the delta
1478 # through to the revlog layer, and let it handle the delta
1479 # process.
1479 # process.
1480 if self._treeondisk:
1480 if self._treeondisk:
1481 assert readtree, "readtree must be set for treemanifest writes"
1481 assert readtree, "readtree must be set for treemanifest writes"
1482 assert match, "match must be specified for treemanifest writes"
1482 assert match, "match must be specified for treemanifest writes"
1483 m1 = readtree(self.tree, p1)
1483 m1 = readtree(self.tree, p1)
1484 m2 = readtree(self.tree, p2)
1484 m2 = readtree(self.tree, p2)
1485 n = self._addtree(m, transaction, link, m1, m2, readtree,
1485 n = self._addtree(m, transaction, link, m1, m2, readtree,
1486 match=match)
1486 match=match)
1487 arraytext = None
1487 arraytext = None
1488 else:
1488 else:
1489 text = m.text()
1489 text = m.text()
1490 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1490 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1491 arraytext = bytearray(text)
1491 arraytext = bytearray(text)
1492
1492
1493 if arraytext is not None:
1493 if arraytext is not None:
1494 self.fulltextcache[n] = arraytext
1494 self.fulltextcache[n] = arraytext
1495
1495
1496 return n
1496 return n
1497
1497
1498 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1498 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1499 # If the manifest is unchanged compared to one parent,
1499 # If the manifest is unchanged compared to one parent,
1500 # don't write a new revision
1500 # don't write a new revision
1501 if self.tree != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(
1501 if self.tree != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(
1502 m2)):
1502 m2)):
1503 return m.node()
1503 return m.node()
1504 def writesubtree(subm, subp1, subp2, match):
1504 def writesubtree(subm, subp1, subp2, match):
1505 sublog = self.dirlog(subm.dir())
1505 sublog = self.dirlog(subm.dir())
1506 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1506 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1507 readtree=readtree, match=match)
1507 readtree=readtree, match=match)
1508 m.writesubtrees(m1, m2, writesubtree, match)
1508 m.writesubtrees(m1, m2, writesubtree, match)
1509 text = m.dirtext()
1509 text = m.dirtext()
1510 n = None
1510 n = None
1511 if self.tree != '':
1511 if self.tree != '':
1512 # Double-check whether contents are unchanged to one parent
1512 # Double-check whether contents are unchanged to one parent
1513 if text == m1.dirtext():
1513 if text == m1.dirtext():
1514 n = m1.node()
1514 n = m1.node()
1515 elif text == m2.dirtext():
1515 elif text == m2.dirtext():
1516 n = m2.node()
1516 n = m2.node()
1517
1517
1518 if not n:
1518 if not n:
1519 n = self._revlog.addrevision(text, transaction, link, m1.node(),
1519 n = self._revlog.addrevision(text, transaction, link, m1.node(),
1520 m2.node())
1520 m2.node())
1521
1521
1522 # Save nodeid so parent manifest can calculate its nodeid
1522 # Save nodeid so parent manifest can calculate its nodeid
1523 m.setnode(n)
1523 m.setnode(n)
1524 return n
1524 return n
1525
1525
1526 def __len__(self):
1526 def __len__(self):
1527 return len(self._revlog)
1527 return len(self._revlog)
1528
1528
1529 def __iter__(self):
1529 def __iter__(self):
1530 return self._revlog.__iter__()
1530 return self._revlog.__iter__()
1531
1531
1532 def rev(self, node):
1532 def rev(self, node):
1533 return self._revlog.rev(node)
1533 return self._revlog.rev(node)
1534
1534
1535 def node(self, rev):
1535 def node(self, rev):
1536 return self._revlog.node(rev)
1536 return self._revlog.node(rev)
1537
1537
1538 def lookup(self, value):
1538 def lookup(self, value):
1539 return self._revlog.lookup(value)
1539 return self._revlog.lookup(value)
1540
1540
1541 def parentrevs(self, rev):
1541 def parentrevs(self, rev):
1542 return self._revlog.parentrevs(rev)
1542 return self._revlog.parentrevs(rev)
1543
1543
1544 def parents(self, node):
1544 def parents(self, node):
1545 return self._revlog.parents(node)
1545 return self._revlog.parents(node)
1546
1546
1547 def linkrev(self, rev):
1547 def linkrev(self, rev):
1548 return self._revlog.linkrev(rev)
1548 return self._revlog.linkrev(rev)
1549
1549
1550 def checksize(self):
1550 def checksize(self):
1551 return self._revlog.checksize()
1551 return self._revlog.checksize()
1552
1552
1553 def revision(self, node, _df=None, raw=False):
1553 def revision(self, node, _df=None, raw=False):
1554 return self._revlog.revision(node, _df=_df, raw=raw)
1554 return self._revlog.revision(node, _df=_df, raw=raw)
1555
1555
1556 def revdiff(self, rev1, rev2):
1556 def revdiff(self, rev1, rev2):
1557 return self._revlog.revdiff(rev1, rev2)
1557 return self._revlog.revdiff(rev1, rev2)
1558
1558
1559 def cmp(self, node, text):
1559 def cmp(self, node, text):
1560 return self._revlog.cmp(node, text)
1560 return self._revlog.cmp(node, text)
1561
1561
1562 def deltaparent(self, rev):
1562 def deltaparent(self, rev):
1563 return self._revlog.deltaparent(rev)
1563 return self._revlog.deltaparent(rev)
1564
1564
1565 def emitrevisiondeltas(self, requests):
1566 return self._revlog.emitrevisiondeltas(requests)
1567
1568 def emitrevisions(self, nodes, nodesorder=None,
1565 def emitrevisions(self, nodes, nodesorder=None,
1569 revisiondata=False, assumehaveparentrevisions=False,
1566 revisiondata=False, assumehaveparentrevisions=False,
1570 deltaprevious=False):
1567 deltaprevious=False):
1571 return self._revlog.emitrevisions(
1568 return self._revlog.emitrevisions(
1572 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
1569 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
1573 assumehaveparentrevisions=assumehaveparentrevisions,
1570 assumehaveparentrevisions=assumehaveparentrevisions,
1574 deltaprevious=deltaprevious)
1571 deltaprevious=deltaprevious)
1575
1572
1576 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1573 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1577 return self._revlog.addgroup(deltas, linkmapper, transaction,
1574 return self._revlog.addgroup(deltas, linkmapper, transaction,
1578 addrevisioncb=addrevisioncb)
1575 addrevisioncb=addrevisioncb)
1579
1576
1580 def rawsize(self, rev):
1577 def rawsize(self, rev):
1581 return self._revlog.rawsize(rev)
1578 return self._revlog.rawsize(rev)
1582
1579
1583 def getstrippoint(self, minlink):
1580 def getstrippoint(self, minlink):
1584 return self._revlog.getstrippoint(minlink)
1581 return self._revlog.getstrippoint(minlink)
1585
1582
1586 def strip(self, minlink, transaction):
1583 def strip(self, minlink, transaction):
1587 return self._revlog.strip(minlink, transaction)
1584 return self._revlog.strip(minlink, transaction)
1588
1585
1589 def files(self):
1586 def files(self):
1590 return self._revlog.files()
1587 return self._revlog.files()
1591
1588
1592 def clone(self, tr, destrevlog, **kwargs):
1589 def clone(self, tr, destrevlog, **kwargs):
1593 if not isinstance(destrevlog, manifestrevlog):
1590 if not isinstance(destrevlog, manifestrevlog):
1594 raise error.ProgrammingError('expected manifestrevlog to clone()')
1591 raise error.ProgrammingError('expected manifestrevlog to clone()')
1595
1592
1596 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1593 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1597
1594
1598 @property
1595 @property
1599 def indexfile(self):
1596 def indexfile(self):
1600 return self._revlog.indexfile
1597 return self._revlog.indexfile
1601
1598
1602 @indexfile.setter
1599 @indexfile.setter
1603 def indexfile(self, value):
1600 def indexfile(self, value):
1604 self._revlog.indexfile = value
1601 self._revlog.indexfile = value
1605
1602
1606 @property
1603 @property
1607 def opener(self):
1604 def opener(self):
1608 return self._revlog.opener
1605 return self._revlog.opener
1609
1606
1610 @opener.setter
1607 @opener.setter
1611 def opener(self, value):
1608 def opener(self, value):
1612 self._revlog.opener = value
1609 self._revlog.opener = value
1613
1610
1614 @interfaceutil.implementer(repository.imanifestlog)
1611 @interfaceutil.implementer(repository.imanifestlog)
1615 class manifestlog(object):
1612 class manifestlog(object):
1616 """A collection class representing the collection of manifest snapshots
1613 """A collection class representing the collection of manifest snapshots
1617 referenced by commits in the repository.
1614 referenced by commits in the repository.
1618
1615
1619 In this situation, 'manifest' refers to the abstract concept of a snapshot
1616 In this situation, 'manifest' refers to the abstract concept of a snapshot
1620 of the list of files in the given commit. Consumers of the output of this
1617 of the list of files in the given commit. Consumers of the output of this
1621 class do not care about the implementation details of the actual manifests
1618 class do not care about the implementation details of the actual manifests
1622 they receive (i.e. tree or flat or lazily loaded, etc)."""
1619 they receive (i.e. tree or flat or lazily loaded, etc)."""
1623 def __init__(self, opener, repo, rootstore):
1620 def __init__(self, opener, repo, rootstore):
1624 usetreemanifest = False
1621 usetreemanifest = False
1625 cachesize = 4
1622 cachesize = 4
1626
1623
1627 opts = getattr(opener, 'options', None)
1624 opts = getattr(opener, 'options', None)
1628 if opts is not None:
1625 if opts is not None:
1629 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1626 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1630 cachesize = opts.get('manifestcachesize', cachesize)
1627 cachesize = opts.get('manifestcachesize', cachesize)
1631
1628
1632 self._treemanifests = usetreemanifest
1629 self._treemanifests = usetreemanifest
1633
1630
1634 self._rootstore = rootstore
1631 self._rootstore = rootstore
1635 self._rootstore._setupmanifestcachehooks(repo)
1632 self._rootstore._setupmanifestcachehooks(repo)
1636 self._narrowmatch = repo.narrowmatch()
1633 self._narrowmatch = repo.narrowmatch()
1637
1634
1638 # A cache of the manifestctx or treemanifestctx for each directory
1635 # A cache of the manifestctx or treemanifestctx for each directory
1639 self._dirmancache = {}
1636 self._dirmancache = {}
1640 self._dirmancache[''] = util.lrucachedict(cachesize)
1637 self._dirmancache[''] = util.lrucachedict(cachesize)
1641
1638
1642 self._cachesize = cachesize
1639 self._cachesize = cachesize
1643
1640
1644 def __getitem__(self, node):
1641 def __getitem__(self, node):
1645 """Retrieves the manifest instance for the given node. Throws a
1642 """Retrieves the manifest instance for the given node. Throws a
1646 LookupError if not found.
1643 LookupError if not found.
1647 """
1644 """
1648 return self.get('', node)
1645 return self.get('', node)
1649
1646
1650 def get(self, tree, node, verify=True):
1647 def get(self, tree, node, verify=True):
1651 """Retrieves the manifest instance for the given node. Throws a
1648 """Retrieves the manifest instance for the given node. Throws a
1652 LookupError if not found.
1649 LookupError if not found.
1653
1650
1654 `verify` - if True an exception will be thrown if the node is not in
1651 `verify` - if True an exception will be thrown if the node is not in
1655 the revlog
1652 the revlog
1656 """
1653 """
1657 if node in self._dirmancache.get(tree, ()):
1654 if node in self._dirmancache.get(tree, ()):
1658 return self._dirmancache[tree][node]
1655 return self._dirmancache[tree][node]
1659
1656
1660 if not self._narrowmatch.always():
1657 if not self._narrowmatch.always():
1661 if not self._narrowmatch.visitdir(tree[:-1] or '.'):
1658 if not self._narrowmatch.visitdir(tree[:-1] or '.'):
1662 return excludeddirmanifestctx(tree, node)
1659 return excludeddirmanifestctx(tree, node)
1663 if tree:
1660 if tree:
1664 if self._rootstore._treeondisk:
1661 if self._rootstore._treeondisk:
1665 if verify:
1662 if verify:
1666 # Side-effect is LookupError is raised if node doesn't
1663 # Side-effect is LookupError is raised if node doesn't
1667 # exist.
1664 # exist.
1668 self.getstorage(tree).rev(node)
1665 self.getstorage(tree).rev(node)
1669
1666
1670 m = treemanifestctx(self, tree, node)
1667 m = treemanifestctx(self, tree, node)
1671 else:
1668 else:
1672 raise error.Abort(
1669 raise error.Abort(
1673 _("cannot ask for manifest directory '%s' in a flat "
1670 _("cannot ask for manifest directory '%s' in a flat "
1674 "manifest") % tree)
1671 "manifest") % tree)
1675 else:
1672 else:
1676 if verify:
1673 if verify:
1677 # Side-effect is LookupError is raised if node doesn't exist.
1674 # Side-effect is LookupError is raised if node doesn't exist.
1678 self._rootstore.rev(node)
1675 self._rootstore.rev(node)
1679
1676
1680 if self._treemanifests:
1677 if self._treemanifests:
1681 m = treemanifestctx(self, '', node)
1678 m = treemanifestctx(self, '', node)
1682 else:
1679 else:
1683 m = manifestctx(self, node)
1680 m = manifestctx(self, node)
1684
1681
1685 if node != nullid:
1682 if node != nullid:
1686 mancache = self._dirmancache.get(tree)
1683 mancache = self._dirmancache.get(tree)
1687 if not mancache:
1684 if not mancache:
1688 mancache = util.lrucachedict(self._cachesize)
1685 mancache = util.lrucachedict(self._cachesize)
1689 self._dirmancache[tree] = mancache
1686 self._dirmancache[tree] = mancache
1690 mancache[node] = m
1687 mancache[node] = m
1691 return m
1688 return m
1692
1689
1693 def getstorage(self, tree):
1690 def getstorage(self, tree):
1694 return self._rootstore.dirlog(tree)
1691 return self._rootstore.dirlog(tree)
1695
1692
1696 def clearcaches(self, clear_persisted_data=False):
1693 def clearcaches(self, clear_persisted_data=False):
1697 self._dirmancache.clear()
1694 self._dirmancache.clear()
1698 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1695 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1699
1696
1700 def rev(self, node):
1697 def rev(self, node):
1701 return self._rootstore.rev(node)
1698 return self._rootstore.rev(node)
1702
1699
1703 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1700 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1704 class memmanifestctx(object):
1701 class memmanifestctx(object):
1705 def __init__(self, manifestlog):
1702 def __init__(self, manifestlog):
1706 self._manifestlog = manifestlog
1703 self._manifestlog = manifestlog
1707 self._manifestdict = manifestdict()
1704 self._manifestdict = manifestdict()
1708
1705
1709 def _storage(self):
1706 def _storage(self):
1710 return self._manifestlog.getstorage(b'')
1707 return self._manifestlog.getstorage(b'')
1711
1708
1712 def new(self):
1709 def new(self):
1713 return memmanifestctx(self._manifestlog)
1710 return memmanifestctx(self._manifestlog)
1714
1711
1715 def copy(self):
1712 def copy(self):
1716 memmf = memmanifestctx(self._manifestlog)
1713 memmf = memmanifestctx(self._manifestlog)
1717 memmf._manifestdict = self.read().copy()
1714 memmf._manifestdict = self.read().copy()
1718 return memmf
1715 return memmf
1719
1716
1720 def read(self):
1717 def read(self):
1721 return self._manifestdict
1718 return self._manifestdict
1722
1719
1723 def write(self, transaction, link, p1, p2, added, removed, match=None):
1720 def write(self, transaction, link, p1, p2, added, removed, match=None):
1724 return self._storage().add(self._manifestdict, transaction, link,
1721 return self._storage().add(self._manifestdict, transaction, link,
1725 p1, p2, added, removed, match=match)
1722 p1, p2, added, removed, match=match)
1726
1723
1727 @interfaceutil.implementer(repository.imanifestrevisionstored)
1724 @interfaceutil.implementer(repository.imanifestrevisionstored)
1728 class manifestctx(object):
1725 class manifestctx(object):
1729 """A class representing a single revision of a manifest, including its
1726 """A class representing a single revision of a manifest, including its
1730 contents, its parent revs, and its linkrev.
1727 contents, its parent revs, and its linkrev.
1731 """
1728 """
1732 def __init__(self, manifestlog, node):
1729 def __init__(self, manifestlog, node):
1733 self._manifestlog = manifestlog
1730 self._manifestlog = manifestlog
1734 self._data = None
1731 self._data = None
1735
1732
1736 self._node = node
1733 self._node = node
1737
1734
1738 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1735 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1739 # but let's add it later when something needs it and we can load it
1736 # but let's add it later when something needs it and we can load it
1740 # lazily.
1737 # lazily.
1741 #self.p1, self.p2 = store.parents(node)
1738 #self.p1, self.p2 = store.parents(node)
1742 #rev = store.rev(node)
1739 #rev = store.rev(node)
1743 #self.linkrev = store.linkrev(rev)
1740 #self.linkrev = store.linkrev(rev)
1744
1741
1745 def _storage(self):
1742 def _storage(self):
1746 return self._manifestlog.getstorage(b'')
1743 return self._manifestlog.getstorage(b'')
1747
1744
1748 def node(self):
1745 def node(self):
1749 return self._node
1746 return self._node
1750
1747
1751 def new(self):
1748 def new(self):
1752 return memmanifestctx(self._manifestlog)
1749 return memmanifestctx(self._manifestlog)
1753
1750
1754 def copy(self):
1751 def copy(self):
1755 memmf = memmanifestctx(self._manifestlog)
1752 memmf = memmanifestctx(self._manifestlog)
1756 memmf._manifestdict = self.read().copy()
1753 memmf._manifestdict = self.read().copy()
1757 return memmf
1754 return memmf
1758
1755
1759 @propertycache
1756 @propertycache
1760 def parents(self):
1757 def parents(self):
1761 return self._storage().parents(self._node)
1758 return self._storage().parents(self._node)
1762
1759
1763 def read(self):
1760 def read(self):
1764 if self._data is None:
1761 if self._data is None:
1765 if self._node == nullid:
1762 if self._node == nullid:
1766 self._data = manifestdict()
1763 self._data = manifestdict()
1767 else:
1764 else:
1768 store = self._storage()
1765 store = self._storage()
1769 if self._node in store.fulltextcache:
1766 if self._node in store.fulltextcache:
1770 text = pycompat.bytestr(store.fulltextcache[self._node])
1767 text = pycompat.bytestr(store.fulltextcache[self._node])
1771 else:
1768 else:
1772 text = store.revision(self._node)
1769 text = store.revision(self._node)
1773 arraytext = bytearray(text)
1770 arraytext = bytearray(text)
1774 store.fulltextcache[self._node] = arraytext
1771 store.fulltextcache[self._node] = arraytext
1775 self._data = manifestdict(text)
1772 self._data = manifestdict(text)
1776 return self._data
1773 return self._data
1777
1774
1778 def readfast(self, shallow=False):
1775 def readfast(self, shallow=False):
1779 '''Calls either readdelta or read, based on which would be less work.
1776 '''Calls either readdelta or read, based on which would be less work.
1780 readdelta is called if the delta is against the p1, and therefore can be
1777 readdelta is called if the delta is against the p1, and therefore can be
1781 read quickly.
1778 read quickly.
1782
1779
1783 If `shallow` is True, nothing changes since this is a flat manifest.
1780 If `shallow` is True, nothing changes since this is a flat manifest.
1784 '''
1781 '''
1785 store = self._storage()
1782 store = self._storage()
1786 r = store.rev(self._node)
1783 r = store.rev(self._node)
1787 deltaparent = store.deltaparent(r)
1784 deltaparent = store.deltaparent(r)
1788 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
1785 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
1789 return self.readdelta()
1786 return self.readdelta()
1790 return self.read()
1787 return self.read()
1791
1788
1792 def readdelta(self, shallow=False):
1789 def readdelta(self, shallow=False):
1793 '''Returns a manifest containing just the entries that are present
1790 '''Returns a manifest containing just the entries that are present
1794 in this manifest, but not in its p1 manifest. This is efficient to read
1791 in this manifest, but not in its p1 manifest. This is efficient to read
1795 if the revlog delta is already p1.
1792 if the revlog delta is already p1.
1796
1793
1797 Changing the value of `shallow` has no effect on flat manifests.
1794 Changing the value of `shallow` has no effect on flat manifests.
1798 '''
1795 '''
1799 store = self._storage()
1796 store = self._storage()
1800 r = store.rev(self._node)
1797 r = store.rev(self._node)
1801 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1798 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1802 return manifestdict(d)
1799 return manifestdict(d)
1803
1800
1804 def find(self, key):
1801 def find(self, key):
1805 return self.read().find(key)
1802 return self.read().find(key)
1806
1803
1807 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1804 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1808 class memtreemanifestctx(object):
1805 class memtreemanifestctx(object):
1809 def __init__(self, manifestlog, dir=''):
1806 def __init__(self, manifestlog, dir=''):
1810 self._manifestlog = manifestlog
1807 self._manifestlog = manifestlog
1811 self._dir = dir
1808 self._dir = dir
1812 self._treemanifest = treemanifest()
1809 self._treemanifest = treemanifest()
1813
1810
1814 def _storage(self):
1811 def _storage(self):
1815 return self._manifestlog.getstorage(b'')
1812 return self._manifestlog.getstorage(b'')
1816
1813
1817 def new(self, dir=''):
1814 def new(self, dir=''):
1818 return memtreemanifestctx(self._manifestlog, dir=dir)
1815 return memtreemanifestctx(self._manifestlog, dir=dir)
1819
1816
1820 def copy(self):
1817 def copy(self):
1821 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1818 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1822 memmf._treemanifest = self._treemanifest.copy()
1819 memmf._treemanifest = self._treemanifest.copy()
1823 return memmf
1820 return memmf
1824
1821
1825 def read(self):
1822 def read(self):
1826 return self._treemanifest
1823 return self._treemanifest
1827
1824
1828 def write(self, transaction, link, p1, p2, added, removed, match=None):
1825 def write(self, transaction, link, p1, p2, added, removed, match=None):
1829 def readtree(dir, node):
1826 def readtree(dir, node):
1830 return self._manifestlog.get(dir, node).read()
1827 return self._manifestlog.get(dir, node).read()
1831 return self._storage().add(self._treemanifest, transaction, link,
1828 return self._storage().add(self._treemanifest, transaction, link,
1832 p1, p2, added, removed, readtree=readtree,
1829 p1, p2, added, removed, readtree=readtree,
1833 match=match)
1830 match=match)
1834
1831
1835 @interfaceutil.implementer(repository.imanifestrevisionstored)
1832 @interfaceutil.implementer(repository.imanifestrevisionstored)
1836 class treemanifestctx(object):
1833 class treemanifestctx(object):
1837 def __init__(self, manifestlog, dir, node):
1834 def __init__(self, manifestlog, dir, node):
1838 self._manifestlog = manifestlog
1835 self._manifestlog = manifestlog
1839 self._dir = dir
1836 self._dir = dir
1840 self._data = None
1837 self._data = None
1841
1838
1842 self._node = node
1839 self._node = node
1843
1840
1844 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1841 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1845 # we can instantiate treemanifestctx objects for directories we don't
1842 # we can instantiate treemanifestctx objects for directories we don't
1846 # have on disk.
1843 # have on disk.
1847 #self.p1, self.p2 = store.parents(node)
1844 #self.p1, self.p2 = store.parents(node)
1848 #rev = store.rev(node)
1845 #rev = store.rev(node)
1849 #self.linkrev = store.linkrev(rev)
1846 #self.linkrev = store.linkrev(rev)
1850
1847
1851 def _storage(self):
1848 def _storage(self):
1852 narrowmatch = self._manifestlog._narrowmatch
1849 narrowmatch = self._manifestlog._narrowmatch
1853 if not narrowmatch.always():
1850 if not narrowmatch.always():
1854 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1851 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1855 return excludedmanifestrevlog(self._dir)
1852 return excludedmanifestrevlog(self._dir)
1856 return self._manifestlog.getstorage(self._dir)
1853 return self._manifestlog.getstorage(self._dir)
1857
1854
1858 def read(self):
1855 def read(self):
1859 if self._data is None:
1856 if self._data is None:
1860 store = self._storage()
1857 store = self._storage()
1861 if self._node == nullid:
1858 if self._node == nullid:
1862 self._data = treemanifest()
1859 self._data = treemanifest()
1863 # TODO accessing non-public API
1860 # TODO accessing non-public API
1864 elif store._treeondisk:
1861 elif store._treeondisk:
1865 m = treemanifest(dir=self._dir)
1862 m = treemanifest(dir=self._dir)
1866 def gettext():
1863 def gettext():
1867 return store.revision(self._node)
1864 return store.revision(self._node)
1868 def readsubtree(dir, subm):
1865 def readsubtree(dir, subm):
1869 # Set verify to False since we need to be able to create
1866 # Set verify to False since we need to be able to create
1870 # subtrees for trees that don't exist on disk.
1867 # subtrees for trees that don't exist on disk.
1871 return self._manifestlog.get(dir, subm, verify=False).read()
1868 return self._manifestlog.get(dir, subm, verify=False).read()
1872 m.read(gettext, readsubtree)
1869 m.read(gettext, readsubtree)
1873 m.setnode(self._node)
1870 m.setnode(self._node)
1874 self._data = m
1871 self._data = m
1875 else:
1872 else:
1876 if self._node in store.fulltextcache:
1873 if self._node in store.fulltextcache:
1877 text = pycompat.bytestr(store.fulltextcache[self._node])
1874 text = pycompat.bytestr(store.fulltextcache[self._node])
1878 else:
1875 else:
1879 text = store.revision(self._node)
1876 text = store.revision(self._node)
1880 arraytext = bytearray(text)
1877 arraytext = bytearray(text)
1881 store.fulltextcache[self._node] = arraytext
1878 store.fulltextcache[self._node] = arraytext
1882 self._data = treemanifest(dir=self._dir, text=text)
1879 self._data = treemanifest(dir=self._dir, text=text)
1883
1880
1884 return self._data
1881 return self._data
1885
1882
1886 def node(self):
1883 def node(self):
1887 return self._node
1884 return self._node
1888
1885
1889 def new(self, dir=''):
1886 def new(self, dir=''):
1890 return memtreemanifestctx(self._manifestlog, dir=dir)
1887 return memtreemanifestctx(self._manifestlog, dir=dir)
1891
1888
1892 def copy(self):
1889 def copy(self):
1893 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1890 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1894 memmf._treemanifest = self.read().copy()
1891 memmf._treemanifest = self.read().copy()
1895 return memmf
1892 return memmf
1896
1893
1897 @propertycache
1894 @propertycache
1898 def parents(self):
1895 def parents(self):
1899 return self._storage().parents(self._node)
1896 return self._storage().parents(self._node)
1900
1897
1901 def readdelta(self, shallow=False):
1898 def readdelta(self, shallow=False):
1902 '''Returns a manifest containing just the entries that are present
1899 '''Returns a manifest containing just the entries that are present
1903 in this manifest, but not in its p1 manifest. This is efficient to read
1900 in this manifest, but not in its p1 manifest. This is efficient to read
1904 if the revlog delta is already p1.
1901 if the revlog delta is already p1.
1905
1902
1906 If `shallow` is True, this will read the delta for this directory,
1903 If `shallow` is True, this will read the delta for this directory,
1907 without recursively reading subdirectory manifests. Instead, any
1904 without recursively reading subdirectory manifests. Instead, any
1908 subdirectory entry will be reported as it appears in the manifest, i.e.
1905 subdirectory entry will be reported as it appears in the manifest, i.e.
1909 the subdirectory will be reported among files and distinguished only by
1906 the subdirectory will be reported among files and distinguished only by
1910 its 't' flag.
1907 its 't' flag.
1911 '''
1908 '''
1912 store = self._storage()
1909 store = self._storage()
1913 if shallow:
1910 if shallow:
1914 r = store.rev(self._node)
1911 r = store.rev(self._node)
1915 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1912 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1916 return manifestdict(d)
1913 return manifestdict(d)
1917 else:
1914 else:
1918 # Need to perform a slow delta
1915 # Need to perform a slow delta
1919 r0 = store.deltaparent(store.rev(self._node))
1916 r0 = store.deltaparent(store.rev(self._node))
1920 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
1917 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
1921 m1 = self.read()
1918 m1 = self.read()
1922 md = treemanifest(dir=self._dir)
1919 md = treemanifest(dir=self._dir)
1923 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1920 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1924 if n1:
1921 if n1:
1925 md[f] = n1
1922 md[f] = n1
1926 if fl1:
1923 if fl1:
1927 md.setflag(f, fl1)
1924 md.setflag(f, fl1)
1928 return md
1925 return md
1929
1926
1930 def readfast(self, shallow=False):
1927 def readfast(self, shallow=False):
1931 '''Calls either readdelta or read, based on which would be less work.
1928 '''Calls either readdelta or read, based on which would be less work.
1932 readdelta is called if the delta is against the p1, and therefore can be
1929 readdelta is called if the delta is against the p1, and therefore can be
1933 read quickly.
1930 read quickly.
1934
1931
1935 If `shallow` is True, it only returns the entries from this manifest,
1932 If `shallow` is True, it only returns the entries from this manifest,
1936 and not any submanifests.
1933 and not any submanifests.
1937 '''
1934 '''
1938 store = self._storage()
1935 store = self._storage()
1939 r = store.rev(self._node)
1936 r = store.rev(self._node)
1940 deltaparent = store.deltaparent(r)
1937 deltaparent = store.deltaparent(r)
1941 if (deltaparent != nullrev and
1938 if (deltaparent != nullrev and
1942 deltaparent in store.parentrevs(r)):
1939 deltaparent in store.parentrevs(r)):
1943 return self.readdelta(shallow=shallow)
1940 return self.readdelta(shallow=shallow)
1944
1941
1945 if shallow:
1942 if shallow:
1946 return manifestdict(store.revision(self._node))
1943 return manifestdict(store.revision(self._node))
1947 else:
1944 else:
1948 return self.read()
1945 return self.read()
1949
1946
1950 def find(self, key):
1947 def find(self, key):
1951 return self.read().find(key)
1948 return self.read().find(key)
1952
1949
1953 class excludeddir(treemanifest):
1950 class excludeddir(treemanifest):
1954 """Stand-in for a directory that is excluded from the repository.
1951 """Stand-in for a directory that is excluded from the repository.
1955
1952
1956 With narrowing active on a repository that uses treemanifests,
1953 With narrowing active on a repository that uses treemanifests,
1957 some of the directory revlogs will be excluded from the resulting
1954 some of the directory revlogs will be excluded from the resulting
1958 clone. This is a huge storage win for clients, but means we need
1955 clone. This is a huge storage win for clients, but means we need
1959 some sort of pseudo-manifest to surface to internals so we can
1956 some sort of pseudo-manifest to surface to internals so we can
1960 detect a merge conflict outside the narrowspec. That's what this
1957 detect a merge conflict outside the narrowspec. That's what this
1961 class is: it stands in for a directory whose node is known, but
1958 class is: it stands in for a directory whose node is known, but
1962 whose contents are unknown.
1959 whose contents are unknown.
1963 """
1960 """
1964 def __init__(self, dir, node):
1961 def __init__(self, dir, node):
1965 super(excludeddir, self).__init__(dir)
1962 super(excludeddir, self).__init__(dir)
1966 self._node = node
1963 self._node = node
1967 # Add an empty file, which will be included by iterators and such,
1964 # Add an empty file, which will be included by iterators and such,
1968 # appearing as the directory itself (i.e. something like "dir/")
1965 # appearing as the directory itself (i.e. something like "dir/")
1969 self._files[''] = node
1966 self._files[''] = node
1970 self._flags[''] = 't'
1967 self._flags[''] = 't'
1971
1968
1972 # Manifests outside the narrowspec should never be modified, so avoid
1969 # Manifests outside the narrowspec should never be modified, so avoid
1973 # copying. This makes a noticeable difference when there are very many
1970 # copying. This makes a noticeable difference when there are very many
1974 # directories outside the narrowspec. Also, it makes sense for the copy to
1971 # directories outside the narrowspec. Also, it makes sense for the copy to
1975 # be of the same type as the original, which would not happen with the
1972 # be of the same type as the original, which would not happen with the
1976 # super type's copy().
1973 # super type's copy().
1977 def copy(self):
1974 def copy(self):
1978 return self
1975 return self
1979
1976
1980 class excludeddirmanifestctx(treemanifestctx):
1977 class excludeddirmanifestctx(treemanifestctx):
1981 """context wrapper for excludeddir - see that docstring for rationale"""
1978 """context wrapper for excludeddir - see that docstring for rationale"""
1982 def __init__(self, dir, node):
1979 def __init__(self, dir, node):
1983 self._dir = dir
1980 self._dir = dir
1984 self._node = node
1981 self._node = node
1985
1982
1986 def read(self):
1983 def read(self):
1987 return excludeddir(self._dir, self._node)
1984 return excludeddir(self._dir, self._node)
1988
1985
1989 def write(self, *args):
1986 def write(self, *args):
1990 raise error.ProgrammingError(
1987 raise error.ProgrammingError(
1991 'attempt to write manifest from excluded dir %s' % self._dir)
1988 'attempt to write manifest from excluded dir %s' % self._dir)
1992
1989
1993 class excludedmanifestrevlog(manifestrevlog):
1990 class excludedmanifestrevlog(manifestrevlog):
1994 """Stand-in for excluded treemanifest revlogs.
1991 """Stand-in for excluded treemanifest revlogs.
1995
1992
1996 When narrowing is active on a treemanifest repository, we'll have
1993 When narrowing is active on a treemanifest repository, we'll have
1997 references to directories we can't see due to the revlog being
1994 references to directories we can't see due to the revlog being
1998 skipped. This class exists to conform to the manifestrevlog
1995 skipped. This class exists to conform to the manifestrevlog
1999 interface for those directories and proactively prevent writes to
1996 interface for those directories and proactively prevent writes to
2000 outside the narrowspec.
1997 outside the narrowspec.
2001 """
1998 """
2002
1999
2003 def __init__(self, dir):
2000 def __init__(self, dir):
2004 self._dir = dir
2001 self._dir = dir
2005
2002
2006 def __len__(self):
2003 def __len__(self):
2007 raise error.ProgrammingError(
2004 raise error.ProgrammingError(
2008 'attempt to get length of excluded dir %s' % self._dir)
2005 'attempt to get length of excluded dir %s' % self._dir)
2009
2006
2010 def rev(self, node):
2007 def rev(self, node):
2011 raise error.ProgrammingError(
2008 raise error.ProgrammingError(
2012 'attempt to get rev from excluded dir %s' % self._dir)
2009 'attempt to get rev from excluded dir %s' % self._dir)
2013
2010
2014 def linkrev(self, node):
2011 def linkrev(self, node):
2015 raise error.ProgrammingError(
2012 raise error.ProgrammingError(
2016 'attempt to get linkrev from excluded dir %s' % self._dir)
2013 'attempt to get linkrev from excluded dir %s' % self._dir)
2017
2014
2018 def node(self, rev):
2015 def node(self, rev):
2019 raise error.ProgrammingError(
2016 raise error.ProgrammingError(
2020 'attempt to get node from excluded dir %s' % self._dir)
2017 'attempt to get node from excluded dir %s' % self._dir)
2021
2018
2022 def add(self, *args, **kwargs):
2019 def add(self, *args, **kwargs):
2023 # We should never write entries in dirlogs outside the narrow clone.
2020 # We should never write entries in dirlogs outside the narrow clone.
2024 # However, the method still gets called from writesubtree() in
2021 # However, the method still gets called from writesubtree() in
2025 # _addtree(), so we need to handle it. We should possibly make that
2022 # _addtree(), so we need to handle it. We should possibly make that
2026 # avoid calling add() with a clean manifest (_dirty is always False
2023 # avoid calling add() with a clean manifest (_dirty is always False
2027 # in excludeddir instances).
2024 # in excludeddir instances).
2028 pass
2025 pass
@@ -1,1701 +1,1640 b''
1 # repository.py - Interfaces and base classes for repositories and peers.
1 # repository.py - Interfaces and base classes for repositories and peers.
2 #
2 #
3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.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 from .i18n import _
10 from .i18n import _
11 from . import (
11 from . import (
12 error,
12 error,
13 )
13 )
14 from .utils import (
14 from .utils import (
15 interfaceutil,
15 interfaceutil,
16 )
16 )
17
17
18 # When narrowing is finalized and no longer subject to format changes,
18 # When narrowing is finalized and no longer subject to format changes,
19 # we should move this to just "narrow" or similar.
19 # we should move this to just "narrow" or similar.
20 NARROW_REQUIREMENT = 'narrowhg-experimental'
20 NARROW_REQUIREMENT = 'narrowhg-experimental'
21
21
22 # Local repository feature string.
22 # Local repository feature string.
23
23
24 # Revlogs are being used for file storage.
24 # Revlogs are being used for file storage.
25 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
25 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
26 # The storage part of the repository is shared from an external source.
26 # The storage part of the repository is shared from an external source.
27 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
27 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
28 # LFS supported for backing file storage.
28 # LFS supported for backing file storage.
29 REPO_FEATURE_LFS = b'lfs'
29 REPO_FEATURE_LFS = b'lfs'
30
30
31 class ipeerconnection(interfaceutil.Interface):
31 class ipeerconnection(interfaceutil.Interface):
32 """Represents a "connection" to a repository.
32 """Represents a "connection" to a repository.
33
33
34 This is the base interface for representing a connection to a repository.
34 This is the base interface for representing a connection to a repository.
35 It holds basic properties and methods applicable to all peer types.
35 It holds basic properties and methods applicable to all peer types.
36
36
37 This is not a complete interface definition and should not be used
37 This is not a complete interface definition and should not be used
38 outside of this module.
38 outside of this module.
39 """
39 """
40 ui = interfaceutil.Attribute("""ui.ui instance""")
40 ui = interfaceutil.Attribute("""ui.ui instance""")
41
41
42 def url():
42 def url():
43 """Returns a URL string representing this peer.
43 """Returns a URL string representing this peer.
44
44
45 Currently, implementations expose the raw URL used to construct the
45 Currently, implementations expose the raw URL used to construct the
46 instance. It may contain credentials as part of the URL. The
46 instance. It may contain credentials as part of the URL. The
47 expectations of the value aren't well-defined and this could lead to
47 expectations of the value aren't well-defined and this could lead to
48 data leakage.
48 data leakage.
49
49
50 TODO audit/clean consumers and more clearly define the contents of this
50 TODO audit/clean consumers and more clearly define the contents of this
51 value.
51 value.
52 """
52 """
53
53
54 def local():
54 def local():
55 """Returns a local repository instance.
55 """Returns a local repository instance.
56
56
57 If the peer represents a local repository, returns an object that
57 If the peer represents a local repository, returns an object that
58 can be used to interface with it. Otherwise returns ``None``.
58 can be used to interface with it. Otherwise returns ``None``.
59 """
59 """
60
60
61 def peer():
61 def peer():
62 """Returns an object conforming to this interface.
62 """Returns an object conforming to this interface.
63
63
64 Most implementations will ``return self``.
64 Most implementations will ``return self``.
65 """
65 """
66
66
67 def canpush():
67 def canpush():
68 """Returns a boolean indicating if this peer can be pushed to."""
68 """Returns a boolean indicating if this peer can be pushed to."""
69
69
70 def close():
70 def close():
71 """Close the connection to this peer.
71 """Close the connection to this peer.
72
72
73 This is called when the peer will no longer be used. Resources
73 This is called when the peer will no longer be used. Resources
74 associated with the peer should be cleaned up.
74 associated with the peer should be cleaned up.
75 """
75 """
76
76
77 class ipeercapabilities(interfaceutil.Interface):
77 class ipeercapabilities(interfaceutil.Interface):
78 """Peer sub-interface related to capabilities."""
78 """Peer sub-interface related to capabilities."""
79
79
80 def capable(name):
80 def capable(name):
81 """Determine support for a named capability.
81 """Determine support for a named capability.
82
82
83 Returns ``False`` if capability not supported.
83 Returns ``False`` if capability not supported.
84
84
85 Returns ``True`` if boolean capability is supported. Returns a string
85 Returns ``True`` if boolean capability is supported. Returns a string
86 if capability support is non-boolean.
86 if capability support is non-boolean.
87
87
88 Capability strings may or may not map to wire protocol capabilities.
88 Capability strings may or may not map to wire protocol capabilities.
89 """
89 """
90
90
91 def requirecap(name, purpose):
91 def requirecap(name, purpose):
92 """Require a capability to be present.
92 """Require a capability to be present.
93
93
94 Raises a ``CapabilityError`` if the capability isn't present.
94 Raises a ``CapabilityError`` if the capability isn't present.
95 """
95 """
96
96
97 class ipeercommands(interfaceutil.Interface):
97 class ipeercommands(interfaceutil.Interface):
98 """Client-side interface for communicating over the wire protocol.
98 """Client-side interface for communicating over the wire protocol.
99
99
100 This interface is used as a gateway to the Mercurial wire protocol.
100 This interface is used as a gateway to the Mercurial wire protocol.
101 methods commonly call wire protocol commands of the same name.
101 methods commonly call wire protocol commands of the same name.
102 """
102 """
103
103
104 def branchmap():
104 def branchmap():
105 """Obtain heads in named branches.
105 """Obtain heads in named branches.
106
106
107 Returns a dict mapping branch name to an iterable of nodes that are
107 Returns a dict mapping branch name to an iterable of nodes that are
108 heads on that branch.
108 heads on that branch.
109 """
109 """
110
110
111 def capabilities():
111 def capabilities():
112 """Obtain capabilities of the peer.
112 """Obtain capabilities of the peer.
113
113
114 Returns a set of string capabilities.
114 Returns a set of string capabilities.
115 """
115 """
116
116
117 def clonebundles():
117 def clonebundles():
118 """Obtains the clone bundles manifest for the repo.
118 """Obtains the clone bundles manifest for the repo.
119
119
120 Returns the manifest as unparsed bytes.
120 Returns the manifest as unparsed bytes.
121 """
121 """
122
122
123 def debugwireargs(one, two, three=None, four=None, five=None):
123 def debugwireargs(one, two, three=None, four=None, five=None):
124 """Used to facilitate debugging of arguments passed over the wire."""
124 """Used to facilitate debugging of arguments passed over the wire."""
125
125
126 def getbundle(source, **kwargs):
126 def getbundle(source, **kwargs):
127 """Obtain remote repository data as a bundle.
127 """Obtain remote repository data as a bundle.
128
128
129 This command is how the bulk of repository data is transferred from
129 This command is how the bulk of repository data is transferred from
130 the peer to the local repository
130 the peer to the local repository
131
131
132 Returns a generator of bundle data.
132 Returns a generator of bundle data.
133 """
133 """
134
134
135 def heads():
135 def heads():
136 """Determine all known head revisions in the peer.
136 """Determine all known head revisions in the peer.
137
137
138 Returns an iterable of binary nodes.
138 Returns an iterable of binary nodes.
139 """
139 """
140
140
141 def known(nodes):
141 def known(nodes):
142 """Determine whether multiple nodes are known.
142 """Determine whether multiple nodes are known.
143
143
144 Accepts an iterable of nodes whose presence to check for.
144 Accepts an iterable of nodes whose presence to check for.
145
145
146 Returns an iterable of booleans indicating of the corresponding node
146 Returns an iterable of booleans indicating of the corresponding node
147 at that index is known to the peer.
147 at that index is known to the peer.
148 """
148 """
149
149
150 def listkeys(namespace):
150 def listkeys(namespace):
151 """Obtain all keys in a pushkey namespace.
151 """Obtain all keys in a pushkey namespace.
152
152
153 Returns an iterable of key names.
153 Returns an iterable of key names.
154 """
154 """
155
155
156 def lookup(key):
156 def lookup(key):
157 """Resolve a value to a known revision.
157 """Resolve a value to a known revision.
158
158
159 Returns a binary node of the resolved revision on success.
159 Returns a binary node of the resolved revision on success.
160 """
160 """
161
161
162 def pushkey(namespace, key, old, new):
162 def pushkey(namespace, key, old, new):
163 """Set a value using the ``pushkey`` protocol.
163 """Set a value using the ``pushkey`` protocol.
164
164
165 Arguments correspond to the pushkey namespace and key to operate on and
165 Arguments correspond to the pushkey namespace and key to operate on and
166 the old and new values for that key.
166 the old and new values for that key.
167
167
168 Returns a string with the peer result. The value inside varies by the
168 Returns a string with the peer result. The value inside varies by the
169 namespace.
169 namespace.
170 """
170 """
171
171
172 def stream_out():
172 def stream_out():
173 """Obtain streaming clone data.
173 """Obtain streaming clone data.
174
174
175 Successful result should be a generator of data chunks.
175 Successful result should be a generator of data chunks.
176 """
176 """
177
177
178 def unbundle(bundle, heads, url):
178 def unbundle(bundle, heads, url):
179 """Transfer repository data to the peer.
179 """Transfer repository data to the peer.
180
180
181 This is how the bulk of data during a push is transferred.
181 This is how the bulk of data during a push is transferred.
182
182
183 Returns the integer number of heads added to the peer.
183 Returns the integer number of heads added to the peer.
184 """
184 """
185
185
186 class ipeerlegacycommands(interfaceutil.Interface):
186 class ipeerlegacycommands(interfaceutil.Interface):
187 """Interface for implementing support for legacy wire protocol commands.
187 """Interface for implementing support for legacy wire protocol commands.
188
188
189 Wire protocol commands transition to legacy status when they are no longer
189 Wire protocol commands transition to legacy status when they are no longer
190 used by modern clients. To facilitate identifying which commands are
190 used by modern clients. To facilitate identifying which commands are
191 legacy, the interfaces are split.
191 legacy, the interfaces are split.
192 """
192 """
193
193
194 def between(pairs):
194 def between(pairs):
195 """Obtain nodes between pairs of nodes.
195 """Obtain nodes between pairs of nodes.
196
196
197 ``pairs`` is an iterable of node pairs.
197 ``pairs`` is an iterable of node pairs.
198
198
199 Returns an iterable of iterables of nodes corresponding to each
199 Returns an iterable of iterables of nodes corresponding to each
200 requested pair.
200 requested pair.
201 """
201 """
202
202
203 def branches(nodes):
203 def branches(nodes):
204 """Obtain ancestor changesets of specific nodes back to a branch point.
204 """Obtain ancestor changesets of specific nodes back to a branch point.
205
205
206 For each requested node, the peer finds the first ancestor node that is
206 For each requested node, the peer finds the first ancestor node that is
207 a DAG root or is a merge.
207 a DAG root or is a merge.
208
208
209 Returns an iterable of iterables with the resolved values for each node.
209 Returns an iterable of iterables with the resolved values for each node.
210 """
210 """
211
211
212 def changegroup(nodes, source):
212 def changegroup(nodes, source):
213 """Obtain a changegroup with data for descendants of specified nodes."""
213 """Obtain a changegroup with data for descendants of specified nodes."""
214
214
215 def changegroupsubset(bases, heads, source):
215 def changegroupsubset(bases, heads, source):
216 pass
216 pass
217
217
218 class ipeercommandexecutor(interfaceutil.Interface):
218 class ipeercommandexecutor(interfaceutil.Interface):
219 """Represents a mechanism to execute remote commands.
219 """Represents a mechanism to execute remote commands.
220
220
221 This is the primary interface for requesting that wire protocol commands
221 This is the primary interface for requesting that wire protocol commands
222 be executed. Instances of this interface are active in a context manager
222 be executed. Instances of this interface are active in a context manager
223 and have a well-defined lifetime. When the context manager exits, all
223 and have a well-defined lifetime. When the context manager exits, all
224 outstanding requests are waited on.
224 outstanding requests are waited on.
225 """
225 """
226
226
227 def callcommand(name, args):
227 def callcommand(name, args):
228 """Request that a named command be executed.
228 """Request that a named command be executed.
229
229
230 Receives the command name and a dictionary of command arguments.
230 Receives the command name and a dictionary of command arguments.
231
231
232 Returns a ``concurrent.futures.Future`` that will resolve to the
232 Returns a ``concurrent.futures.Future`` that will resolve to the
233 result of that command request. That exact value is left up to
233 result of that command request. That exact value is left up to
234 the implementation and possibly varies by command.
234 the implementation and possibly varies by command.
235
235
236 Not all commands can coexist with other commands in an executor
236 Not all commands can coexist with other commands in an executor
237 instance: it depends on the underlying wire protocol transport being
237 instance: it depends on the underlying wire protocol transport being
238 used and the command itself.
238 used and the command itself.
239
239
240 Implementations MAY call ``sendcommands()`` automatically if the
240 Implementations MAY call ``sendcommands()`` automatically if the
241 requested command can not coexist with other commands in this executor.
241 requested command can not coexist with other commands in this executor.
242
242
243 Implementations MAY call ``sendcommands()`` automatically when the
243 Implementations MAY call ``sendcommands()`` automatically when the
244 future's ``result()`` is called. So, consumers using multiple
244 future's ``result()`` is called. So, consumers using multiple
245 commands with an executor MUST ensure that ``result()`` is not called
245 commands with an executor MUST ensure that ``result()`` is not called
246 until all command requests have been issued.
246 until all command requests have been issued.
247 """
247 """
248
248
249 def sendcommands():
249 def sendcommands():
250 """Trigger submission of queued command requests.
250 """Trigger submission of queued command requests.
251
251
252 Not all transports submit commands as soon as they are requested to
252 Not all transports submit commands as soon as they are requested to
253 run. When called, this method forces queued command requests to be
253 run. When called, this method forces queued command requests to be
254 issued. It will no-op if all commands have already been sent.
254 issued. It will no-op if all commands have already been sent.
255
255
256 When called, no more new commands may be issued with this executor.
256 When called, no more new commands may be issued with this executor.
257 """
257 """
258
258
259 def close():
259 def close():
260 """Signal that this command request is finished.
260 """Signal that this command request is finished.
261
261
262 When called, no more new commands may be issued. All outstanding
262 When called, no more new commands may be issued. All outstanding
263 commands that have previously been issued are waited on before
263 commands that have previously been issued are waited on before
264 returning. This not only includes waiting for the futures to resolve,
264 returning. This not only includes waiting for the futures to resolve,
265 but also waiting for all response data to arrive. In other words,
265 but also waiting for all response data to arrive. In other words,
266 calling this waits for all on-wire state for issued command requests
266 calling this waits for all on-wire state for issued command requests
267 to finish.
267 to finish.
268
268
269 When used as a context manager, this method is called when exiting the
269 When used as a context manager, this method is called when exiting the
270 context manager.
270 context manager.
271
271
272 This method may call ``sendcommands()`` if there are buffered commands.
272 This method may call ``sendcommands()`` if there are buffered commands.
273 """
273 """
274
274
275 class ipeerrequests(interfaceutil.Interface):
275 class ipeerrequests(interfaceutil.Interface):
276 """Interface for executing commands on a peer."""
276 """Interface for executing commands on a peer."""
277
277
278 def commandexecutor():
278 def commandexecutor():
279 """A context manager that resolves to an ipeercommandexecutor.
279 """A context manager that resolves to an ipeercommandexecutor.
280
280
281 The object this resolves to can be used to issue command requests
281 The object this resolves to can be used to issue command requests
282 to the peer.
282 to the peer.
283
283
284 Callers should call its ``callcommand`` method to issue command
284 Callers should call its ``callcommand`` method to issue command
285 requests.
285 requests.
286
286
287 A new executor should be obtained for each distinct set of commands
287 A new executor should be obtained for each distinct set of commands
288 (possibly just a single command) that the consumer wants to execute
288 (possibly just a single command) that the consumer wants to execute
289 as part of a single operation or round trip. This is because some
289 as part of a single operation or round trip. This is because some
290 peers are half-duplex and/or don't support persistent connections.
290 peers are half-duplex and/or don't support persistent connections.
291 e.g. in the case of HTTP peers, commands sent to an executor represent
291 e.g. in the case of HTTP peers, commands sent to an executor represent
292 a single HTTP request. While some peers may support multiple command
292 a single HTTP request. While some peers may support multiple command
293 sends over the wire per executor, consumers need to code to the least
293 sends over the wire per executor, consumers need to code to the least
294 capable peer. So it should be assumed that command executors buffer
294 capable peer. So it should be assumed that command executors buffer
295 called commands until they are told to send them and that each
295 called commands until they are told to send them and that each
296 command executor could result in a new connection or wire-level request
296 command executor could result in a new connection or wire-level request
297 being issued.
297 being issued.
298 """
298 """
299
299
300 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
300 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
301 """Unified interface for peer repositories.
301 """Unified interface for peer repositories.
302
302
303 All peer instances must conform to this interface.
303 All peer instances must conform to this interface.
304 """
304 """
305
305
306 @interfaceutil.implementer(ipeerbase)
306 @interfaceutil.implementer(ipeerbase)
307 class peer(object):
307 class peer(object):
308 """Base class for peer repositories."""
308 """Base class for peer repositories."""
309
309
310 def capable(self, name):
310 def capable(self, name):
311 caps = self.capabilities()
311 caps = self.capabilities()
312 if name in caps:
312 if name in caps:
313 return True
313 return True
314
314
315 name = '%s=' % name
315 name = '%s=' % name
316 for cap in caps:
316 for cap in caps:
317 if cap.startswith(name):
317 if cap.startswith(name):
318 return cap[len(name):]
318 return cap[len(name):]
319
319
320 return False
320 return False
321
321
322 def requirecap(self, name, purpose):
322 def requirecap(self, name, purpose):
323 if self.capable(name):
323 if self.capable(name):
324 return
324 return
325
325
326 raise error.CapabilityError(
326 raise error.CapabilityError(
327 _('cannot %s; remote repository does not support the %r '
327 _('cannot %s; remote repository does not support the %r '
328 'capability') % (purpose, name))
328 'capability') % (purpose, name))
329
329
330 class iverifyproblem(interfaceutil.Interface):
330 class iverifyproblem(interfaceutil.Interface):
331 """Represents a problem with the integrity of the repository.
331 """Represents a problem with the integrity of the repository.
332
332
333 Instances of this interface are emitted to describe an integrity issue
333 Instances of this interface are emitted to describe an integrity issue
334 with a repository (e.g. corrupt storage, missing data, etc).
334 with a repository (e.g. corrupt storage, missing data, etc).
335
335
336 Instances are essentially messages associated with severity.
336 Instances are essentially messages associated with severity.
337 """
337 """
338 warning = interfaceutil.Attribute(
338 warning = interfaceutil.Attribute(
339 """Message indicating a non-fatal problem.""")
339 """Message indicating a non-fatal problem.""")
340
340
341 error = interfaceutil.Attribute(
341 error = interfaceutil.Attribute(
342 """Message indicating a fatal problem.""")
342 """Message indicating a fatal problem.""")
343
343
344 class irevisiondelta(interfaceutil.Interface):
344 class irevisiondelta(interfaceutil.Interface):
345 """Represents a delta between one revision and another.
345 """Represents a delta between one revision and another.
346
346
347 Instances convey enough information to allow a revision to be exchanged
347 Instances convey enough information to allow a revision to be exchanged
348 with another repository.
348 with another repository.
349
349
350 Instances represent the fulltext revision data or a delta against
350 Instances represent the fulltext revision data or a delta against
351 another revision. Therefore the ``revision`` and ``delta`` attributes
351 another revision. Therefore the ``revision`` and ``delta`` attributes
352 are mutually exclusive.
352 are mutually exclusive.
353
353
354 Typically used for changegroup generation.
354 Typically used for changegroup generation.
355 """
355 """
356
356
357 node = interfaceutil.Attribute(
357 node = interfaceutil.Attribute(
358 """20 byte node of this revision.""")
358 """20 byte node of this revision.""")
359
359
360 p1node = interfaceutil.Attribute(
360 p1node = interfaceutil.Attribute(
361 """20 byte node of 1st parent of this revision.""")
361 """20 byte node of 1st parent of this revision.""")
362
362
363 p2node = interfaceutil.Attribute(
363 p2node = interfaceutil.Attribute(
364 """20 byte node of 2nd parent of this revision.""")
364 """20 byte node of 2nd parent of this revision.""")
365
365
366 linknode = interfaceutil.Attribute(
366 linknode = interfaceutil.Attribute(
367 """20 byte node of the changelog revision this node is linked to.""")
367 """20 byte node of the changelog revision this node is linked to.""")
368
368
369 flags = interfaceutil.Attribute(
369 flags = interfaceutil.Attribute(
370 """2 bytes of integer flags that apply to this revision.""")
370 """2 bytes of integer flags that apply to this revision.""")
371
371
372 basenode = interfaceutil.Attribute(
372 basenode = interfaceutil.Attribute(
373 """20 byte node of the revision this data is a delta against.
373 """20 byte node of the revision this data is a delta against.
374
374
375 ``nullid`` indicates that the revision is a full revision and not
375 ``nullid`` indicates that the revision is a full revision and not
376 a delta.
376 a delta.
377 """)
377 """)
378
378
379 baserevisionsize = interfaceutil.Attribute(
379 baserevisionsize = interfaceutil.Attribute(
380 """Size of base revision this delta is against.
380 """Size of base revision this delta is against.
381
381
382 May be ``None`` if ``basenode`` is ``nullid``.
382 May be ``None`` if ``basenode`` is ``nullid``.
383 """)
383 """)
384
384
385 revision = interfaceutil.Attribute(
385 revision = interfaceutil.Attribute(
386 """Raw fulltext of revision data for this node.""")
386 """Raw fulltext of revision data for this node.""")
387
387
388 delta = interfaceutil.Attribute(
388 delta = interfaceutil.Attribute(
389 """Delta between ``basenode`` and ``node``.
389 """Delta between ``basenode`` and ``node``.
390
390
391 Stored in the bdiff delta format.
391 Stored in the bdiff delta format.
392 """)
392 """)
393
393
394 class irevisiondeltarequest(interfaceutil.Interface):
395 """Represents a request to generate an ``irevisiondelta``."""
396
397 node = interfaceutil.Attribute(
398 """20 byte node of revision being requested.""")
399
400 p1node = interfaceutil.Attribute(
401 """20 byte node of 1st parent of revision.""")
402
403 p2node = interfaceutil.Attribute(
404 """20 byte node of 2nd parent of revision.""")
405
406 linknode = interfaceutil.Attribute(
407 """20 byte node to store in ``linknode`` attribute.""")
408
409 basenode = interfaceutil.Attribute(
410 """Base revision that delta should be generated against.
411
412 If ``nullid``, the derived ``irevisiondelta`` should have its
413 ``revision`` field populated and no delta should be generated.
414
415 If ``None``, the delta may be generated against any revision that
416 is an ancestor of this revision. Or a full revision may be used.
417
418 If any other value, the delta should be produced against that
419 revision.
420 """)
421
422 ellipsis = interfaceutil.Attribute(
423 """Boolean on whether the ellipsis flag should be set.""")
424
425 class ifilerevisionssequence(interfaceutil.Interface):
394 class ifilerevisionssequence(interfaceutil.Interface):
426 """Contains index data for all revisions of a file.
395 """Contains index data for all revisions of a file.
427
396
428 Types implementing this behave like lists of tuples. The index
397 Types implementing this behave like lists of tuples. The index
429 in the list corresponds to the revision number. The values contain
398 in the list corresponds to the revision number. The values contain
430 index metadata.
399 index metadata.
431
400
432 The *null* revision (revision number -1) is always the last item
401 The *null* revision (revision number -1) is always the last item
433 in the index.
402 in the index.
434 """
403 """
435
404
436 def __len__():
405 def __len__():
437 """The total number of revisions."""
406 """The total number of revisions."""
438
407
439 def __getitem__(rev):
408 def __getitem__(rev):
440 """Returns the object having a specific revision number.
409 """Returns the object having a specific revision number.
441
410
442 Returns an 8-tuple with the following fields:
411 Returns an 8-tuple with the following fields:
443
412
444 offset+flags
413 offset+flags
445 Contains the offset and flags for the revision. 64-bit unsigned
414 Contains the offset and flags for the revision. 64-bit unsigned
446 integer where first 6 bytes are the offset and the next 2 bytes
415 integer where first 6 bytes are the offset and the next 2 bytes
447 are flags. The offset can be 0 if it is not used by the store.
416 are flags. The offset can be 0 if it is not used by the store.
448 compressed size
417 compressed size
449 Size of the revision data in the store. It can be 0 if it isn't
418 Size of the revision data in the store. It can be 0 if it isn't
450 needed by the store.
419 needed by the store.
451 uncompressed size
420 uncompressed size
452 Fulltext size. It can be 0 if it isn't needed by the store.
421 Fulltext size. It can be 0 if it isn't needed by the store.
453 base revision
422 base revision
454 Revision number of revision the delta for storage is encoded
423 Revision number of revision the delta for storage is encoded
455 against. -1 indicates not encoded against a base revision.
424 against. -1 indicates not encoded against a base revision.
456 link revision
425 link revision
457 Revision number of changelog revision this entry is related to.
426 Revision number of changelog revision this entry is related to.
458 p1 revision
427 p1 revision
459 Revision number of 1st parent. -1 if no 1st parent.
428 Revision number of 1st parent. -1 if no 1st parent.
460 p2 revision
429 p2 revision
461 Revision number of 2nd parent. -1 if no 1st parent.
430 Revision number of 2nd parent. -1 if no 1st parent.
462 node
431 node
463 Binary node value for this revision number.
432 Binary node value for this revision number.
464
433
465 Negative values should index off the end of the sequence. ``-1``
434 Negative values should index off the end of the sequence. ``-1``
466 should return the null revision. ``-2`` should return the most
435 should return the null revision. ``-2`` should return the most
467 recent revision.
436 recent revision.
468 """
437 """
469
438
470 def __contains__(rev):
439 def __contains__(rev):
471 """Whether a revision number exists."""
440 """Whether a revision number exists."""
472
441
473 def insert(self, i, entry):
442 def insert(self, i, entry):
474 """Add an item to the index at specific revision."""
443 """Add an item to the index at specific revision."""
475
444
476 class ifileindex(interfaceutil.Interface):
445 class ifileindex(interfaceutil.Interface):
477 """Storage interface for index data of a single file.
446 """Storage interface for index data of a single file.
478
447
479 File storage data is divided into index metadata and data storage.
448 File storage data is divided into index metadata and data storage.
480 This interface defines the index portion of the interface.
449 This interface defines the index portion of the interface.
481
450
482 The index logically consists of:
451 The index logically consists of:
483
452
484 * A mapping between revision numbers and nodes.
453 * A mapping between revision numbers and nodes.
485 * DAG data (storing and querying the relationship between nodes).
454 * DAG data (storing and querying the relationship between nodes).
486 * Metadata to facilitate storage.
455 * Metadata to facilitate storage.
487 """
456 """
488 def __len__():
457 def __len__():
489 """Obtain the number of revisions stored for this file."""
458 """Obtain the number of revisions stored for this file."""
490
459
491 def __iter__():
460 def __iter__():
492 """Iterate over revision numbers for this file."""
461 """Iterate over revision numbers for this file."""
493
462
494 def revs(start=0, stop=None):
463 def revs(start=0, stop=None):
495 """Iterate over revision numbers for this file, with control."""
464 """Iterate over revision numbers for this file, with control."""
496
465
497 def parents(node):
466 def parents(node):
498 """Returns a 2-tuple of parent nodes for a revision.
467 """Returns a 2-tuple of parent nodes for a revision.
499
468
500 Values will be ``nullid`` if the parent is empty.
469 Values will be ``nullid`` if the parent is empty.
501 """
470 """
502
471
503 def parentrevs(rev):
472 def parentrevs(rev):
504 """Like parents() but operates on revision numbers."""
473 """Like parents() but operates on revision numbers."""
505
474
506 def rev(node):
475 def rev(node):
507 """Obtain the revision number given a node.
476 """Obtain the revision number given a node.
508
477
509 Raises ``error.LookupError`` if the node is not known.
478 Raises ``error.LookupError`` if the node is not known.
510 """
479 """
511
480
512 def node(rev):
481 def node(rev):
513 """Obtain the node value given a revision number.
482 """Obtain the node value given a revision number.
514
483
515 Raises ``IndexError`` if the node is not known.
484 Raises ``IndexError`` if the node is not known.
516 """
485 """
517
486
518 def lookup(node):
487 def lookup(node):
519 """Attempt to resolve a value to a node.
488 """Attempt to resolve a value to a node.
520
489
521 Value can be a binary node, hex node, revision number, or a string
490 Value can be a binary node, hex node, revision number, or a string
522 that can be converted to an integer.
491 that can be converted to an integer.
523
492
524 Raises ``error.LookupError`` if a node could not be resolved.
493 Raises ``error.LookupError`` if a node could not be resolved.
525 """
494 """
526
495
527 def linkrev(rev):
496 def linkrev(rev):
528 """Obtain the changeset revision number a revision is linked to."""
497 """Obtain the changeset revision number a revision is linked to."""
529
498
530 def flags(rev):
499 def flags(rev):
531 """Obtain flags used to affect storage of a revision."""
500 """Obtain flags used to affect storage of a revision."""
532
501
533 def iscensored(rev):
502 def iscensored(rev):
534 """Return whether a revision's content has been censored."""
503 """Return whether a revision's content has been censored."""
535
504
536 def commonancestorsheads(node1, node2):
505 def commonancestorsheads(node1, node2):
537 """Obtain an iterable of nodes containing heads of common ancestors.
506 """Obtain an iterable of nodes containing heads of common ancestors.
538
507
539 See ``ancestor.commonancestorsheads()``.
508 See ``ancestor.commonancestorsheads()``.
540 """
509 """
541
510
542 def descendants(revs):
511 def descendants(revs):
543 """Obtain descendant revision numbers for a set of revision numbers.
512 """Obtain descendant revision numbers for a set of revision numbers.
544
513
545 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
514 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
546 """
515 """
547
516
548 def heads(start=None, stop=None):
517 def heads(start=None, stop=None):
549 """Obtain a list of nodes that are DAG heads, with control.
518 """Obtain a list of nodes that are DAG heads, with control.
550
519
551 The set of revisions examined can be limited by specifying
520 The set of revisions examined can be limited by specifying
552 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
521 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
553 iterable of nodes. DAG traversal starts at earlier revision
522 iterable of nodes. DAG traversal starts at earlier revision
554 ``start`` and iterates forward until any node in ``stop`` is
523 ``start`` and iterates forward until any node in ``stop`` is
555 encountered.
524 encountered.
556 """
525 """
557
526
558 def children(node):
527 def children(node):
559 """Obtain nodes that are children of a node.
528 """Obtain nodes that are children of a node.
560
529
561 Returns a list of nodes.
530 Returns a list of nodes.
562 """
531 """
563
532
564 def deltaparent(rev):
533 def deltaparent(rev):
565 """"Return the revision that is a suitable parent to delta against."""
534 """"Return the revision that is a suitable parent to delta against."""
566
535
567 class ifiledata(interfaceutil.Interface):
536 class ifiledata(interfaceutil.Interface):
568 """Storage interface for data storage of a specific file.
537 """Storage interface for data storage of a specific file.
569
538
570 This complements ``ifileindex`` and provides an interface for accessing
539 This complements ``ifileindex`` and provides an interface for accessing
571 data for a tracked file.
540 data for a tracked file.
572 """
541 """
573 def rawsize(rev):
542 def rawsize(rev):
574 """The size of the fulltext data for a revision as stored."""
543 """The size of the fulltext data for a revision as stored."""
575
544
576 def size(rev):
545 def size(rev):
577 """Obtain the fulltext size of file data.
546 """Obtain the fulltext size of file data.
578
547
579 Any metadata is excluded from size measurements. Use ``rawsize()`` if
548 Any metadata is excluded from size measurements. Use ``rawsize()`` if
580 metadata size is important.
549 metadata size is important.
581 """
550 """
582
551
583 def checkhash(fulltext, node, p1=None, p2=None, rev=None):
552 def checkhash(fulltext, node, p1=None, p2=None, rev=None):
584 """Validate the stored hash of a given fulltext and node.
553 """Validate the stored hash of a given fulltext and node.
585
554
586 Raises ``error.StorageError`` is hash validation fails.
555 Raises ``error.StorageError`` is hash validation fails.
587 """
556 """
588
557
589 def revision(node, raw=False):
558 def revision(node, raw=False):
590 """"Obtain fulltext data for a node.
559 """"Obtain fulltext data for a node.
591
560
592 By default, any storage transformations are applied before the data
561 By default, any storage transformations are applied before the data
593 is returned. If ``raw`` is True, non-raw storage transformations
562 is returned. If ``raw`` is True, non-raw storage transformations
594 are not applied.
563 are not applied.
595
564
596 The fulltext data may contain a header containing metadata. Most
565 The fulltext data may contain a header containing metadata. Most
597 consumers should use ``read()`` to obtain the actual file data.
566 consumers should use ``read()`` to obtain the actual file data.
598 """
567 """
599
568
600 def read(node):
569 def read(node):
601 """Resolve file fulltext data.
570 """Resolve file fulltext data.
602
571
603 This is similar to ``revision()`` except any metadata in the data
572 This is similar to ``revision()`` except any metadata in the data
604 headers is stripped.
573 headers is stripped.
605 """
574 """
606
575
607 def renamed(node):
576 def renamed(node):
608 """Obtain copy metadata for a node.
577 """Obtain copy metadata for a node.
609
578
610 Returns ``False`` if no copy metadata is stored or a 2-tuple of
579 Returns ``False`` if no copy metadata is stored or a 2-tuple of
611 (path, node) from which this revision was copied.
580 (path, node) from which this revision was copied.
612 """
581 """
613
582
614 def cmp(node, fulltext):
583 def cmp(node, fulltext):
615 """Compare fulltext to another revision.
584 """Compare fulltext to another revision.
616
585
617 Returns True if the fulltext is different from what is stored.
586 Returns True if the fulltext is different from what is stored.
618
587
619 This takes copy metadata into account.
588 This takes copy metadata into account.
620
589
621 TODO better document the copy metadata and censoring logic.
590 TODO better document the copy metadata and censoring logic.
622 """
591 """
623
592
624 def revdiff(rev1, rev2):
593 def revdiff(rev1, rev2):
625 """Obtain a delta between two revision numbers.
594 """Obtain a delta between two revision numbers.
626
595
627 Operates on raw data in the store (``revision(node, raw=True)``).
596 Operates on raw data in the store (``revision(node, raw=True)``).
628
597
629 The returned data is the result of ``bdiff.bdiff`` on the raw
598 The returned data is the result of ``bdiff.bdiff`` on the raw
630 revision data.
599 revision data.
631 """
600 """
632
601
633 def emitrevisiondeltas(requests):
634 """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
635
636 Given an iterable of objects conforming to the ``irevisiondeltarequest``
637 interface, emits objects conforming to the ``irevisiondelta``
638 interface.
639
640 This method is a generator.
641
642 ``irevisiondelta`` should be emitted in the same order of
643 ``irevisiondeltarequest`` that was passed in.
644
645 The emitted objects MUST conform by the results of
646 ``irevisiondeltarequest``. Namely, they must respect any requests
647 for building a delta from a specific ``basenode`` if defined.
648
649 When sending deltas, implementations must take into account whether
650 the client has the base delta before encoding a delta against that
651 revision. A revision encountered previously in ``requests`` is
652 always a suitable base revision. An example of a bad delta is a delta
653 against a non-ancestor revision. Another example of a bad delta is a
654 delta against a censored revision.
655 """
656
657 def emitrevisions(nodes,
602 def emitrevisions(nodes,
658 nodesorder=None,
603 nodesorder=None,
659 revisiondata=False,
604 revisiondata=False,
660 assumehaveparentrevisions=False,
605 assumehaveparentrevisions=False,
661 deltaprevious=False):
606 deltaprevious=False):
662 """Produce ``irevisiondelta`` for revisions.
607 """Produce ``irevisiondelta`` for revisions.
663
608
664 Given an iterable of nodes, emits objects conforming to the
609 Given an iterable of nodes, emits objects conforming to the
665 ``irevisiondelta`` interface that describe revisions in storage.
610 ``irevisiondelta`` interface that describe revisions in storage.
666
611
667 This method is a generator.
612 This method is a generator.
668
613
669 The input nodes may be unordered. Implementations must ensure that a
614 The input nodes may be unordered. Implementations must ensure that a
670 node's parents are emitted before the node itself. Transitively, this
615 node's parents are emitted before the node itself. Transitively, this
671 means that a node may only be emitted once all its ancestors in
616 means that a node may only be emitted once all its ancestors in
672 ``nodes`` have also been emitted.
617 ``nodes`` have also been emitted.
673
618
674 By default, emits "index" data (the ``node``, ``p1node``, and
619 By default, emits "index" data (the ``node``, ``p1node``, and
675 ``p2node`` attributes). If ``revisiondata`` is set, revision data
620 ``p2node`` attributes). If ``revisiondata`` is set, revision data
676 will also be present on the emitted objects.
621 will also be present on the emitted objects.
677
622
678 With default argument values, implementations can choose to emit
623 With default argument values, implementations can choose to emit
679 either fulltext revision data or a delta. When emitting deltas,
624 either fulltext revision data or a delta. When emitting deltas,
680 implementations must consider whether the delta's base revision
625 implementations must consider whether the delta's base revision
681 fulltext is available to the receiver.
626 fulltext is available to the receiver.
682
627
683 The base revision fulltext is guaranteed to be available if any of
628 The base revision fulltext is guaranteed to be available if any of
684 the following are met:
629 the following are met:
685
630
686 * Its fulltext revision was emitted by this method call.
631 * Its fulltext revision was emitted by this method call.
687 * A delta for that revision was emitted by this method call.
632 * A delta for that revision was emitted by this method call.
688 * ``assumehaveparentrevisions`` is True and the base revision is a
633 * ``assumehaveparentrevisions`` is True and the base revision is a
689 parent of the node.
634 parent of the node.
690
635
691 ``nodesorder`` can be used to control the order that revisions are
636 ``nodesorder`` can be used to control the order that revisions are
692 emitted. By default, revisions can be reordered as long as they are
637 emitted. By default, revisions can be reordered as long as they are
693 in DAG topological order (see above). If the value is ``nodes``,
638 in DAG topological order (see above). If the value is ``nodes``,
694 the iteration order from ``nodes`` should be used. If the value is
639 the iteration order from ``nodes`` should be used. If the value is
695 ``storage``, then the native order from the backing storage layer
640 ``storage``, then the native order from the backing storage layer
696 is used. (Not all storage layers will have strong ordering and behavior
641 is used. (Not all storage layers will have strong ordering and behavior
697 of this mode is storage-dependent.) ``nodes`` ordering can force
642 of this mode is storage-dependent.) ``nodes`` ordering can force
698 revisions to be emitted before their ancestors, so consumers should
643 revisions to be emitted before their ancestors, so consumers should
699 use it with care.
644 use it with care.
700
645
701 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
646 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
702 be set and it is the caller's responsibility to resolve it, if needed.
647 be set and it is the caller's responsibility to resolve it, if needed.
703
648
704 If ``deltaprevious`` is True and revision data is requested, all
649 If ``deltaprevious`` is True and revision data is requested, all
705 revision data should be emitted as deltas against the revision
650 revision data should be emitted as deltas against the revision
706 emitted just prior. The initial revision should be a delta against
651 emitted just prior. The initial revision should be a delta against
707 its 1st parent.
652 its 1st parent.
708 """
653 """
709
654
710 class ifilemutation(interfaceutil.Interface):
655 class ifilemutation(interfaceutil.Interface):
711 """Storage interface for mutation events of a tracked file."""
656 """Storage interface for mutation events of a tracked file."""
712
657
713 def add(filedata, meta, transaction, linkrev, p1, p2):
658 def add(filedata, meta, transaction, linkrev, p1, p2):
714 """Add a new revision to the store.
659 """Add a new revision to the store.
715
660
716 Takes file data, dictionary of metadata, a transaction, linkrev,
661 Takes file data, dictionary of metadata, a transaction, linkrev,
717 and parent nodes.
662 and parent nodes.
718
663
719 Returns the node that was added.
664 Returns the node that was added.
720
665
721 May no-op if a revision matching the supplied data is already stored.
666 May no-op if a revision matching the supplied data is already stored.
722 """
667 """
723
668
724 def addrevision(revisiondata, transaction, linkrev, p1, p2, node=None,
669 def addrevision(revisiondata, transaction, linkrev, p1, p2, node=None,
725 flags=0, cachedelta=None):
670 flags=0, cachedelta=None):
726 """Add a new revision to the store.
671 """Add a new revision to the store.
727
672
728 This is similar to ``add()`` except it operates at a lower level.
673 This is similar to ``add()`` except it operates at a lower level.
729
674
730 The data passed in already contains a metadata header, if any.
675 The data passed in already contains a metadata header, if any.
731
676
732 ``node`` and ``flags`` can be used to define the expected node and
677 ``node`` and ``flags`` can be used to define the expected node and
733 the flags to use with storage.
678 the flags to use with storage.
734
679
735 ``add()`` is usually called when adding files from e.g. the working
680 ``add()`` is usually called when adding files from e.g. the working
736 directory. ``addrevision()`` is often called by ``add()`` and for
681 directory. ``addrevision()`` is often called by ``add()`` and for
737 scenarios where revision data has already been computed, such as when
682 scenarios where revision data has already been computed, such as when
738 applying raw data from a peer repo.
683 applying raw data from a peer repo.
739 """
684 """
740
685
741 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
686 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
742 """Process a series of deltas for storage.
687 """Process a series of deltas for storage.
743
688
744 ``deltas`` is an iterable of 7-tuples of
689 ``deltas`` is an iterable of 7-tuples of
745 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
690 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
746 to add.
691 to add.
747
692
748 The ``delta`` field contains ``mpatch`` data to apply to a base
693 The ``delta`` field contains ``mpatch`` data to apply to a base
749 revision, identified by ``deltabase``. The base node can be
694 revision, identified by ``deltabase``. The base node can be
750 ``nullid``, in which case the header from the delta can be ignored
695 ``nullid``, in which case the header from the delta can be ignored
751 and the delta used as the fulltext.
696 and the delta used as the fulltext.
752
697
753 ``addrevisioncb`` should be called for each node as it is committed.
698 ``addrevisioncb`` should be called for each node as it is committed.
754
699
755 Returns a list of nodes that were processed. A node will be in the list
700 Returns a list of nodes that were processed. A node will be in the list
756 even if it existed in the store previously.
701 even if it existed in the store previously.
757 """
702 """
758
703
759 def censorrevision(tr, node, tombstone=b''):
704 def censorrevision(tr, node, tombstone=b''):
760 """Remove the content of a single revision.
705 """Remove the content of a single revision.
761
706
762 The specified ``node`` will have its content purged from storage.
707 The specified ``node`` will have its content purged from storage.
763 Future attempts to access the revision data for this node will
708 Future attempts to access the revision data for this node will
764 result in failure.
709 result in failure.
765
710
766 A ``tombstone`` message can optionally be stored. This message may be
711 A ``tombstone`` message can optionally be stored. This message may be
767 displayed to users when they attempt to access the missing revision
712 displayed to users when they attempt to access the missing revision
768 data.
713 data.
769
714
770 Storage backends may have stored deltas against the previous content
715 Storage backends may have stored deltas against the previous content
771 in this revision. As part of censoring a revision, these storage
716 in this revision. As part of censoring a revision, these storage
772 backends are expected to rewrite any internally stored deltas such
717 backends are expected to rewrite any internally stored deltas such
773 that they no longer reference the deleted content.
718 that they no longer reference the deleted content.
774 """
719 """
775
720
776 def getstrippoint(minlink):
721 def getstrippoint(minlink):
777 """Find the minimum revision that must be stripped to strip a linkrev.
722 """Find the minimum revision that must be stripped to strip a linkrev.
778
723
779 Returns a 2-tuple containing the minimum revision number and a set
724 Returns a 2-tuple containing the minimum revision number and a set
780 of all revisions numbers that would be broken by this strip.
725 of all revisions numbers that would be broken by this strip.
781
726
782 TODO this is highly revlog centric and should be abstracted into
727 TODO this is highly revlog centric and should be abstracted into
783 a higher-level deletion API. ``repair.strip()`` relies on this.
728 a higher-level deletion API. ``repair.strip()`` relies on this.
784 """
729 """
785
730
786 def strip(minlink, transaction):
731 def strip(minlink, transaction):
787 """Remove storage of items starting at a linkrev.
732 """Remove storage of items starting at a linkrev.
788
733
789 This uses ``getstrippoint()`` to determine the first node to remove.
734 This uses ``getstrippoint()`` to determine the first node to remove.
790 Then it effectively truncates storage for all revisions after that.
735 Then it effectively truncates storage for all revisions after that.
791
736
792 TODO this is highly revlog centric and should be abstracted into a
737 TODO this is highly revlog centric and should be abstracted into a
793 higher-level deletion API.
738 higher-level deletion API.
794 """
739 """
795
740
796 class ifilestorage(ifileindex, ifiledata, ifilemutation):
741 class ifilestorage(ifileindex, ifiledata, ifilemutation):
797 """Complete storage interface for a single tracked file."""
742 """Complete storage interface for a single tracked file."""
798
743
799 _generaldelta = interfaceutil.Attribute(
744 _generaldelta = interfaceutil.Attribute(
800 """Whether deltas can be against any parent revision.
745 """Whether deltas can be against any parent revision.
801
746
802 TODO this is used by changegroup code and it could probably be
747 TODO this is used by changegroup code and it could probably be
803 folded into another API.
748 folded into another API.
804 """)
749 """)
805
750
806 def files():
751 def files():
807 """Obtain paths that are backing storage for this file.
752 """Obtain paths that are backing storage for this file.
808
753
809 TODO this is used heavily by verify code and there should probably
754 TODO this is used heavily by verify code and there should probably
810 be a better API for that.
755 be a better API for that.
811 """
756 """
812
757
813 def verifyintegrity(state):
758 def verifyintegrity(state):
814 """Verifies the integrity of file storage.
759 """Verifies the integrity of file storage.
815
760
816 ``state`` is a dict holding state of the verifier process. It can be
761 ``state`` is a dict holding state of the verifier process. It can be
817 used to communicate data between invocations of multiple storage
762 used to communicate data between invocations of multiple storage
818 primitives.
763 primitives.
819
764
820 The method yields objects conforming to the ``iverifyproblem``
765 The method yields objects conforming to the ``iverifyproblem``
821 interface.
766 interface.
822 """
767 """
823
768
824 class idirs(interfaceutil.Interface):
769 class idirs(interfaceutil.Interface):
825 """Interface representing a collection of directories from paths.
770 """Interface representing a collection of directories from paths.
826
771
827 This interface is essentially a derived data structure representing
772 This interface is essentially a derived data structure representing
828 directories from a collection of paths.
773 directories from a collection of paths.
829 """
774 """
830
775
831 def addpath(path):
776 def addpath(path):
832 """Add a path to the collection.
777 """Add a path to the collection.
833
778
834 All directories in the path will be added to the collection.
779 All directories in the path will be added to the collection.
835 """
780 """
836
781
837 def delpath(path):
782 def delpath(path):
838 """Remove a path from the collection.
783 """Remove a path from the collection.
839
784
840 If the removal was the last path in a particular directory, the
785 If the removal was the last path in a particular directory, the
841 directory is removed from the collection.
786 directory is removed from the collection.
842 """
787 """
843
788
844 def __iter__():
789 def __iter__():
845 """Iterate over the directories in this collection of paths."""
790 """Iterate over the directories in this collection of paths."""
846
791
847 def __contains__(path):
792 def __contains__(path):
848 """Whether a specific directory is in this collection."""
793 """Whether a specific directory is in this collection."""
849
794
850 class imanifestdict(interfaceutil.Interface):
795 class imanifestdict(interfaceutil.Interface):
851 """Interface representing a manifest data structure.
796 """Interface representing a manifest data structure.
852
797
853 A manifest is effectively a dict mapping paths to entries. Each entry
798 A manifest is effectively a dict mapping paths to entries. Each entry
854 consists of a binary node and extra flags affecting that entry.
799 consists of a binary node and extra flags affecting that entry.
855 """
800 """
856
801
857 def __getitem__(path):
802 def __getitem__(path):
858 """Returns the binary node value for a path in the manifest.
803 """Returns the binary node value for a path in the manifest.
859
804
860 Raises ``KeyError`` if the path does not exist in the manifest.
805 Raises ``KeyError`` if the path does not exist in the manifest.
861
806
862 Equivalent to ``self.find(path)[0]``.
807 Equivalent to ``self.find(path)[0]``.
863 """
808 """
864
809
865 def find(path):
810 def find(path):
866 """Returns the entry for a path in the manifest.
811 """Returns the entry for a path in the manifest.
867
812
868 Returns a 2-tuple of (node, flags).
813 Returns a 2-tuple of (node, flags).
869
814
870 Raises ``KeyError`` if the path does not exist in the manifest.
815 Raises ``KeyError`` if the path does not exist in the manifest.
871 """
816 """
872
817
873 def __len__():
818 def __len__():
874 """Return the number of entries in the manifest."""
819 """Return the number of entries in the manifest."""
875
820
876 def __nonzero__():
821 def __nonzero__():
877 """Returns True if the manifest has entries, False otherwise."""
822 """Returns True if the manifest has entries, False otherwise."""
878
823
879 __bool__ = __nonzero__
824 __bool__ = __nonzero__
880
825
881 def __setitem__(path, node):
826 def __setitem__(path, node):
882 """Define the node value for a path in the manifest.
827 """Define the node value for a path in the manifest.
883
828
884 If the path is already in the manifest, its flags will be copied to
829 If the path is already in the manifest, its flags will be copied to
885 the new entry.
830 the new entry.
886 """
831 """
887
832
888 def __contains__(path):
833 def __contains__(path):
889 """Whether a path exists in the manifest."""
834 """Whether a path exists in the manifest."""
890
835
891 def __delitem__(path):
836 def __delitem__(path):
892 """Remove a path from the manifest.
837 """Remove a path from the manifest.
893
838
894 Raises ``KeyError`` if the path is not in the manifest.
839 Raises ``KeyError`` if the path is not in the manifest.
895 """
840 """
896
841
897 def __iter__():
842 def __iter__():
898 """Iterate over paths in the manifest."""
843 """Iterate over paths in the manifest."""
899
844
900 def iterkeys():
845 def iterkeys():
901 """Iterate over paths in the manifest."""
846 """Iterate over paths in the manifest."""
902
847
903 def keys():
848 def keys():
904 """Obtain a list of paths in the manifest."""
849 """Obtain a list of paths in the manifest."""
905
850
906 def filesnotin(other, match=None):
851 def filesnotin(other, match=None):
907 """Obtain the set of paths in this manifest but not in another.
852 """Obtain the set of paths in this manifest but not in another.
908
853
909 ``match`` is an optional matcher function to be applied to both
854 ``match`` is an optional matcher function to be applied to both
910 manifests.
855 manifests.
911
856
912 Returns a set of paths.
857 Returns a set of paths.
913 """
858 """
914
859
915 def dirs():
860 def dirs():
916 """Returns an object implementing the ``idirs`` interface."""
861 """Returns an object implementing the ``idirs`` interface."""
917
862
918 def hasdir(dir):
863 def hasdir(dir):
919 """Returns a bool indicating if a directory is in this manifest."""
864 """Returns a bool indicating if a directory is in this manifest."""
920
865
921 def matches(match):
866 def matches(match):
922 """Generate a new manifest filtered through a matcher.
867 """Generate a new manifest filtered through a matcher.
923
868
924 Returns an object conforming to the ``imanifestdict`` interface.
869 Returns an object conforming to the ``imanifestdict`` interface.
925 """
870 """
926
871
927 def walk(match):
872 def walk(match):
928 """Generator of paths in manifest satisfying a matcher.
873 """Generator of paths in manifest satisfying a matcher.
929
874
930 This is equivalent to ``self.matches(match).iterkeys()`` except a new
875 This is equivalent to ``self.matches(match).iterkeys()`` except a new
931 manifest object is not created.
876 manifest object is not created.
932
877
933 If the matcher has explicit files listed and they don't exist in
878 If the matcher has explicit files listed and they don't exist in
934 the manifest, ``match.bad()`` is called for each missing file.
879 the manifest, ``match.bad()`` is called for each missing file.
935 """
880 """
936
881
937 def diff(other, match=None, clean=False):
882 def diff(other, match=None, clean=False):
938 """Find differences between this manifest and another.
883 """Find differences between this manifest and another.
939
884
940 This manifest is compared to ``other``.
885 This manifest is compared to ``other``.
941
886
942 If ``match`` is provided, the two manifests are filtered against this
887 If ``match`` is provided, the two manifests are filtered against this
943 matcher and only entries satisfying the matcher are compared.
888 matcher and only entries satisfying the matcher are compared.
944
889
945 If ``clean`` is True, unchanged files are included in the returned
890 If ``clean`` is True, unchanged files are included in the returned
946 object.
891 object.
947
892
948 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
893 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
949 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
894 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
950 represents the node and flags for this manifest and ``(node2, flag2)``
895 represents the node and flags for this manifest and ``(node2, flag2)``
951 are the same for the other manifest.
896 are the same for the other manifest.
952 """
897 """
953
898
954 def setflag(path, flag):
899 def setflag(path, flag):
955 """Set the flag value for a given path.
900 """Set the flag value for a given path.
956
901
957 Raises ``KeyError`` if the path is not already in the manifest.
902 Raises ``KeyError`` if the path is not already in the manifest.
958 """
903 """
959
904
960 def get(path, default=None):
905 def get(path, default=None):
961 """Obtain the node value for a path or a default value if missing."""
906 """Obtain the node value for a path or a default value if missing."""
962
907
963 def flags(path, default=''):
908 def flags(path, default=''):
964 """Return the flags value for a path or a default value if missing."""
909 """Return the flags value for a path or a default value if missing."""
965
910
966 def copy():
911 def copy():
967 """Return a copy of this manifest."""
912 """Return a copy of this manifest."""
968
913
969 def items():
914 def items():
970 """Returns an iterable of (path, node) for items in this manifest."""
915 """Returns an iterable of (path, node) for items in this manifest."""
971
916
972 def iteritems():
917 def iteritems():
973 """Identical to items()."""
918 """Identical to items()."""
974
919
975 def iterentries():
920 def iterentries():
976 """Returns an iterable of (path, node, flags) for this manifest.
921 """Returns an iterable of (path, node, flags) for this manifest.
977
922
978 Similar to ``iteritems()`` except items are a 3-tuple and include
923 Similar to ``iteritems()`` except items are a 3-tuple and include
979 flags.
924 flags.
980 """
925 """
981
926
982 def text():
927 def text():
983 """Obtain the raw data representation for this manifest.
928 """Obtain the raw data representation for this manifest.
984
929
985 Result is used to create a manifest revision.
930 Result is used to create a manifest revision.
986 """
931 """
987
932
988 def fastdelta(base, changes):
933 def fastdelta(base, changes):
989 """Obtain a delta between this manifest and another given changes.
934 """Obtain a delta between this manifest and another given changes.
990
935
991 ``base`` in the raw data representation for another manifest.
936 ``base`` in the raw data representation for another manifest.
992
937
993 ``changes`` is an iterable of ``(path, to_delete)``.
938 ``changes`` is an iterable of ``(path, to_delete)``.
994
939
995 Returns a 2-tuple containing ``bytearray(self.text())`` and the
940 Returns a 2-tuple containing ``bytearray(self.text())`` and the
996 delta between ``base`` and this manifest.
941 delta between ``base`` and this manifest.
997 """
942 """
998
943
999 class imanifestrevisionbase(interfaceutil.Interface):
944 class imanifestrevisionbase(interfaceutil.Interface):
1000 """Base interface representing a single revision of a manifest.
945 """Base interface representing a single revision of a manifest.
1001
946
1002 Should not be used as a primary interface: should always be inherited
947 Should not be used as a primary interface: should always be inherited
1003 as part of a larger interface.
948 as part of a larger interface.
1004 """
949 """
1005
950
1006 def new():
951 def new():
1007 """Obtain a new manifest instance.
952 """Obtain a new manifest instance.
1008
953
1009 Returns an object conforming to the ``imanifestrevisionwritable``
954 Returns an object conforming to the ``imanifestrevisionwritable``
1010 interface. The instance will be associated with the same
955 interface. The instance will be associated with the same
1011 ``imanifestlog`` collection as this instance.
956 ``imanifestlog`` collection as this instance.
1012 """
957 """
1013
958
1014 def copy():
959 def copy():
1015 """Obtain a copy of this manifest instance.
960 """Obtain a copy of this manifest instance.
1016
961
1017 Returns an object conforming to the ``imanifestrevisionwritable``
962 Returns an object conforming to the ``imanifestrevisionwritable``
1018 interface. The instance will be associated with the same
963 interface. The instance will be associated with the same
1019 ``imanifestlog`` collection as this instance.
964 ``imanifestlog`` collection as this instance.
1020 """
965 """
1021
966
1022 def read():
967 def read():
1023 """Obtain the parsed manifest data structure.
968 """Obtain the parsed manifest data structure.
1024
969
1025 The returned object conforms to the ``imanifestdict`` interface.
970 The returned object conforms to the ``imanifestdict`` interface.
1026 """
971 """
1027
972
1028 class imanifestrevisionstored(imanifestrevisionbase):
973 class imanifestrevisionstored(imanifestrevisionbase):
1029 """Interface representing a manifest revision committed to storage."""
974 """Interface representing a manifest revision committed to storage."""
1030
975
1031 def node():
976 def node():
1032 """The binary node for this manifest."""
977 """The binary node for this manifest."""
1033
978
1034 parents = interfaceutil.Attribute(
979 parents = interfaceutil.Attribute(
1035 """List of binary nodes that are parents for this manifest revision."""
980 """List of binary nodes that are parents for this manifest revision."""
1036 )
981 )
1037
982
1038 def readdelta(shallow=False):
983 def readdelta(shallow=False):
1039 """Obtain the manifest data structure representing changes from parent.
984 """Obtain the manifest data structure representing changes from parent.
1040
985
1041 This manifest is compared to its 1st parent. A new manifest representing
986 This manifest is compared to its 1st parent. A new manifest representing
1042 those differences is constructed.
987 those differences is constructed.
1043
988
1044 The returned object conforms to the ``imanifestdict`` interface.
989 The returned object conforms to the ``imanifestdict`` interface.
1045 """
990 """
1046
991
1047 def readfast(shallow=False):
992 def readfast(shallow=False):
1048 """Calls either ``read()`` or ``readdelta()``.
993 """Calls either ``read()`` or ``readdelta()``.
1049
994
1050 The faster of the two options is called.
995 The faster of the two options is called.
1051 """
996 """
1052
997
1053 def find(key):
998 def find(key):
1054 """Calls self.read().find(key)``.
999 """Calls self.read().find(key)``.
1055
1000
1056 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1001 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1057 """
1002 """
1058
1003
1059 class imanifestrevisionwritable(imanifestrevisionbase):
1004 class imanifestrevisionwritable(imanifestrevisionbase):
1060 """Interface representing a manifest revision that can be committed."""
1005 """Interface representing a manifest revision that can be committed."""
1061
1006
1062 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1007 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1063 """Add this revision to storage.
1008 """Add this revision to storage.
1064
1009
1065 Takes a transaction object, the changeset revision number it will
1010 Takes a transaction object, the changeset revision number it will
1066 be associated with, its parent nodes, and lists of added and
1011 be associated with, its parent nodes, and lists of added and
1067 removed paths.
1012 removed paths.
1068
1013
1069 If match is provided, storage can choose not to inspect or write out
1014 If match is provided, storage can choose not to inspect or write out
1070 items that do not match. Storage is still required to be able to provide
1015 items that do not match. Storage is still required to be able to provide
1071 the full manifest in the future for any directories written (these
1016 the full manifest in the future for any directories written (these
1072 manifests should not be "narrowed on disk").
1017 manifests should not be "narrowed on disk").
1073
1018
1074 Returns the binary node of the created revision.
1019 Returns the binary node of the created revision.
1075 """
1020 """
1076
1021
1077 class imanifeststorage(interfaceutil.Interface):
1022 class imanifeststorage(interfaceutil.Interface):
1078 """Storage interface for manifest data."""
1023 """Storage interface for manifest data."""
1079
1024
1080 tree = interfaceutil.Attribute(
1025 tree = interfaceutil.Attribute(
1081 """The path to the directory this manifest tracks.
1026 """The path to the directory this manifest tracks.
1082
1027
1083 The empty bytestring represents the root manifest.
1028 The empty bytestring represents the root manifest.
1084 """)
1029 """)
1085
1030
1086 index = interfaceutil.Attribute(
1031 index = interfaceutil.Attribute(
1087 """An ``ifilerevisionssequence`` instance.""")
1032 """An ``ifilerevisionssequence`` instance.""")
1088
1033
1089 indexfile = interfaceutil.Attribute(
1034 indexfile = interfaceutil.Attribute(
1090 """Path of revlog index file.
1035 """Path of revlog index file.
1091
1036
1092 TODO this is revlog specific and should not be exposed.
1037 TODO this is revlog specific and should not be exposed.
1093 """)
1038 """)
1094
1039
1095 opener = interfaceutil.Attribute(
1040 opener = interfaceutil.Attribute(
1096 """VFS opener to use to access underlying files used for storage.
1041 """VFS opener to use to access underlying files used for storage.
1097
1042
1098 TODO this is revlog specific and should not be exposed.
1043 TODO this is revlog specific and should not be exposed.
1099 """)
1044 """)
1100
1045
1101 version = interfaceutil.Attribute(
1046 version = interfaceutil.Attribute(
1102 """Revlog version number.
1047 """Revlog version number.
1103
1048
1104 TODO this is revlog specific and should not be exposed.
1049 TODO this is revlog specific and should not be exposed.
1105 """)
1050 """)
1106
1051
1107 _generaldelta = interfaceutil.Attribute(
1052 _generaldelta = interfaceutil.Attribute(
1108 """Whether generaldelta storage is being used.
1053 """Whether generaldelta storage is being used.
1109
1054
1110 TODO this is revlog specific and should not be exposed.
1055 TODO this is revlog specific and should not be exposed.
1111 """)
1056 """)
1112
1057
1113 fulltextcache = interfaceutil.Attribute(
1058 fulltextcache = interfaceutil.Attribute(
1114 """Dict with cache of fulltexts.
1059 """Dict with cache of fulltexts.
1115
1060
1116 TODO this doesn't feel appropriate for the storage interface.
1061 TODO this doesn't feel appropriate for the storage interface.
1117 """)
1062 """)
1118
1063
1119 def __len__():
1064 def __len__():
1120 """Obtain the number of revisions stored for this manifest."""
1065 """Obtain the number of revisions stored for this manifest."""
1121
1066
1122 def __iter__():
1067 def __iter__():
1123 """Iterate over revision numbers for this manifest."""
1068 """Iterate over revision numbers for this manifest."""
1124
1069
1125 def rev(node):
1070 def rev(node):
1126 """Obtain the revision number given a binary node.
1071 """Obtain the revision number given a binary node.
1127
1072
1128 Raises ``error.LookupError`` if the node is not known.
1073 Raises ``error.LookupError`` if the node is not known.
1129 """
1074 """
1130
1075
1131 def node(rev):
1076 def node(rev):
1132 """Obtain the node value given a revision number.
1077 """Obtain the node value given a revision number.
1133
1078
1134 Raises ``error.LookupError`` if the revision is not known.
1079 Raises ``error.LookupError`` if the revision is not known.
1135 """
1080 """
1136
1081
1137 def lookup(value):
1082 def lookup(value):
1138 """Attempt to resolve a value to a node.
1083 """Attempt to resolve a value to a node.
1139
1084
1140 Value can be a binary node, hex node, revision number, or a bytes
1085 Value can be a binary node, hex node, revision number, or a bytes
1141 that can be converted to an integer.
1086 that can be converted to an integer.
1142
1087
1143 Raises ``error.LookupError`` if a ndoe could not be resolved.
1088 Raises ``error.LookupError`` if a ndoe could not be resolved.
1144
1089
1145 TODO this is only used by debug* commands and can probably be deleted
1090 TODO this is only used by debug* commands and can probably be deleted
1146 easily.
1091 easily.
1147 """
1092 """
1148
1093
1149 def parents(node):
1094 def parents(node):
1150 """Returns a 2-tuple of parent nodes for a node.
1095 """Returns a 2-tuple of parent nodes for a node.
1151
1096
1152 Values will be ``nullid`` if the parent is empty.
1097 Values will be ``nullid`` if the parent is empty.
1153 """
1098 """
1154
1099
1155 def parentrevs(rev):
1100 def parentrevs(rev):
1156 """Like parents() but operates on revision numbers."""
1101 """Like parents() but operates on revision numbers."""
1157
1102
1158 def linkrev(rev):
1103 def linkrev(rev):
1159 """Obtain the changeset revision number a revision is linked to."""
1104 """Obtain the changeset revision number a revision is linked to."""
1160
1105
1161 def revision(node, _df=None, raw=False):
1106 def revision(node, _df=None, raw=False):
1162 """Obtain fulltext data for a node."""
1107 """Obtain fulltext data for a node."""
1163
1108
1164 def revdiff(rev1, rev2):
1109 def revdiff(rev1, rev2):
1165 """Obtain a delta between two revision numbers.
1110 """Obtain a delta between two revision numbers.
1166
1111
1167 The returned data is the result of ``bdiff.bdiff()`` on the raw
1112 The returned data is the result of ``bdiff.bdiff()`` on the raw
1168 revision data.
1113 revision data.
1169 """
1114 """
1170
1115
1171 def cmp(node, fulltext):
1116 def cmp(node, fulltext):
1172 """Compare fulltext to another revision.
1117 """Compare fulltext to another revision.
1173
1118
1174 Returns True if the fulltext is different from what is stored.
1119 Returns True if the fulltext is different from what is stored.
1175 """
1120 """
1176
1121
1177 def emitrevisiondeltas(requests):
1178 """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
1179
1180 See the documentation for ``ifiledata`` for more.
1181 """
1182
1183 def emitrevisions(nodes,
1122 def emitrevisions(nodes,
1184 nodesorder=None,
1123 nodesorder=None,
1185 revisiondata=False,
1124 revisiondata=False,
1186 assumehaveparentrevisions=False):
1125 assumehaveparentrevisions=False):
1187 """Produce ``irevisiondelta`` describing revisions.
1126 """Produce ``irevisiondelta`` describing revisions.
1188
1127
1189 See the documentation for ``ifiledata`` for more.
1128 See the documentation for ``ifiledata`` for more.
1190 """
1129 """
1191
1130
1192 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
1131 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
1193 """Process a series of deltas for storage.
1132 """Process a series of deltas for storage.
1194
1133
1195 See the documentation in ``ifilemutation`` for more.
1134 See the documentation in ``ifilemutation`` for more.
1196 """
1135 """
1197
1136
1198 def rawsize(rev):
1137 def rawsize(rev):
1199 """Obtain the size of tracked data.
1138 """Obtain the size of tracked data.
1200
1139
1201 Is equivalent to ``len(m.revision(node, raw=True))``.
1140 Is equivalent to ``len(m.revision(node, raw=True))``.
1202
1141
1203 TODO this method is only used by upgrade code and may be removed.
1142 TODO this method is only used by upgrade code and may be removed.
1204 """
1143 """
1205
1144
1206 def getstrippoint(minlink):
1145 def getstrippoint(minlink):
1207 """Find minimum revision that must be stripped to strip a linkrev.
1146 """Find minimum revision that must be stripped to strip a linkrev.
1208
1147
1209 See the documentation in ``ifilemutation`` for more.
1148 See the documentation in ``ifilemutation`` for more.
1210 """
1149 """
1211
1150
1212 def strip(minlink, transaction):
1151 def strip(minlink, transaction):
1213 """Remove storage of items starting at a linkrev.
1152 """Remove storage of items starting at a linkrev.
1214
1153
1215 See the documentation in ``ifilemutation`` for more.
1154 See the documentation in ``ifilemutation`` for more.
1216 """
1155 """
1217
1156
1218 def checksize():
1157 def checksize():
1219 """Obtain the expected sizes of backing files.
1158 """Obtain the expected sizes of backing files.
1220
1159
1221 TODO this is used by verify and it should not be part of the interface.
1160 TODO this is used by verify and it should not be part of the interface.
1222 """
1161 """
1223
1162
1224 def files():
1163 def files():
1225 """Obtain paths that are backing storage for this manifest.
1164 """Obtain paths that are backing storage for this manifest.
1226
1165
1227 TODO this is used by verify and there should probably be a better API
1166 TODO this is used by verify and there should probably be a better API
1228 for this functionality.
1167 for this functionality.
1229 """
1168 """
1230
1169
1231 def deltaparent(rev):
1170 def deltaparent(rev):
1232 """Obtain the revision that a revision is delta'd against.
1171 """Obtain the revision that a revision is delta'd against.
1233
1172
1234 TODO delta encoding is an implementation detail of storage and should
1173 TODO delta encoding is an implementation detail of storage and should
1235 not be exposed to the storage interface.
1174 not be exposed to the storage interface.
1236 """
1175 """
1237
1176
1238 def clone(tr, dest, **kwargs):
1177 def clone(tr, dest, **kwargs):
1239 """Clone this instance to another."""
1178 """Clone this instance to another."""
1240
1179
1241 def clearcaches(clear_persisted_data=False):
1180 def clearcaches(clear_persisted_data=False):
1242 """Clear any caches associated with this instance."""
1181 """Clear any caches associated with this instance."""
1243
1182
1244 def dirlog(d):
1183 def dirlog(d):
1245 """Obtain a manifest storage instance for a tree."""
1184 """Obtain a manifest storage instance for a tree."""
1246
1185
1247 def add(m, transaction, link, p1, p2, added, removed, readtree=None,
1186 def add(m, transaction, link, p1, p2, added, removed, readtree=None,
1248 match=None):
1187 match=None):
1249 """Add a revision to storage.
1188 """Add a revision to storage.
1250
1189
1251 ``m`` is an object conforming to ``imanifestdict``.
1190 ``m`` is an object conforming to ``imanifestdict``.
1252
1191
1253 ``link`` is the linkrev revision number.
1192 ``link`` is the linkrev revision number.
1254
1193
1255 ``p1`` and ``p2`` are the parent revision numbers.
1194 ``p1`` and ``p2`` are the parent revision numbers.
1256
1195
1257 ``added`` and ``removed`` are iterables of added and removed paths,
1196 ``added`` and ``removed`` are iterables of added and removed paths,
1258 respectively.
1197 respectively.
1259
1198
1260 ``readtree`` is a function that can be used to read the child tree(s)
1199 ``readtree`` is a function that can be used to read the child tree(s)
1261 when recursively writing the full tree structure when using
1200 when recursively writing the full tree structure when using
1262 treemanifets.
1201 treemanifets.
1263
1202
1264 ``match`` is a matcher that can be used to hint to storage that not all
1203 ``match`` is a matcher that can be used to hint to storage that not all
1265 paths must be inspected; this is an optimization and can be safely
1204 paths must be inspected; this is an optimization and can be safely
1266 ignored. Note that the storage must still be able to reproduce a full
1205 ignored. Note that the storage must still be able to reproduce a full
1267 manifest including files that did not match.
1206 manifest including files that did not match.
1268 """
1207 """
1269
1208
1270 class imanifestlog(interfaceutil.Interface):
1209 class imanifestlog(interfaceutil.Interface):
1271 """Interface representing a collection of manifest snapshots.
1210 """Interface representing a collection of manifest snapshots.
1272
1211
1273 Represents the root manifest in a repository.
1212 Represents the root manifest in a repository.
1274
1213
1275 Also serves as a means to access nested tree manifests and to cache
1214 Also serves as a means to access nested tree manifests and to cache
1276 tree manifests.
1215 tree manifests.
1277 """
1216 """
1278
1217
1279 def __getitem__(node):
1218 def __getitem__(node):
1280 """Obtain a manifest instance for a given binary node.
1219 """Obtain a manifest instance for a given binary node.
1281
1220
1282 Equivalent to calling ``self.get('', node)``.
1221 Equivalent to calling ``self.get('', node)``.
1283
1222
1284 The returned object conforms to the ``imanifestrevisionstored``
1223 The returned object conforms to the ``imanifestrevisionstored``
1285 interface.
1224 interface.
1286 """
1225 """
1287
1226
1288 def get(tree, node, verify=True):
1227 def get(tree, node, verify=True):
1289 """Retrieve the manifest instance for a given directory and binary node.
1228 """Retrieve the manifest instance for a given directory and binary node.
1290
1229
1291 ``node`` always refers to the node of the root manifest (which will be
1230 ``node`` always refers to the node of the root manifest (which will be
1292 the only manifest if flat manifests are being used).
1231 the only manifest if flat manifests are being used).
1293
1232
1294 If ``tree`` is the empty string, the root manifest is returned.
1233 If ``tree`` is the empty string, the root manifest is returned.
1295 Otherwise the manifest for the specified directory will be returned
1234 Otherwise the manifest for the specified directory will be returned
1296 (requires tree manifests).
1235 (requires tree manifests).
1297
1236
1298 If ``verify`` is True, ``LookupError`` is raised if the node is not
1237 If ``verify`` is True, ``LookupError`` is raised if the node is not
1299 known.
1238 known.
1300
1239
1301 The returned object conforms to the ``imanifestrevisionstored``
1240 The returned object conforms to the ``imanifestrevisionstored``
1302 interface.
1241 interface.
1303 """
1242 """
1304
1243
1305 def getstorage(tree):
1244 def getstorage(tree):
1306 """Retrieve an interface to storage for a particular tree.
1245 """Retrieve an interface to storage for a particular tree.
1307
1246
1308 If ``tree`` is the empty bytestring, storage for the root manifest will
1247 If ``tree`` is the empty bytestring, storage for the root manifest will
1309 be returned. Otherwise storage for a tree manifest is returned.
1248 be returned. Otherwise storage for a tree manifest is returned.
1310
1249
1311 TODO formalize interface for returned object.
1250 TODO formalize interface for returned object.
1312 """
1251 """
1313
1252
1314 def clearcaches():
1253 def clearcaches():
1315 """Clear caches associated with this collection."""
1254 """Clear caches associated with this collection."""
1316
1255
1317 def rev(node):
1256 def rev(node):
1318 """Obtain the revision number for a binary node.
1257 """Obtain the revision number for a binary node.
1319
1258
1320 Raises ``error.LookupError`` if the node is not known.
1259 Raises ``error.LookupError`` if the node is not known.
1321 """
1260 """
1322
1261
1323 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1262 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1324 """Local repository sub-interface providing access to tracked file storage.
1263 """Local repository sub-interface providing access to tracked file storage.
1325
1264
1326 This interface defines how a repository accesses storage for a single
1265 This interface defines how a repository accesses storage for a single
1327 tracked file path.
1266 tracked file path.
1328 """
1267 """
1329
1268
1330 def file(f):
1269 def file(f):
1331 """Obtain a filelog for a tracked path.
1270 """Obtain a filelog for a tracked path.
1332
1271
1333 The returned type conforms to the ``ifilestorage`` interface.
1272 The returned type conforms to the ``ifilestorage`` interface.
1334 """
1273 """
1335
1274
1336 class ilocalrepositorymain(interfaceutil.Interface):
1275 class ilocalrepositorymain(interfaceutil.Interface):
1337 """Main interface for local repositories.
1276 """Main interface for local repositories.
1338
1277
1339 This currently captures the reality of things - not how things should be.
1278 This currently captures the reality of things - not how things should be.
1340 """
1279 """
1341
1280
1342 supportedformats = interfaceutil.Attribute(
1281 supportedformats = interfaceutil.Attribute(
1343 """Set of requirements that apply to stream clone.
1282 """Set of requirements that apply to stream clone.
1344
1283
1345 This is actually a class attribute and is shared among all instances.
1284 This is actually a class attribute and is shared among all instances.
1346 """)
1285 """)
1347
1286
1348 supported = interfaceutil.Attribute(
1287 supported = interfaceutil.Attribute(
1349 """Set of requirements that this repo is capable of opening.""")
1288 """Set of requirements that this repo is capable of opening.""")
1350
1289
1351 requirements = interfaceutil.Attribute(
1290 requirements = interfaceutil.Attribute(
1352 """Set of requirements this repo uses.""")
1291 """Set of requirements this repo uses.""")
1353
1292
1354 features = interfaceutil.Attribute(
1293 features = interfaceutil.Attribute(
1355 """Set of "features" this repository supports.
1294 """Set of "features" this repository supports.
1356
1295
1357 A "feature" is a loosely-defined term. It can refer to a feature
1296 A "feature" is a loosely-defined term. It can refer to a feature
1358 in the classical sense or can describe an implementation detail
1297 in the classical sense or can describe an implementation detail
1359 of the repository. For example, a ``readonly`` feature may denote
1298 of the repository. For example, a ``readonly`` feature may denote
1360 the repository as read-only. Or a ``revlogfilestore`` feature may
1299 the repository as read-only. Or a ``revlogfilestore`` feature may
1361 denote that the repository is using revlogs for file storage.
1300 denote that the repository is using revlogs for file storage.
1362
1301
1363 The intent of features is to provide a machine-queryable mechanism
1302 The intent of features is to provide a machine-queryable mechanism
1364 for repo consumers to test for various repository characteristics.
1303 for repo consumers to test for various repository characteristics.
1365
1304
1366 Features are similar to ``requirements``. The main difference is that
1305 Features are similar to ``requirements``. The main difference is that
1367 requirements are stored on-disk and represent requirements to open the
1306 requirements are stored on-disk and represent requirements to open the
1368 repository. Features are more run-time capabilities of the repository
1307 repository. Features are more run-time capabilities of the repository
1369 and more granular capabilities (which may be derived from requirements).
1308 and more granular capabilities (which may be derived from requirements).
1370 """)
1309 """)
1371
1310
1372 filtername = interfaceutil.Attribute(
1311 filtername = interfaceutil.Attribute(
1373 """Name of the repoview that is active on this repo.""")
1312 """Name of the repoview that is active on this repo.""")
1374
1313
1375 wvfs = interfaceutil.Attribute(
1314 wvfs = interfaceutil.Attribute(
1376 """VFS used to access the working directory.""")
1315 """VFS used to access the working directory.""")
1377
1316
1378 vfs = interfaceutil.Attribute(
1317 vfs = interfaceutil.Attribute(
1379 """VFS rooted at the .hg directory.
1318 """VFS rooted at the .hg directory.
1380
1319
1381 Used to access repository data not in the store.
1320 Used to access repository data not in the store.
1382 """)
1321 """)
1383
1322
1384 svfs = interfaceutil.Attribute(
1323 svfs = interfaceutil.Attribute(
1385 """VFS rooted at the store.
1324 """VFS rooted at the store.
1386
1325
1387 Used to access repository data in the store. Typically .hg/store.
1326 Used to access repository data in the store. Typically .hg/store.
1388 But can point elsewhere if the store is shared.
1327 But can point elsewhere if the store is shared.
1389 """)
1328 """)
1390
1329
1391 root = interfaceutil.Attribute(
1330 root = interfaceutil.Attribute(
1392 """Path to the root of the working directory.""")
1331 """Path to the root of the working directory.""")
1393
1332
1394 path = interfaceutil.Attribute(
1333 path = interfaceutil.Attribute(
1395 """Path to the .hg directory.""")
1334 """Path to the .hg directory.""")
1396
1335
1397 origroot = interfaceutil.Attribute(
1336 origroot = interfaceutil.Attribute(
1398 """The filesystem path that was used to construct the repo.""")
1337 """The filesystem path that was used to construct the repo.""")
1399
1338
1400 auditor = interfaceutil.Attribute(
1339 auditor = interfaceutil.Attribute(
1401 """A pathauditor for the working directory.
1340 """A pathauditor for the working directory.
1402
1341
1403 This checks if a path refers to a nested repository.
1342 This checks if a path refers to a nested repository.
1404
1343
1405 Operates on the filesystem.
1344 Operates on the filesystem.
1406 """)
1345 """)
1407
1346
1408 nofsauditor = interfaceutil.Attribute(
1347 nofsauditor = interfaceutil.Attribute(
1409 """A pathauditor for the working directory.
1348 """A pathauditor for the working directory.
1410
1349
1411 This is like ``auditor`` except it doesn't do filesystem checks.
1350 This is like ``auditor`` except it doesn't do filesystem checks.
1412 """)
1351 """)
1413
1352
1414 baseui = interfaceutil.Attribute(
1353 baseui = interfaceutil.Attribute(
1415 """Original ui instance passed into constructor.""")
1354 """Original ui instance passed into constructor.""")
1416
1355
1417 ui = interfaceutil.Attribute(
1356 ui = interfaceutil.Attribute(
1418 """Main ui instance for this instance.""")
1357 """Main ui instance for this instance.""")
1419
1358
1420 sharedpath = interfaceutil.Attribute(
1359 sharedpath = interfaceutil.Attribute(
1421 """Path to the .hg directory of the repo this repo was shared from.""")
1360 """Path to the .hg directory of the repo this repo was shared from.""")
1422
1361
1423 store = interfaceutil.Attribute(
1362 store = interfaceutil.Attribute(
1424 """A store instance.""")
1363 """A store instance.""")
1425
1364
1426 spath = interfaceutil.Attribute(
1365 spath = interfaceutil.Attribute(
1427 """Path to the store.""")
1366 """Path to the store.""")
1428
1367
1429 sjoin = interfaceutil.Attribute(
1368 sjoin = interfaceutil.Attribute(
1430 """Alias to self.store.join.""")
1369 """Alias to self.store.join.""")
1431
1370
1432 cachevfs = interfaceutil.Attribute(
1371 cachevfs = interfaceutil.Attribute(
1433 """A VFS used to access the cache directory.
1372 """A VFS used to access the cache directory.
1434
1373
1435 Typically .hg/cache.
1374 Typically .hg/cache.
1436 """)
1375 """)
1437
1376
1438 filteredrevcache = interfaceutil.Attribute(
1377 filteredrevcache = interfaceutil.Attribute(
1439 """Holds sets of revisions to be filtered.""")
1378 """Holds sets of revisions to be filtered.""")
1440
1379
1441 names = interfaceutil.Attribute(
1380 names = interfaceutil.Attribute(
1442 """A ``namespaces`` instance.""")
1381 """A ``namespaces`` instance.""")
1443
1382
1444 def close():
1383 def close():
1445 """Close the handle on this repository."""
1384 """Close the handle on this repository."""
1446
1385
1447 def peer():
1386 def peer():
1448 """Obtain an object conforming to the ``peer`` interface."""
1387 """Obtain an object conforming to the ``peer`` interface."""
1449
1388
1450 def unfiltered():
1389 def unfiltered():
1451 """Obtain an unfiltered/raw view of this repo."""
1390 """Obtain an unfiltered/raw view of this repo."""
1452
1391
1453 def filtered(name, visibilityexceptions=None):
1392 def filtered(name, visibilityexceptions=None):
1454 """Obtain a named view of this repository."""
1393 """Obtain a named view of this repository."""
1455
1394
1456 obsstore = interfaceutil.Attribute(
1395 obsstore = interfaceutil.Attribute(
1457 """A store of obsolescence data.""")
1396 """A store of obsolescence data.""")
1458
1397
1459 changelog = interfaceutil.Attribute(
1398 changelog = interfaceutil.Attribute(
1460 """A handle on the changelog revlog.""")
1399 """A handle on the changelog revlog.""")
1461
1400
1462 manifestlog = interfaceutil.Attribute(
1401 manifestlog = interfaceutil.Attribute(
1463 """An instance conforming to the ``imanifestlog`` interface.
1402 """An instance conforming to the ``imanifestlog`` interface.
1464
1403
1465 Provides access to manifests for the repository.
1404 Provides access to manifests for the repository.
1466 """)
1405 """)
1467
1406
1468 dirstate = interfaceutil.Attribute(
1407 dirstate = interfaceutil.Attribute(
1469 """Working directory state.""")
1408 """Working directory state.""")
1470
1409
1471 narrowpats = interfaceutil.Attribute(
1410 narrowpats = interfaceutil.Attribute(
1472 """Matcher patterns for this repository's narrowspec.""")
1411 """Matcher patterns for this repository's narrowspec.""")
1473
1412
1474 def narrowmatch():
1413 def narrowmatch():
1475 """Obtain a matcher for the narrowspec."""
1414 """Obtain a matcher for the narrowspec."""
1476
1415
1477 def setnarrowpats(newincludes, newexcludes):
1416 def setnarrowpats(newincludes, newexcludes):
1478 """Define the narrowspec for this repository."""
1417 """Define the narrowspec for this repository."""
1479
1418
1480 def __getitem__(changeid):
1419 def __getitem__(changeid):
1481 """Try to resolve a changectx."""
1420 """Try to resolve a changectx."""
1482
1421
1483 def __contains__(changeid):
1422 def __contains__(changeid):
1484 """Whether a changeset exists."""
1423 """Whether a changeset exists."""
1485
1424
1486 def __nonzero__():
1425 def __nonzero__():
1487 """Always returns True."""
1426 """Always returns True."""
1488 return True
1427 return True
1489
1428
1490 __bool__ = __nonzero__
1429 __bool__ = __nonzero__
1491
1430
1492 def __len__():
1431 def __len__():
1493 """Returns the number of changesets in the repo."""
1432 """Returns the number of changesets in the repo."""
1494
1433
1495 def __iter__():
1434 def __iter__():
1496 """Iterate over revisions in the changelog."""
1435 """Iterate over revisions in the changelog."""
1497
1436
1498 def revs(expr, *args):
1437 def revs(expr, *args):
1499 """Evaluate a revset.
1438 """Evaluate a revset.
1500
1439
1501 Emits revisions.
1440 Emits revisions.
1502 """
1441 """
1503
1442
1504 def set(expr, *args):
1443 def set(expr, *args):
1505 """Evaluate a revset.
1444 """Evaluate a revset.
1506
1445
1507 Emits changectx instances.
1446 Emits changectx instances.
1508 """
1447 """
1509
1448
1510 def anyrevs(specs, user=False, localalias=None):
1449 def anyrevs(specs, user=False, localalias=None):
1511 """Find revisions matching one of the given revsets."""
1450 """Find revisions matching one of the given revsets."""
1512
1451
1513 def url():
1452 def url():
1514 """Returns a string representing the location of this repo."""
1453 """Returns a string representing the location of this repo."""
1515
1454
1516 def hook(name, throw=False, **args):
1455 def hook(name, throw=False, **args):
1517 """Call a hook."""
1456 """Call a hook."""
1518
1457
1519 def tags():
1458 def tags():
1520 """Return a mapping of tag to node."""
1459 """Return a mapping of tag to node."""
1521
1460
1522 def tagtype(tagname):
1461 def tagtype(tagname):
1523 """Return the type of a given tag."""
1462 """Return the type of a given tag."""
1524
1463
1525 def tagslist():
1464 def tagslist():
1526 """Return a list of tags ordered by revision."""
1465 """Return a list of tags ordered by revision."""
1527
1466
1528 def nodetags(node):
1467 def nodetags(node):
1529 """Return the tags associated with a node."""
1468 """Return the tags associated with a node."""
1530
1469
1531 def nodebookmarks(node):
1470 def nodebookmarks(node):
1532 """Return the list of bookmarks pointing to the specified node."""
1471 """Return the list of bookmarks pointing to the specified node."""
1533
1472
1534 def branchmap():
1473 def branchmap():
1535 """Return a mapping of branch to heads in that branch."""
1474 """Return a mapping of branch to heads in that branch."""
1536
1475
1537 def revbranchcache():
1476 def revbranchcache():
1538 pass
1477 pass
1539
1478
1540 def branchtip(branchtip, ignoremissing=False):
1479 def branchtip(branchtip, ignoremissing=False):
1541 """Return the tip node for a given branch."""
1480 """Return the tip node for a given branch."""
1542
1481
1543 def lookup(key):
1482 def lookup(key):
1544 """Resolve the node for a revision."""
1483 """Resolve the node for a revision."""
1545
1484
1546 def lookupbranch(key):
1485 def lookupbranch(key):
1547 """Look up the branch name of the given revision or branch name."""
1486 """Look up the branch name of the given revision or branch name."""
1548
1487
1549 def known(nodes):
1488 def known(nodes):
1550 """Determine whether a series of nodes is known.
1489 """Determine whether a series of nodes is known.
1551
1490
1552 Returns a list of bools.
1491 Returns a list of bools.
1553 """
1492 """
1554
1493
1555 def local():
1494 def local():
1556 """Whether the repository is local."""
1495 """Whether the repository is local."""
1557 return True
1496 return True
1558
1497
1559 def publishing():
1498 def publishing():
1560 """Whether the repository is a publishing repository."""
1499 """Whether the repository is a publishing repository."""
1561
1500
1562 def cancopy():
1501 def cancopy():
1563 pass
1502 pass
1564
1503
1565 def shared():
1504 def shared():
1566 """The type of shared repository or None."""
1505 """The type of shared repository or None."""
1567
1506
1568 def wjoin(f, *insidef):
1507 def wjoin(f, *insidef):
1569 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1508 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1570
1509
1571 def setparents(p1, p2):
1510 def setparents(p1, p2):
1572 """Set the parent nodes of the working directory."""
1511 """Set the parent nodes of the working directory."""
1573
1512
1574 def filectx(path, changeid=None, fileid=None):
1513 def filectx(path, changeid=None, fileid=None):
1575 """Obtain a filectx for the given file revision."""
1514 """Obtain a filectx for the given file revision."""
1576
1515
1577 def getcwd():
1516 def getcwd():
1578 """Obtain the current working directory from the dirstate."""
1517 """Obtain the current working directory from the dirstate."""
1579
1518
1580 def pathto(f, cwd=None):
1519 def pathto(f, cwd=None):
1581 """Obtain the relative path to a file."""
1520 """Obtain the relative path to a file."""
1582
1521
1583 def adddatafilter(name, fltr):
1522 def adddatafilter(name, fltr):
1584 pass
1523 pass
1585
1524
1586 def wread(filename):
1525 def wread(filename):
1587 """Read a file from wvfs, using data filters."""
1526 """Read a file from wvfs, using data filters."""
1588
1527
1589 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1528 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1590 """Write data to a file in the wvfs, using data filters."""
1529 """Write data to a file in the wvfs, using data filters."""
1591
1530
1592 def wwritedata(filename, data):
1531 def wwritedata(filename, data):
1593 """Resolve data for writing to the wvfs, using data filters."""
1532 """Resolve data for writing to the wvfs, using data filters."""
1594
1533
1595 def currenttransaction():
1534 def currenttransaction():
1596 """Obtain the current transaction instance or None."""
1535 """Obtain the current transaction instance or None."""
1597
1536
1598 def transaction(desc, report=None):
1537 def transaction(desc, report=None):
1599 """Open a new transaction to write to the repository."""
1538 """Open a new transaction to write to the repository."""
1600
1539
1601 def undofiles():
1540 def undofiles():
1602 """Returns a list of (vfs, path) for files to undo transactions."""
1541 """Returns a list of (vfs, path) for files to undo transactions."""
1603
1542
1604 def recover():
1543 def recover():
1605 """Roll back an interrupted transaction."""
1544 """Roll back an interrupted transaction."""
1606
1545
1607 def rollback(dryrun=False, force=False):
1546 def rollback(dryrun=False, force=False):
1608 """Undo the last transaction.
1547 """Undo the last transaction.
1609
1548
1610 DANGEROUS.
1549 DANGEROUS.
1611 """
1550 """
1612
1551
1613 def updatecaches(tr=None, full=False):
1552 def updatecaches(tr=None, full=False):
1614 """Warm repo caches."""
1553 """Warm repo caches."""
1615
1554
1616 def invalidatecaches():
1555 def invalidatecaches():
1617 """Invalidate cached data due to the repository mutating."""
1556 """Invalidate cached data due to the repository mutating."""
1618
1557
1619 def invalidatevolatilesets():
1558 def invalidatevolatilesets():
1620 pass
1559 pass
1621
1560
1622 def invalidatedirstate():
1561 def invalidatedirstate():
1623 """Invalidate the dirstate."""
1562 """Invalidate the dirstate."""
1624
1563
1625 def invalidate(clearfilecache=False):
1564 def invalidate(clearfilecache=False):
1626 pass
1565 pass
1627
1566
1628 def invalidateall():
1567 def invalidateall():
1629 pass
1568 pass
1630
1569
1631 def lock(wait=True):
1570 def lock(wait=True):
1632 """Lock the repository store and return a lock instance."""
1571 """Lock the repository store and return a lock instance."""
1633
1572
1634 def wlock(wait=True):
1573 def wlock(wait=True):
1635 """Lock the non-store parts of the repository."""
1574 """Lock the non-store parts of the repository."""
1636
1575
1637 def currentwlock():
1576 def currentwlock():
1638 """Return the wlock if it's held or None."""
1577 """Return the wlock if it's held or None."""
1639
1578
1640 def checkcommitpatterns(wctx, vdirs, match, status, fail):
1579 def checkcommitpatterns(wctx, vdirs, match, status, fail):
1641 pass
1580 pass
1642
1581
1643 def commit(text='', user=None, date=None, match=None, force=False,
1582 def commit(text='', user=None, date=None, match=None, force=False,
1644 editor=False, extra=None):
1583 editor=False, extra=None):
1645 """Add a new revision to the repository."""
1584 """Add a new revision to the repository."""
1646
1585
1647 def commitctx(ctx, error=False):
1586 def commitctx(ctx, error=False):
1648 """Commit a commitctx instance to the repository."""
1587 """Commit a commitctx instance to the repository."""
1649
1588
1650 def destroying():
1589 def destroying():
1651 """Inform the repository that nodes are about to be destroyed."""
1590 """Inform the repository that nodes are about to be destroyed."""
1652
1591
1653 def destroyed():
1592 def destroyed():
1654 """Inform the repository that nodes have been destroyed."""
1593 """Inform the repository that nodes have been destroyed."""
1655
1594
1656 def status(node1='.', node2=None, match=None, ignored=False,
1595 def status(node1='.', node2=None, match=None, ignored=False,
1657 clean=False, unknown=False, listsubrepos=False):
1596 clean=False, unknown=False, listsubrepos=False):
1658 """Convenience method to call repo[x].status()."""
1597 """Convenience method to call repo[x].status()."""
1659
1598
1660 def addpostdsstatus(ps):
1599 def addpostdsstatus(ps):
1661 pass
1600 pass
1662
1601
1663 def postdsstatus():
1602 def postdsstatus():
1664 pass
1603 pass
1665
1604
1666 def clearpostdsstatus():
1605 def clearpostdsstatus():
1667 pass
1606 pass
1668
1607
1669 def heads(start=None):
1608 def heads(start=None):
1670 """Obtain list of nodes that are DAG heads."""
1609 """Obtain list of nodes that are DAG heads."""
1671
1610
1672 def branchheads(branch=None, start=None, closed=False):
1611 def branchheads(branch=None, start=None, closed=False):
1673 pass
1612 pass
1674
1613
1675 def branches(nodes):
1614 def branches(nodes):
1676 pass
1615 pass
1677
1616
1678 def between(pairs):
1617 def between(pairs):
1679 pass
1618 pass
1680
1619
1681 def checkpush(pushop):
1620 def checkpush(pushop):
1682 pass
1621 pass
1683
1622
1684 prepushoutgoinghooks = interfaceutil.Attribute(
1623 prepushoutgoinghooks = interfaceutil.Attribute(
1685 """util.hooks instance.""")
1624 """util.hooks instance.""")
1686
1625
1687 def pushkey(namespace, key, old, new):
1626 def pushkey(namespace, key, old, new):
1688 pass
1627 pass
1689
1628
1690 def listkeys(namespace):
1629 def listkeys(namespace):
1691 pass
1630 pass
1692
1631
1693 def debugwireargs(one, two, three=None, four=None, five=None):
1632 def debugwireargs(one, two, three=None, four=None, five=None):
1694 pass
1633 pass
1695
1634
1696 def savecommitmessage(text):
1635 def savecommitmessage(text):
1697 pass
1636 pass
1698
1637
1699 class completelocalrepository(ilocalrepositorymain,
1638 class completelocalrepository(ilocalrepositorymain,
1700 ilocalrepositoryfilestorage):
1639 ilocalrepositoryfilestorage):
1701 """Complete interface for a local repository."""
1640 """Complete interface for a local repository."""
@@ -1,2726 +1,2645 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end 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 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import hashlib
19 import hashlib
20 import os
20 import os
21 import re
21 import re
22 import struct
22 import struct
23 import zlib
23 import zlib
24
24
25 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
26 from .node import (
26 from .node import (
27 bin,
27 bin,
28 hex,
28 hex,
29 nullhex,
29 nullhex,
30 nullid,
30 nullid,
31 nullrev,
31 nullrev,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .revlogutils.constants import (
38 from .revlogutils.constants import (
39 FLAG_GENERALDELTA,
39 FLAG_GENERALDELTA,
40 FLAG_INLINE_DATA,
40 FLAG_INLINE_DATA,
41 REVIDX_DEFAULT_FLAGS,
41 REVIDX_DEFAULT_FLAGS,
42 REVIDX_ELLIPSIS,
42 REVIDX_ELLIPSIS,
43 REVIDX_EXTSTORED,
43 REVIDX_EXTSTORED,
44 REVIDX_FLAGS_ORDER,
44 REVIDX_FLAGS_ORDER,
45 REVIDX_ISCENSORED,
45 REVIDX_ISCENSORED,
46 REVIDX_KNOWN_FLAGS,
46 REVIDX_KNOWN_FLAGS,
47 REVIDX_RAWTEXT_CHANGING_FLAGS,
47 REVIDX_RAWTEXT_CHANGING_FLAGS,
48 REVLOGV0,
48 REVLOGV0,
49 REVLOGV1,
49 REVLOGV1,
50 REVLOGV1_FLAGS,
50 REVLOGV1_FLAGS,
51 REVLOGV2,
51 REVLOGV2,
52 REVLOGV2_FLAGS,
52 REVLOGV2_FLAGS,
53 REVLOG_DEFAULT_FLAGS,
53 REVLOG_DEFAULT_FLAGS,
54 REVLOG_DEFAULT_FORMAT,
54 REVLOG_DEFAULT_FORMAT,
55 REVLOG_DEFAULT_VERSION,
55 REVLOG_DEFAULT_VERSION,
56 )
56 )
57 from .thirdparty import (
57 from .thirdparty import (
58 attr,
58 attr,
59 )
59 )
60 from . import (
60 from . import (
61 ancestor,
61 ancestor,
62 dagop,
62 dagop,
63 error,
63 error,
64 mdiff,
64 mdiff,
65 policy,
65 policy,
66 pycompat,
66 pycompat,
67 repository,
67 repository,
68 templatefilters,
68 templatefilters,
69 util,
69 util,
70 )
70 )
71 from .revlogutils import (
71 from .revlogutils import (
72 deltas as deltautil,
72 deltas as deltautil,
73 )
73 )
74 from .utils import (
74 from .utils import (
75 interfaceutil,
75 interfaceutil,
76 stringutil,
76 stringutil,
77 )
77 )
78
78
79 # blanked usage of all the name to prevent pyflakes constraints
79 # blanked usage of all the name to prevent pyflakes constraints
80 # We need these name available in the module for extensions.
80 # We need these name available in the module for extensions.
81 REVLOGV0
81 REVLOGV0
82 REVLOGV1
82 REVLOGV1
83 REVLOGV2
83 REVLOGV2
84 FLAG_INLINE_DATA
84 FLAG_INLINE_DATA
85 FLAG_GENERALDELTA
85 FLAG_GENERALDELTA
86 REVLOG_DEFAULT_FLAGS
86 REVLOG_DEFAULT_FLAGS
87 REVLOG_DEFAULT_FORMAT
87 REVLOG_DEFAULT_FORMAT
88 REVLOG_DEFAULT_VERSION
88 REVLOG_DEFAULT_VERSION
89 REVLOGV1_FLAGS
89 REVLOGV1_FLAGS
90 REVLOGV2_FLAGS
90 REVLOGV2_FLAGS
91 REVIDX_ISCENSORED
91 REVIDX_ISCENSORED
92 REVIDX_ELLIPSIS
92 REVIDX_ELLIPSIS
93 REVIDX_EXTSTORED
93 REVIDX_EXTSTORED
94 REVIDX_DEFAULT_FLAGS
94 REVIDX_DEFAULT_FLAGS
95 REVIDX_FLAGS_ORDER
95 REVIDX_FLAGS_ORDER
96 REVIDX_KNOWN_FLAGS
96 REVIDX_KNOWN_FLAGS
97 REVIDX_RAWTEXT_CHANGING_FLAGS
97 REVIDX_RAWTEXT_CHANGING_FLAGS
98
98
99 parsers = policy.importmod(r'parsers')
99 parsers = policy.importmod(r'parsers')
100
100
101 # Aliased for performance.
101 # Aliased for performance.
102 _zlibdecompress = zlib.decompress
102 _zlibdecompress = zlib.decompress
103
103
104 # max size of revlog with inline data
104 # max size of revlog with inline data
105 _maxinline = 131072
105 _maxinline = 131072
106 _chunksize = 1048576
106 _chunksize = 1048576
107
107
108 # Store flag processors (cf. 'addflagprocessor()' to register)
108 # Store flag processors (cf. 'addflagprocessor()' to register)
109 _flagprocessors = {
109 _flagprocessors = {
110 REVIDX_ISCENSORED: None,
110 REVIDX_ISCENSORED: None,
111 }
111 }
112
112
113 # Flag processors for REVIDX_ELLIPSIS.
113 # Flag processors for REVIDX_ELLIPSIS.
114 def ellipsisreadprocessor(rl, text):
114 def ellipsisreadprocessor(rl, text):
115 return text, False
115 return text, False
116
116
117 def ellipsiswriteprocessor(rl, text):
117 def ellipsiswriteprocessor(rl, text):
118 return text, False
118 return text, False
119
119
120 def ellipsisrawprocessor(rl, text):
120 def ellipsisrawprocessor(rl, text):
121 return False
121 return False
122
122
123 ellipsisprocessor = (
123 ellipsisprocessor = (
124 ellipsisreadprocessor,
124 ellipsisreadprocessor,
125 ellipsiswriteprocessor,
125 ellipsiswriteprocessor,
126 ellipsisrawprocessor,
126 ellipsisrawprocessor,
127 )
127 )
128
128
129 _mdre = re.compile('\1\n')
129 _mdre = re.compile('\1\n')
130 def parsemeta(text):
130 def parsemeta(text):
131 """return (metadatadict, metadatasize)"""
131 """return (metadatadict, metadatasize)"""
132 # text can be buffer, so we can't use .startswith or .index
132 # text can be buffer, so we can't use .startswith or .index
133 if text[:2] != '\1\n':
133 if text[:2] != '\1\n':
134 return None, None
134 return None, None
135 s = _mdre.search(text, 2).start()
135 s = _mdre.search(text, 2).start()
136 mtext = text[2:s]
136 mtext = text[2:s]
137 meta = {}
137 meta = {}
138 for l in mtext.splitlines():
138 for l in mtext.splitlines():
139 k, v = l.split(": ", 1)
139 k, v = l.split(": ", 1)
140 meta[k] = v
140 meta[k] = v
141 return meta, (s + 2)
141 return meta, (s + 2)
142
142
143 def packmeta(meta, text):
143 def packmeta(meta, text):
144 keys = sorted(meta)
144 keys = sorted(meta)
145 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
145 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
146 return "\1\n%s\1\n%s" % (metatext, text)
146 return "\1\n%s\1\n%s" % (metatext, text)
147
147
148 def _censoredtext(text):
148 def _censoredtext(text):
149 m, offs = parsemeta(text)
149 m, offs = parsemeta(text)
150 return m and "censored" in m
150 return m and "censored" in m
151
151
152 def addflagprocessor(flag, processor):
152 def addflagprocessor(flag, processor):
153 """Register a flag processor on a revision data flag.
153 """Register a flag processor on a revision data flag.
154
154
155 Invariant:
155 Invariant:
156 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
156 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
157 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
157 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
158 - Only one flag processor can be registered on a specific flag.
158 - Only one flag processor can be registered on a specific flag.
159 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
159 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
160 following signatures:
160 following signatures:
161 - (read) f(self, rawtext) -> text, bool
161 - (read) f(self, rawtext) -> text, bool
162 - (write) f(self, text) -> rawtext, bool
162 - (write) f(self, text) -> rawtext, bool
163 - (raw) f(self, rawtext) -> bool
163 - (raw) f(self, rawtext) -> bool
164 "text" is presented to the user. "rawtext" is stored in revlog data, not
164 "text" is presented to the user. "rawtext" is stored in revlog data, not
165 directly visible to the user.
165 directly visible to the user.
166 The boolean returned by these transforms is used to determine whether
166 The boolean returned by these transforms is used to determine whether
167 the returned text can be used for hash integrity checking. For example,
167 the returned text can be used for hash integrity checking. For example,
168 if "write" returns False, then "text" is used to generate hash. If
168 if "write" returns False, then "text" is used to generate hash. If
169 "write" returns True, that basically means "rawtext" returned by "write"
169 "write" returns True, that basically means "rawtext" returned by "write"
170 should be used to generate hash. Usually, "write" and "read" return
170 should be used to generate hash. Usually, "write" and "read" return
171 different booleans. And "raw" returns a same boolean as "write".
171 different booleans. And "raw" returns a same boolean as "write".
172
172
173 Note: The 'raw' transform is used for changegroup generation and in some
173 Note: The 'raw' transform is used for changegroup generation and in some
174 debug commands. In this case the transform only indicates whether the
174 debug commands. In this case the transform only indicates whether the
175 contents can be used for hash integrity checks.
175 contents can be used for hash integrity checks.
176 """
176 """
177 if not flag & REVIDX_KNOWN_FLAGS:
177 if not flag & REVIDX_KNOWN_FLAGS:
178 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
178 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
179 raise error.ProgrammingError(msg)
179 raise error.ProgrammingError(msg)
180 if flag not in REVIDX_FLAGS_ORDER:
180 if flag not in REVIDX_FLAGS_ORDER:
181 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
181 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
182 raise error.ProgrammingError(msg)
182 raise error.ProgrammingError(msg)
183 if flag in _flagprocessors:
183 if flag in _flagprocessors:
184 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
184 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
185 raise error.Abort(msg)
185 raise error.Abort(msg)
186 _flagprocessors[flag] = processor
186 _flagprocessors[flag] = processor
187
187
188 def getoffset(q):
188 def getoffset(q):
189 return int(q >> 16)
189 return int(q >> 16)
190
190
191 def gettype(q):
191 def gettype(q):
192 return int(q & 0xFFFF)
192 return int(q & 0xFFFF)
193
193
194 def offset_type(offset, type):
194 def offset_type(offset, type):
195 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
195 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
196 raise ValueError('unknown revlog index flags')
196 raise ValueError('unknown revlog index flags')
197 return int(int(offset) << 16 | type)
197 return int(int(offset) << 16 | type)
198
198
199 _nullhash = hashlib.sha1(nullid)
199 _nullhash = hashlib.sha1(nullid)
200
200
201 def hash(text, p1, p2):
201 def hash(text, p1, p2):
202 """generate a hash from the given text and its parent hashes
202 """generate a hash from the given text and its parent hashes
203
203
204 This hash combines both the current file contents and its history
204 This hash combines both the current file contents and its history
205 in a manner that makes it easy to distinguish nodes with the same
205 in a manner that makes it easy to distinguish nodes with the same
206 content in the revision graph.
206 content in the revision graph.
207 """
207 """
208 # As of now, if one of the parent node is null, p2 is null
208 # As of now, if one of the parent node is null, p2 is null
209 if p2 == nullid:
209 if p2 == nullid:
210 # deep copy of a hash is faster than creating one
210 # deep copy of a hash is faster than creating one
211 s = _nullhash.copy()
211 s = _nullhash.copy()
212 s.update(p1)
212 s.update(p1)
213 else:
213 else:
214 # none of the parent nodes are nullid
214 # none of the parent nodes are nullid
215 if p1 < p2:
215 if p1 < p2:
216 a = p1
216 a = p1
217 b = p2
217 b = p2
218 else:
218 else:
219 a = p2
219 a = p2
220 b = p1
220 b = p1
221 s = hashlib.sha1(a)
221 s = hashlib.sha1(a)
222 s.update(b)
222 s.update(b)
223 s.update(text)
223 s.update(text)
224 return s.digest()
224 return s.digest()
225
225
226 @attr.s(slots=True, frozen=True)
226 @attr.s(slots=True, frozen=True)
227 class _revisioninfo(object):
227 class _revisioninfo(object):
228 """Information about a revision that allows building its fulltext
228 """Information about a revision that allows building its fulltext
229 node: expected hash of the revision
229 node: expected hash of the revision
230 p1, p2: parent revs of the revision
230 p1, p2: parent revs of the revision
231 btext: built text cache consisting of a one-element list
231 btext: built text cache consisting of a one-element list
232 cachedelta: (baserev, uncompressed_delta) or None
232 cachedelta: (baserev, uncompressed_delta) or None
233 flags: flags associated to the revision storage
233 flags: flags associated to the revision storage
234
234
235 One of btext[0] or cachedelta must be set.
235 One of btext[0] or cachedelta must be set.
236 """
236 """
237 node = attr.ib()
237 node = attr.ib()
238 p1 = attr.ib()
238 p1 = attr.ib()
239 p2 = attr.ib()
239 p2 = attr.ib()
240 btext = attr.ib()
240 btext = attr.ib()
241 textlen = attr.ib()
241 textlen = attr.ib()
242 cachedelta = attr.ib()
242 cachedelta = attr.ib()
243 flags = attr.ib()
243 flags = attr.ib()
244
244
245 @interfaceutil.implementer(repository.irevisiondelta)
245 @interfaceutil.implementer(repository.irevisiondelta)
246 @attr.s(slots=True)
246 @attr.s(slots=True)
247 class revlogrevisiondelta(object):
247 class revlogrevisiondelta(object):
248 node = attr.ib()
248 node = attr.ib()
249 p1node = attr.ib()
249 p1node = attr.ib()
250 p2node = attr.ib()
250 p2node = attr.ib()
251 basenode = attr.ib()
251 basenode = attr.ib()
252 flags = attr.ib()
252 flags = attr.ib()
253 baserevisionsize = attr.ib()
253 baserevisionsize = attr.ib()
254 revision = attr.ib()
254 revision = attr.ib()
255 delta = attr.ib()
255 delta = attr.ib()
256 linknode = attr.ib(default=None)
256 linknode = attr.ib(default=None)
257
257
258 @interfaceutil.implementer(repository.iverifyproblem)
258 @interfaceutil.implementer(repository.iverifyproblem)
259 @attr.s(frozen=True)
259 @attr.s(frozen=True)
260 class revlogproblem(object):
260 class revlogproblem(object):
261 warning = attr.ib(default=None)
261 warning = attr.ib(default=None)
262 error = attr.ib(default=None)
262 error = attr.ib(default=None)
263
263
264 # index v0:
264 # index v0:
265 # 4 bytes: offset
265 # 4 bytes: offset
266 # 4 bytes: compressed length
266 # 4 bytes: compressed length
267 # 4 bytes: base rev
267 # 4 bytes: base rev
268 # 4 bytes: link rev
268 # 4 bytes: link rev
269 # 20 bytes: parent 1 nodeid
269 # 20 bytes: parent 1 nodeid
270 # 20 bytes: parent 2 nodeid
270 # 20 bytes: parent 2 nodeid
271 # 20 bytes: nodeid
271 # 20 bytes: nodeid
272 indexformatv0 = struct.Struct(">4l20s20s20s")
272 indexformatv0 = struct.Struct(">4l20s20s20s")
273 indexformatv0_pack = indexformatv0.pack
273 indexformatv0_pack = indexformatv0.pack
274 indexformatv0_unpack = indexformatv0.unpack
274 indexformatv0_unpack = indexformatv0.unpack
275
275
276 class revlogoldindex(list):
276 class revlogoldindex(list):
277 def __getitem__(self, i):
277 def __getitem__(self, i):
278 if i == -1:
278 if i == -1:
279 return (0, 0, 0, -1, -1, -1, -1, nullid)
279 return (0, 0, 0, -1, -1, -1, -1, nullid)
280 return list.__getitem__(self, i)
280 return list.__getitem__(self, i)
281
281
282 class revlogoldio(object):
282 class revlogoldio(object):
283 def __init__(self):
283 def __init__(self):
284 self.size = indexformatv0.size
284 self.size = indexformatv0.size
285
285
286 def parseindex(self, data, inline):
286 def parseindex(self, data, inline):
287 s = self.size
287 s = self.size
288 index = []
288 index = []
289 nodemap = {nullid: nullrev}
289 nodemap = {nullid: nullrev}
290 n = off = 0
290 n = off = 0
291 l = len(data)
291 l = len(data)
292 while off + s <= l:
292 while off + s <= l:
293 cur = data[off:off + s]
293 cur = data[off:off + s]
294 off += s
294 off += s
295 e = indexformatv0_unpack(cur)
295 e = indexformatv0_unpack(cur)
296 # transform to revlogv1 format
296 # transform to revlogv1 format
297 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
297 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
298 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
298 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
299 index.append(e2)
299 index.append(e2)
300 nodemap[e[6]] = n
300 nodemap[e[6]] = n
301 n += 1
301 n += 1
302
302
303 return revlogoldindex(index), nodemap, None
303 return revlogoldindex(index), nodemap, None
304
304
305 def packentry(self, entry, node, version, rev):
305 def packentry(self, entry, node, version, rev):
306 if gettype(entry[0]):
306 if gettype(entry[0]):
307 raise error.RevlogError(_('index entry flags need revlog '
307 raise error.RevlogError(_('index entry flags need revlog '
308 'version 1'))
308 'version 1'))
309 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
309 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
310 node(entry[5]), node(entry[6]), entry[7])
310 node(entry[5]), node(entry[6]), entry[7])
311 return indexformatv0_pack(*e2)
311 return indexformatv0_pack(*e2)
312
312
313 # index ng:
313 # index ng:
314 # 6 bytes: offset
314 # 6 bytes: offset
315 # 2 bytes: flags
315 # 2 bytes: flags
316 # 4 bytes: compressed length
316 # 4 bytes: compressed length
317 # 4 bytes: uncompressed length
317 # 4 bytes: uncompressed length
318 # 4 bytes: base rev
318 # 4 bytes: base rev
319 # 4 bytes: link rev
319 # 4 bytes: link rev
320 # 4 bytes: parent 1 rev
320 # 4 bytes: parent 1 rev
321 # 4 bytes: parent 2 rev
321 # 4 bytes: parent 2 rev
322 # 32 bytes: nodeid
322 # 32 bytes: nodeid
323 indexformatng = struct.Struct(">Qiiiiii20s12x")
323 indexformatng = struct.Struct(">Qiiiiii20s12x")
324 indexformatng_pack = indexformatng.pack
324 indexformatng_pack = indexformatng.pack
325 versionformat = struct.Struct(">I")
325 versionformat = struct.Struct(">I")
326 versionformat_pack = versionformat.pack
326 versionformat_pack = versionformat.pack
327 versionformat_unpack = versionformat.unpack
327 versionformat_unpack = versionformat.unpack
328
328
329 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
329 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
330 # signed integer)
330 # signed integer)
331 _maxentrysize = 0x7fffffff
331 _maxentrysize = 0x7fffffff
332
332
333 class revlogio(object):
333 class revlogio(object):
334 def __init__(self):
334 def __init__(self):
335 self.size = indexformatng.size
335 self.size = indexformatng.size
336
336
337 def parseindex(self, data, inline):
337 def parseindex(self, data, inline):
338 # call the C implementation to parse the index data
338 # call the C implementation to parse the index data
339 index, cache = parsers.parse_index2(data, inline)
339 index, cache = parsers.parse_index2(data, inline)
340 return index, getattr(index, 'nodemap', None), cache
340 return index, getattr(index, 'nodemap', None), cache
341
341
342 def packentry(self, entry, node, version, rev):
342 def packentry(self, entry, node, version, rev):
343 p = indexformatng_pack(*entry)
343 p = indexformatng_pack(*entry)
344 if rev == 0:
344 if rev == 0:
345 p = versionformat_pack(version) + p[4:]
345 p = versionformat_pack(version) + p[4:]
346 return p
346 return p
347
347
348 class revlog(object):
348 class revlog(object):
349 """
349 """
350 the underlying revision storage object
350 the underlying revision storage object
351
351
352 A revlog consists of two parts, an index and the revision data.
352 A revlog consists of two parts, an index and the revision data.
353
353
354 The index is a file with a fixed record size containing
354 The index is a file with a fixed record size containing
355 information on each revision, including its nodeid (hash), the
355 information on each revision, including its nodeid (hash), the
356 nodeids of its parents, the position and offset of its data within
356 nodeids of its parents, the position and offset of its data within
357 the data file, and the revision it's based on. Finally, each entry
357 the data file, and the revision it's based on. Finally, each entry
358 contains a linkrev entry that can serve as a pointer to external
358 contains a linkrev entry that can serve as a pointer to external
359 data.
359 data.
360
360
361 The revision data itself is a linear collection of data chunks.
361 The revision data itself is a linear collection of data chunks.
362 Each chunk represents a revision and is usually represented as a
362 Each chunk represents a revision and is usually represented as a
363 delta against the previous chunk. To bound lookup time, runs of
363 delta against the previous chunk. To bound lookup time, runs of
364 deltas are limited to about 2 times the length of the original
364 deltas are limited to about 2 times the length of the original
365 version data. This makes retrieval of a version proportional to
365 version data. This makes retrieval of a version proportional to
366 its size, or O(1) relative to the number of revisions.
366 its size, or O(1) relative to the number of revisions.
367
367
368 Both pieces of the revlog are written to in an append-only
368 Both pieces of the revlog are written to in an append-only
369 fashion, which means we never need to rewrite a file to insert or
369 fashion, which means we never need to rewrite a file to insert or
370 remove data, and can use some simple techniques to avoid the need
370 remove data, and can use some simple techniques to avoid the need
371 for locking while reading.
371 for locking while reading.
372
372
373 If checkambig, indexfile is opened with checkambig=True at
373 If checkambig, indexfile is opened with checkambig=True at
374 writing, to avoid file stat ambiguity.
374 writing, to avoid file stat ambiguity.
375
375
376 If mmaplargeindex is True, and an mmapindexthreshold is set, the
376 If mmaplargeindex is True, and an mmapindexthreshold is set, the
377 index will be mmapped rather than read if it is larger than the
377 index will be mmapped rather than read if it is larger than the
378 configured threshold.
378 configured threshold.
379
379
380 If censorable is True, the revlog can have censored revisions.
380 If censorable is True, the revlog can have censored revisions.
381 """
381 """
382 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
382 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
383 mmaplargeindex=False, censorable=False):
383 mmaplargeindex=False, censorable=False):
384 """
384 """
385 create a revlog object
385 create a revlog object
386
386
387 opener is a function that abstracts the file opening operation
387 opener is a function that abstracts the file opening operation
388 and can be used to implement COW semantics or the like.
388 and can be used to implement COW semantics or the like.
389 """
389 """
390 self.indexfile = indexfile
390 self.indexfile = indexfile
391 self.datafile = datafile or (indexfile[:-2] + ".d")
391 self.datafile = datafile or (indexfile[:-2] + ".d")
392 self.opener = opener
392 self.opener = opener
393 # When True, indexfile is opened with checkambig=True at writing, to
393 # When True, indexfile is opened with checkambig=True at writing, to
394 # avoid file stat ambiguity.
394 # avoid file stat ambiguity.
395 self._checkambig = checkambig
395 self._checkambig = checkambig
396 self._censorable = censorable
396 self._censorable = censorable
397 # 3-tuple of (node, rev, text) for a raw revision.
397 # 3-tuple of (node, rev, text) for a raw revision.
398 self._cache = None
398 self._cache = None
399 # Maps rev to chain base rev.
399 # Maps rev to chain base rev.
400 self._chainbasecache = util.lrucachedict(100)
400 self._chainbasecache = util.lrucachedict(100)
401 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
401 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
402 self._chunkcache = (0, '')
402 self._chunkcache = (0, '')
403 # How much data to read and cache into the raw revlog data cache.
403 # How much data to read and cache into the raw revlog data cache.
404 self._chunkcachesize = 65536
404 self._chunkcachesize = 65536
405 self._maxchainlen = None
405 self._maxchainlen = None
406 self._deltabothparents = True
406 self._deltabothparents = True
407 self.index = []
407 self.index = []
408 # Mapping of partial identifiers to full nodes.
408 # Mapping of partial identifiers to full nodes.
409 self._pcache = {}
409 self._pcache = {}
410 # Mapping of revision integer to full node.
410 # Mapping of revision integer to full node.
411 self._nodecache = {nullid: nullrev}
411 self._nodecache = {nullid: nullrev}
412 self._nodepos = None
412 self._nodepos = None
413 self._compengine = 'zlib'
413 self._compengine = 'zlib'
414 self._maxdeltachainspan = -1
414 self._maxdeltachainspan = -1
415 self._withsparseread = False
415 self._withsparseread = False
416 self._sparserevlog = False
416 self._sparserevlog = False
417 self._srdensitythreshold = 0.50
417 self._srdensitythreshold = 0.50
418 self._srmingapsize = 262144
418 self._srmingapsize = 262144
419
419
420 # Make copy of flag processors so each revlog instance can support
420 # Make copy of flag processors so each revlog instance can support
421 # custom flags.
421 # custom flags.
422 self._flagprocessors = dict(_flagprocessors)
422 self._flagprocessors = dict(_flagprocessors)
423
423
424 mmapindexthreshold = None
424 mmapindexthreshold = None
425 v = REVLOG_DEFAULT_VERSION
425 v = REVLOG_DEFAULT_VERSION
426 opts = getattr(opener, 'options', None)
426 opts = getattr(opener, 'options', None)
427 if opts is not None:
427 if opts is not None:
428 if 'revlogv2' in opts:
428 if 'revlogv2' in opts:
429 # version 2 revlogs always use generaldelta.
429 # version 2 revlogs always use generaldelta.
430 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
430 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
431 elif 'revlogv1' in opts:
431 elif 'revlogv1' in opts:
432 if 'generaldelta' in opts:
432 if 'generaldelta' in opts:
433 v |= FLAG_GENERALDELTA
433 v |= FLAG_GENERALDELTA
434 else:
434 else:
435 v = 0
435 v = 0
436 if 'chunkcachesize' in opts:
436 if 'chunkcachesize' in opts:
437 self._chunkcachesize = opts['chunkcachesize']
437 self._chunkcachesize = opts['chunkcachesize']
438 if 'maxchainlen' in opts:
438 if 'maxchainlen' in opts:
439 self._maxchainlen = opts['maxchainlen']
439 self._maxchainlen = opts['maxchainlen']
440 if 'deltabothparents' in opts:
440 if 'deltabothparents' in opts:
441 self._deltabothparents = opts['deltabothparents']
441 self._deltabothparents = opts['deltabothparents']
442 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
442 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
443 if 'compengine' in opts:
443 if 'compengine' in opts:
444 self._compengine = opts['compengine']
444 self._compengine = opts['compengine']
445 if 'maxdeltachainspan' in opts:
445 if 'maxdeltachainspan' in opts:
446 self._maxdeltachainspan = opts['maxdeltachainspan']
446 self._maxdeltachainspan = opts['maxdeltachainspan']
447 if mmaplargeindex and 'mmapindexthreshold' in opts:
447 if mmaplargeindex and 'mmapindexthreshold' in opts:
448 mmapindexthreshold = opts['mmapindexthreshold']
448 mmapindexthreshold = opts['mmapindexthreshold']
449 self._sparserevlog = bool(opts.get('sparse-revlog', False))
449 self._sparserevlog = bool(opts.get('sparse-revlog', False))
450 withsparseread = bool(opts.get('with-sparse-read', False))
450 withsparseread = bool(opts.get('with-sparse-read', False))
451 # sparse-revlog forces sparse-read
451 # sparse-revlog forces sparse-read
452 self._withsparseread = self._sparserevlog or withsparseread
452 self._withsparseread = self._sparserevlog or withsparseread
453 if 'sparse-read-density-threshold' in opts:
453 if 'sparse-read-density-threshold' in opts:
454 self._srdensitythreshold = opts['sparse-read-density-threshold']
454 self._srdensitythreshold = opts['sparse-read-density-threshold']
455 if 'sparse-read-min-gap-size' in opts:
455 if 'sparse-read-min-gap-size' in opts:
456 self._srmingapsize = opts['sparse-read-min-gap-size']
456 self._srmingapsize = opts['sparse-read-min-gap-size']
457 if opts.get('enableellipsis'):
457 if opts.get('enableellipsis'):
458 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
458 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
459
459
460 if self._chunkcachesize <= 0:
460 if self._chunkcachesize <= 0:
461 raise error.RevlogError(_('revlog chunk cache size %r is not '
461 raise error.RevlogError(_('revlog chunk cache size %r is not '
462 'greater than 0') % self._chunkcachesize)
462 'greater than 0') % self._chunkcachesize)
463 elif self._chunkcachesize & (self._chunkcachesize - 1):
463 elif self._chunkcachesize & (self._chunkcachesize - 1):
464 raise error.RevlogError(_('revlog chunk cache size %r is not a '
464 raise error.RevlogError(_('revlog chunk cache size %r is not a '
465 'power of 2') % self._chunkcachesize)
465 'power of 2') % self._chunkcachesize)
466
466
467 indexdata = ''
467 indexdata = ''
468 self._initempty = True
468 self._initempty = True
469 try:
469 try:
470 with self._indexfp() as f:
470 with self._indexfp() as f:
471 if (mmapindexthreshold is not None and
471 if (mmapindexthreshold is not None and
472 self.opener.fstat(f).st_size >= mmapindexthreshold):
472 self.opener.fstat(f).st_size >= mmapindexthreshold):
473 indexdata = util.buffer(util.mmapread(f))
473 indexdata = util.buffer(util.mmapread(f))
474 else:
474 else:
475 indexdata = f.read()
475 indexdata = f.read()
476 if len(indexdata) > 0:
476 if len(indexdata) > 0:
477 v = versionformat_unpack(indexdata[:4])[0]
477 v = versionformat_unpack(indexdata[:4])[0]
478 self._initempty = False
478 self._initempty = False
479 except IOError as inst:
479 except IOError as inst:
480 if inst.errno != errno.ENOENT:
480 if inst.errno != errno.ENOENT:
481 raise
481 raise
482
482
483 self.version = v
483 self.version = v
484 self._inline = v & FLAG_INLINE_DATA
484 self._inline = v & FLAG_INLINE_DATA
485 self._generaldelta = v & FLAG_GENERALDELTA
485 self._generaldelta = v & FLAG_GENERALDELTA
486 flags = v & ~0xFFFF
486 flags = v & ~0xFFFF
487 fmt = v & 0xFFFF
487 fmt = v & 0xFFFF
488 if fmt == REVLOGV0:
488 if fmt == REVLOGV0:
489 if flags:
489 if flags:
490 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
490 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
491 'revlog %s') %
491 'revlog %s') %
492 (flags >> 16, fmt, self.indexfile))
492 (flags >> 16, fmt, self.indexfile))
493 elif fmt == REVLOGV1:
493 elif fmt == REVLOGV1:
494 if flags & ~REVLOGV1_FLAGS:
494 if flags & ~REVLOGV1_FLAGS:
495 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
495 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
496 'revlog %s') %
496 'revlog %s') %
497 (flags >> 16, fmt, self.indexfile))
497 (flags >> 16, fmt, self.indexfile))
498 elif fmt == REVLOGV2:
498 elif fmt == REVLOGV2:
499 if flags & ~REVLOGV2_FLAGS:
499 if flags & ~REVLOGV2_FLAGS:
500 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
500 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
501 'revlog %s') %
501 'revlog %s') %
502 (flags >> 16, fmt, self.indexfile))
502 (flags >> 16, fmt, self.indexfile))
503 else:
503 else:
504 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
504 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
505 (fmt, self.indexfile))
505 (fmt, self.indexfile))
506
506
507 self._storedeltachains = True
507 self._storedeltachains = True
508
508
509 self._io = revlogio()
509 self._io = revlogio()
510 if self.version == REVLOGV0:
510 if self.version == REVLOGV0:
511 self._io = revlogoldio()
511 self._io = revlogoldio()
512 try:
512 try:
513 d = self._io.parseindex(indexdata, self._inline)
513 d = self._io.parseindex(indexdata, self._inline)
514 except (ValueError, IndexError):
514 except (ValueError, IndexError):
515 raise error.RevlogError(_("index %s is corrupted") %
515 raise error.RevlogError(_("index %s is corrupted") %
516 self.indexfile)
516 self.indexfile)
517 self.index, nodemap, self._chunkcache = d
517 self.index, nodemap, self._chunkcache = d
518 if nodemap is not None:
518 if nodemap is not None:
519 self.nodemap = self._nodecache = nodemap
519 self.nodemap = self._nodecache = nodemap
520 if not self._chunkcache:
520 if not self._chunkcache:
521 self._chunkclear()
521 self._chunkclear()
522 # revnum -> (chain-length, sum-delta-length)
522 # revnum -> (chain-length, sum-delta-length)
523 self._chaininfocache = {}
523 self._chaininfocache = {}
524 # revlog header -> revlog compressor
524 # revlog header -> revlog compressor
525 self._decompressors = {}
525 self._decompressors = {}
526
526
527 @util.propertycache
527 @util.propertycache
528 def _compressor(self):
528 def _compressor(self):
529 return util.compengines[self._compengine].revlogcompressor()
529 return util.compengines[self._compengine].revlogcompressor()
530
530
531 def _indexfp(self, mode='r'):
531 def _indexfp(self, mode='r'):
532 """file object for the revlog's index file"""
532 """file object for the revlog's index file"""
533 args = {r'mode': mode}
533 args = {r'mode': mode}
534 if mode != 'r':
534 if mode != 'r':
535 args[r'checkambig'] = self._checkambig
535 args[r'checkambig'] = self._checkambig
536 if mode == 'w':
536 if mode == 'w':
537 args[r'atomictemp'] = True
537 args[r'atomictemp'] = True
538 return self.opener(self.indexfile, **args)
538 return self.opener(self.indexfile, **args)
539
539
540 def _datafp(self, mode='r'):
540 def _datafp(self, mode='r'):
541 """file object for the revlog's data file"""
541 """file object for the revlog's data file"""
542 return self.opener(self.datafile, mode=mode)
542 return self.opener(self.datafile, mode=mode)
543
543
544 @contextlib.contextmanager
544 @contextlib.contextmanager
545 def _datareadfp(self, existingfp=None):
545 def _datareadfp(self, existingfp=None):
546 """file object suitable to read data"""
546 """file object suitable to read data"""
547 if existingfp is not None:
547 if existingfp is not None:
548 yield existingfp
548 yield existingfp
549 else:
549 else:
550 if self._inline:
550 if self._inline:
551 func = self._indexfp
551 func = self._indexfp
552 else:
552 else:
553 func = self._datafp
553 func = self._datafp
554 with func() as fp:
554 with func() as fp:
555 yield fp
555 yield fp
556
556
557 def tip(self):
557 def tip(self):
558 return self.node(len(self.index) - 1)
558 return self.node(len(self.index) - 1)
559 def __contains__(self, rev):
559 def __contains__(self, rev):
560 return 0 <= rev < len(self)
560 return 0 <= rev < len(self)
561 def __len__(self):
561 def __len__(self):
562 return len(self.index)
562 return len(self.index)
563 def __iter__(self):
563 def __iter__(self):
564 return iter(pycompat.xrange(len(self)))
564 return iter(pycompat.xrange(len(self)))
565 def revs(self, start=0, stop=None):
565 def revs(self, start=0, stop=None):
566 """iterate over all rev in this revlog (from start to stop)"""
566 """iterate over all rev in this revlog (from start to stop)"""
567 step = 1
567 step = 1
568 length = len(self)
568 length = len(self)
569 if stop is not None:
569 if stop is not None:
570 if start > stop:
570 if start > stop:
571 step = -1
571 step = -1
572 stop += step
572 stop += step
573 if stop > length:
573 if stop > length:
574 stop = length
574 stop = length
575 else:
575 else:
576 stop = length
576 stop = length
577 return pycompat.xrange(start, stop, step)
577 return pycompat.xrange(start, stop, step)
578
578
579 @util.propertycache
579 @util.propertycache
580 def nodemap(self):
580 def nodemap(self):
581 if self.index:
581 if self.index:
582 # populate mapping down to the initial node
582 # populate mapping down to the initial node
583 node0 = self.index[0][7] # get around changelog filtering
583 node0 = self.index[0][7] # get around changelog filtering
584 self.rev(node0)
584 self.rev(node0)
585 return self._nodecache
585 return self._nodecache
586
586
587 def hasnode(self, node):
587 def hasnode(self, node):
588 try:
588 try:
589 self.rev(node)
589 self.rev(node)
590 return True
590 return True
591 except KeyError:
591 except KeyError:
592 return False
592 return False
593
593
594 def candelta(self, baserev, rev):
594 def candelta(self, baserev, rev):
595 """whether two revisions (baserev, rev) can be delta-ed or not"""
595 """whether two revisions (baserev, rev) can be delta-ed or not"""
596 # Disable delta if either rev requires a content-changing flag
596 # Disable delta if either rev requires a content-changing flag
597 # processor (ex. LFS). This is because such flag processor can alter
597 # processor (ex. LFS). This is because such flag processor can alter
598 # the rawtext content that the delta will be based on, and two clients
598 # the rawtext content that the delta will be based on, and two clients
599 # could have a same revlog node with different flags (i.e. different
599 # could have a same revlog node with different flags (i.e. different
600 # rawtext contents) and the delta could be incompatible.
600 # rawtext contents) and the delta could be incompatible.
601 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
601 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
602 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
602 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
603 return False
603 return False
604 return True
604 return True
605
605
606 def clearcaches(self):
606 def clearcaches(self):
607 self._cache = None
607 self._cache = None
608 self._chainbasecache.clear()
608 self._chainbasecache.clear()
609 self._chunkcache = (0, '')
609 self._chunkcache = (0, '')
610 self._pcache = {}
610 self._pcache = {}
611
611
612 try:
612 try:
613 self._nodecache.clearcaches()
613 self._nodecache.clearcaches()
614 except AttributeError:
614 except AttributeError:
615 self._nodecache = {nullid: nullrev}
615 self._nodecache = {nullid: nullrev}
616 self._nodepos = None
616 self._nodepos = None
617
617
618 def rev(self, node):
618 def rev(self, node):
619 try:
619 try:
620 return self._nodecache[node]
620 return self._nodecache[node]
621 except TypeError:
621 except TypeError:
622 raise
622 raise
623 except error.RevlogError:
623 except error.RevlogError:
624 # parsers.c radix tree lookup failed
624 # parsers.c radix tree lookup failed
625 if node == wdirid or node in wdirfilenodeids:
625 if node == wdirid or node in wdirfilenodeids:
626 raise error.WdirUnsupported
626 raise error.WdirUnsupported
627 raise error.LookupError(node, self.indexfile, _('no node'))
627 raise error.LookupError(node, self.indexfile, _('no node'))
628 except KeyError:
628 except KeyError:
629 # pure python cache lookup failed
629 # pure python cache lookup failed
630 n = self._nodecache
630 n = self._nodecache
631 i = self.index
631 i = self.index
632 p = self._nodepos
632 p = self._nodepos
633 if p is None:
633 if p is None:
634 p = len(i) - 1
634 p = len(i) - 1
635 else:
635 else:
636 assert p < len(i)
636 assert p < len(i)
637 for r in pycompat.xrange(p, -1, -1):
637 for r in pycompat.xrange(p, -1, -1):
638 v = i[r][7]
638 v = i[r][7]
639 n[v] = r
639 n[v] = r
640 if v == node:
640 if v == node:
641 self._nodepos = r - 1
641 self._nodepos = r - 1
642 return r
642 return r
643 if node == wdirid or node in wdirfilenodeids:
643 if node == wdirid or node in wdirfilenodeids:
644 raise error.WdirUnsupported
644 raise error.WdirUnsupported
645 raise error.LookupError(node, self.indexfile, _('no node'))
645 raise error.LookupError(node, self.indexfile, _('no node'))
646
646
647 # Accessors for index entries.
647 # Accessors for index entries.
648
648
649 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
649 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
650 # are flags.
650 # are flags.
651 def start(self, rev):
651 def start(self, rev):
652 return int(self.index[rev][0] >> 16)
652 return int(self.index[rev][0] >> 16)
653
653
654 def flags(self, rev):
654 def flags(self, rev):
655 return self.index[rev][0] & 0xFFFF
655 return self.index[rev][0] & 0xFFFF
656
656
657 def length(self, rev):
657 def length(self, rev):
658 return self.index[rev][1]
658 return self.index[rev][1]
659
659
660 def rawsize(self, rev):
660 def rawsize(self, rev):
661 """return the length of the uncompressed text for a given revision"""
661 """return the length of the uncompressed text for a given revision"""
662 l = self.index[rev][2]
662 l = self.index[rev][2]
663 if l >= 0:
663 if l >= 0:
664 return l
664 return l
665
665
666 t = self.revision(rev, raw=True)
666 t = self.revision(rev, raw=True)
667 return len(t)
667 return len(t)
668
668
669 def size(self, rev):
669 def size(self, rev):
670 """length of non-raw text (processed by a "read" flag processor)"""
670 """length of non-raw text (processed by a "read" flag processor)"""
671 # fast path: if no "read" flag processor could change the content,
671 # fast path: if no "read" flag processor could change the content,
672 # size is rawsize. note: ELLIPSIS is known to not change the content.
672 # size is rawsize. note: ELLIPSIS is known to not change the content.
673 flags = self.flags(rev)
673 flags = self.flags(rev)
674 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
674 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
675 return self.rawsize(rev)
675 return self.rawsize(rev)
676
676
677 return len(self.revision(rev, raw=False))
677 return len(self.revision(rev, raw=False))
678
678
679 def chainbase(self, rev):
679 def chainbase(self, rev):
680 base = self._chainbasecache.get(rev)
680 base = self._chainbasecache.get(rev)
681 if base is not None:
681 if base is not None:
682 return base
682 return base
683
683
684 index = self.index
684 index = self.index
685 iterrev = rev
685 iterrev = rev
686 base = index[iterrev][3]
686 base = index[iterrev][3]
687 while base != iterrev:
687 while base != iterrev:
688 iterrev = base
688 iterrev = base
689 base = index[iterrev][3]
689 base = index[iterrev][3]
690
690
691 self._chainbasecache[rev] = base
691 self._chainbasecache[rev] = base
692 return base
692 return base
693
693
694 def linkrev(self, rev):
694 def linkrev(self, rev):
695 return self.index[rev][4]
695 return self.index[rev][4]
696
696
697 def parentrevs(self, rev):
697 def parentrevs(self, rev):
698 try:
698 try:
699 entry = self.index[rev]
699 entry = self.index[rev]
700 except IndexError:
700 except IndexError:
701 if rev == wdirrev:
701 if rev == wdirrev:
702 raise error.WdirUnsupported
702 raise error.WdirUnsupported
703 raise
703 raise
704
704
705 return entry[5], entry[6]
705 return entry[5], entry[6]
706
706
707 def node(self, rev):
707 def node(self, rev):
708 try:
708 try:
709 return self.index[rev][7]
709 return self.index[rev][7]
710 except IndexError:
710 except IndexError:
711 if rev == wdirrev:
711 if rev == wdirrev:
712 raise error.WdirUnsupported
712 raise error.WdirUnsupported
713 raise
713 raise
714
714
715 # Derived from index values.
715 # Derived from index values.
716
716
717 def end(self, rev):
717 def end(self, rev):
718 return self.start(rev) + self.length(rev)
718 return self.start(rev) + self.length(rev)
719
719
720 def parents(self, node):
720 def parents(self, node):
721 i = self.index
721 i = self.index
722 d = i[self.rev(node)]
722 d = i[self.rev(node)]
723 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
723 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
724
724
725 def chainlen(self, rev):
725 def chainlen(self, rev):
726 return self._chaininfo(rev)[0]
726 return self._chaininfo(rev)[0]
727
727
728 def _chaininfo(self, rev):
728 def _chaininfo(self, rev):
729 chaininfocache = self._chaininfocache
729 chaininfocache = self._chaininfocache
730 if rev in chaininfocache:
730 if rev in chaininfocache:
731 return chaininfocache[rev]
731 return chaininfocache[rev]
732 index = self.index
732 index = self.index
733 generaldelta = self._generaldelta
733 generaldelta = self._generaldelta
734 iterrev = rev
734 iterrev = rev
735 e = index[iterrev]
735 e = index[iterrev]
736 clen = 0
736 clen = 0
737 compresseddeltalen = 0
737 compresseddeltalen = 0
738 while iterrev != e[3]:
738 while iterrev != e[3]:
739 clen += 1
739 clen += 1
740 compresseddeltalen += e[1]
740 compresseddeltalen += e[1]
741 if generaldelta:
741 if generaldelta:
742 iterrev = e[3]
742 iterrev = e[3]
743 else:
743 else:
744 iterrev -= 1
744 iterrev -= 1
745 if iterrev in chaininfocache:
745 if iterrev in chaininfocache:
746 t = chaininfocache[iterrev]
746 t = chaininfocache[iterrev]
747 clen += t[0]
747 clen += t[0]
748 compresseddeltalen += t[1]
748 compresseddeltalen += t[1]
749 break
749 break
750 e = index[iterrev]
750 e = index[iterrev]
751 else:
751 else:
752 # Add text length of base since decompressing that also takes
752 # Add text length of base since decompressing that also takes
753 # work. For cache hits the length is already included.
753 # work. For cache hits the length is already included.
754 compresseddeltalen += e[1]
754 compresseddeltalen += e[1]
755 r = (clen, compresseddeltalen)
755 r = (clen, compresseddeltalen)
756 chaininfocache[rev] = r
756 chaininfocache[rev] = r
757 return r
757 return r
758
758
759 def _deltachain(self, rev, stoprev=None):
759 def _deltachain(self, rev, stoprev=None):
760 """Obtain the delta chain for a revision.
760 """Obtain the delta chain for a revision.
761
761
762 ``stoprev`` specifies a revision to stop at. If not specified, we
762 ``stoprev`` specifies a revision to stop at. If not specified, we
763 stop at the base of the chain.
763 stop at the base of the chain.
764
764
765 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
765 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
766 revs in ascending order and ``stopped`` is a bool indicating whether
766 revs in ascending order and ``stopped`` is a bool indicating whether
767 ``stoprev`` was hit.
767 ``stoprev`` was hit.
768 """
768 """
769 # Try C implementation.
769 # Try C implementation.
770 try:
770 try:
771 return self.index.deltachain(rev, stoprev, self._generaldelta)
771 return self.index.deltachain(rev, stoprev, self._generaldelta)
772 except AttributeError:
772 except AttributeError:
773 pass
773 pass
774
774
775 chain = []
775 chain = []
776
776
777 # Alias to prevent attribute lookup in tight loop.
777 # Alias to prevent attribute lookup in tight loop.
778 index = self.index
778 index = self.index
779 generaldelta = self._generaldelta
779 generaldelta = self._generaldelta
780
780
781 iterrev = rev
781 iterrev = rev
782 e = index[iterrev]
782 e = index[iterrev]
783 while iterrev != e[3] and iterrev != stoprev:
783 while iterrev != e[3] and iterrev != stoprev:
784 chain.append(iterrev)
784 chain.append(iterrev)
785 if generaldelta:
785 if generaldelta:
786 iterrev = e[3]
786 iterrev = e[3]
787 else:
787 else:
788 iterrev -= 1
788 iterrev -= 1
789 e = index[iterrev]
789 e = index[iterrev]
790
790
791 if iterrev == stoprev:
791 if iterrev == stoprev:
792 stopped = True
792 stopped = True
793 else:
793 else:
794 chain.append(iterrev)
794 chain.append(iterrev)
795 stopped = False
795 stopped = False
796
796
797 chain.reverse()
797 chain.reverse()
798 return chain, stopped
798 return chain, stopped
799
799
800 def ancestors(self, revs, stoprev=0, inclusive=False):
800 def ancestors(self, revs, stoprev=0, inclusive=False):
801 """Generate the ancestors of 'revs' in reverse topological order.
801 """Generate the ancestors of 'revs' in reverse topological order.
802 Does not generate revs lower than stoprev.
802 Does not generate revs lower than stoprev.
803
803
804 See the documentation for ancestor.lazyancestors for more details."""
804 See the documentation for ancestor.lazyancestors for more details."""
805
805
806 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
806 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
807 inclusive=inclusive)
807 inclusive=inclusive)
808
808
809 def descendants(self, revs):
809 def descendants(self, revs):
810 """Generate the descendants of 'revs' in revision order.
810 """Generate the descendants of 'revs' in revision order.
811
811
812 Yield a sequence of revision numbers starting with a child of
812 Yield a sequence of revision numbers starting with a child of
813 some rev in revs, i.e., each revision is *not* considered a
813 some rev in revs, i.e., each revision is *not* considered a
814 descendant of itself. Results are ordered by revision number (a
814 descendant of itself. Results are ordered by revision number (a
815 topological sort)."""
815 topological sort)."""
816 first = min(revs)
816 first = min(revs)
817 if first == nullrev:
817 if first == nullrev:
818 for i in self:
818 for i in self:
819 yield i
819 yield i
820 return
820 return
821
821
822 seen = set(revs)
822 seen = set(revs)
823 for i in self.revs(start=first + 1):
823 for i in self.revs(start=first + 1):
824 for x in self.parentrevs(i):
824 for x in self.parentrevs(i):
825 if x != nullrev and x in seen:
825 if x != nullrev and x in seen:
826 seen.add(i)
826 seen.add(i)
827 yield i
827 yield i
828 break
828 break
829
829
830 def findcommonmissing(self, common=None, heads=None):
830 def findcommonmissing(self, common=None, heads=None):
831 """Return a tuple of the ancestors of common and the ancestors of heads
831 """Return a tuple of the ancestors of common and the ancestors of heads
832 that are not ancestors of common. In revset terminology, we return the
832 that are not ancestors of common. In revset terminology, we return the
833 tuple:
833 tuple:
834
834
835 ::common, (::heads) - (::common)
835 ::common, (::heads) - (::common)
836
836
837 The list is sorted by revision number, meaning it is
837 The list is sorted by revision number, meaning it is
838 topologically sorted.
838 topologically sorted.
839
839
840 'heads' and 'common' are both lists of node IDs. If heads is
840 'heads' and 'common' are both lists of node IDs. If heads is
841 not supplied, uses all of the revlog's heads. If common is not
841 not supplied, uses all of the revlog's heads. If common is not
842 supplied, uses nullid."""
842 supplied, uses nullid."""
843 if common is None:
843 if common is None:
844 common = [nullid]
844 common = [nullid]
845 if heads is None:
845 if heads is None:
846 heads = self.heads()
846 heads = self.heads()
847
847
848 common = [self.rev(n) for n in common]
848 common = [self.rev(n) for n in common]
849 heads = [self.rev(n) for n in heads]
849 heads = [self.rev(n) for n in heads]
850
850
851 # we want the ancestors, but inclusive
851 # we want the ancestors, but inclusive
852 class lazyset(object):
852 class lazyset(object):
853 def __init__(self, lazyvalues):
853 def __init__(self, lazyvalues):
854 self.addedvalues = set()
854 self.addedvalues = set()
855 self.lazyvalues = lazyvalues
855 self.lazyvalues = lazyvalues
856
856
857 def __contains__(self, value):
857 def __contains__(self, value):
858 return value in self.addedvalues or value in self.lazyvalues
858 return value in self.addedvalues or value in self.lazyvalues
859
859
860 def __iter__(self):
860 def __iter__(self):
861 added = self.addedvalues
861 added = self.addedvalues
862 for r in added:
862 for r in added:
863 yield r
863 yield r
864 for r in self.lazyvalues:
864 for r in self.lazyvalues:
865 if not r in added:
865 if not r in added:
866 yield r
866 yield r
867
867
868 def add(self, value):
868 def add(self, value):
869 self.addedvalues.add(value)
869 self.addedvalues.add(value)
870
870
871 def update(self, values):
871 def update(self, values):
872 self.addedvalues.update(values)
872 self.addedvalues.update(values)
873
873
874 has = lazyset(self.ancestors(common))
874 has = lazyset(self.ancestors(common))
875 has.add(nullrev)
875 has.add(nullrev)
876 has.update(common)
876 has.update(common)
877
877
878 # take all ancestors from heads that aren't in has
878 # take all ancestors from heads that aren't in has
879 missing = set()
879 missing = set()
880 visit = collections.deque(r for r in heads if r not in has)
880 visit = collections.deque(r for r in heads if r not in has)
881 while visit:
881 while visit:
882 r = visit.popleft()
882 r = visit.popleft()
883 if r in missing:
883 if r in missing:
884 continue
884 continue
885 else:
885 else:
886 missing.add(r)
886 missing.add(r)
887 for p in self.parentrevs(r):
887 for p in self.parentrevs(r):
888 if p not in has:
888 if p not in has:
889 visit.append(p)
889 visit.append(p)
890 missing = list(missing)
890 missing = list(missing)
891 missing.sort()
891 missing.sort()
892 return has, [self.node(miss) for miss in missing]
892 return has, [self.node(miss) for miss in missing]
893
893
894 def incrementalmissingrevs(self, common=None):
894 def incrementalmissingrevs(self, common=None):
895 """Return an object that can be used to incrementally compute the
895 """Return an object that can be used to incrementally compute the
896 revision numbers of the ancestors of arbitrary sets that are not
896 revision numbers of the ancestors of arbitrary sets that are not
897 ancestors of common. This is an ancestor.incrementalmissingancestors
897 ancestors of common. This is an ancestor.incrementalmissingancestors
898 object.
898 object.
899
899
900 'common' is a list of revision numbers. If common is not supplied, uses
900 'common' is a list of revision numbers. If common is not supplied, uses
901 nullrev.
901 nullrev.
902 """
902 """
903 if common is None:
903 if common is None:
904 common = [nullrev]
904 common = [nullrev]
905
905
906 return ancestor.incrementalmissingancestors(self.parentrevs, common)
906 return ancestor.incrementalmissingancestors(self.parentrevs, common)
907
907
908 def findmissingrevs(self, common=None, heads=None):
908 def findmissingrevs(self, common=None, heads=None):
909 """Return the revision numbers of the ancestors of heads that
909 """Return the revision numbers of the ancestors of heads that
910 are not ancestors of common.
910 are not ancestors of common.
911
911
912 More specifically, return a list of revision numbers corresponding to
912 More specifically, return a list of revision numbers corresponding to
913 nodes N such that every N satisfies the following constraints:
913 nodes N such that every N satisfies the following constraints:
914
914
915 1. N is an ancestor of some node in 'heads'
915 1. N is an ancestor of some node in 'heads'
916 2. N is not an ancestor of any node in 'common'
916 2. N is not an ancestor of any node in 'common'
917
917
918 The list is sorted by revision number, meaning it is
918 The list is sorted by revision number, meaning it is
919 topologically sorted.
919 topologically sorted.
920
920
921 'heads' and 'common' are both lists of revision numbers. If heads is
921 'heads' and 'common' are both lists of revision numbers. If heads is
922 not supplied, uses all of the revlog's heads. If common is not
922 not supplied, uses all of the revlog's heads. If common is not
923 supplied, uses nullid."""
923 supplied, uses nullid."""
924 if common is None:
924 if common is None:
925 common = [nullrev]
925 common = [nullrev]
926 if heads is None:
926 if heads is None:
927 heads = self.headrevs()
927 heads = self.headrevs()
928
928
929 inc = self.incrementalmissingrevs(common=common)
929 inc = self.incrementalmissingrevs(common=common)
930 return inc.missingancestors(heads)
930 return inc.missingancestors(heads)
931
931
932 def findmissing(self, common=None, heads=None):
932 def findmissing(self, common=None, heads=None):
933 """Return the ancestors of heads that are not ancestors of common.
933 """Return the ancestors of heads that are not ancestors of common.
934
934
935 More specifically, return a list of nodes N such that every N
935 More specifically, return a list of nodes N such that every N
936 satisfies the following constraints:
936 satisfies the following constraints:
937
937
938 1. N is an ancestor of some node in 'heads'
938 1. N is an ancestor of some node in 'heads'
939 2. N is not an ancestor of any node in 'common'
939 2. N is not an ancestor of any node in 'common'
940
940
941 The list is sorted by revision number, meaning it is
941 The list is sorted by revision number, meaning it is
942 topologically sorted.
942 topologically sorted.
943
943
944 'heads' and 'common' are both lists of node IDs. If heads is
944 'heads' and 'common' are both lists of node IDs. If heads is
945 not supplied, uses all of the revlog's heads. If common is not
945 not supplied, uses all of the revlog's heads. If common is not
946 supplied, uses nullid."""
946 supplied, uses nullid."""
947 if common is None:
947 if common is None:
948 common = [nullid]
948 common = [nullid]
949 if heads is None:
949 if heads is None:
950 heads = self.heads()
950 heads = self.heads()
951
951
952 common = [self.rev(n) for n in common]
952 common = [self.rev(n) for n in common]
953 heads = [self.rev(n) for n in heads]
953 heads = [self.rev(n) for n in heads]
954
954
955 inc = self.incrementalmissingrevs(common=common)
955 inc = self.incrementalmissingrevs(common=common)
956 return [self.node(r) for r in inc.missingancestors(heads)]
956 return [self.node(r) for r in inc.missingancestors(heads)]
957
957
958 def nodesbetween(self, roots=None, heads=None):
958 def nodesbetween(self, roots=None, heads=None):
959 """Return a topological path from 'roots' to 'heads'.
959 """Return a topological path from 'roots' to 'heads'.
960
960
961 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
961 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
962 topologically sorted list of all nodes N that satisfy both of
962 topologically sorted list of all nodes N that satisfy both of
963 these constraints:
963 these constraints:
964
964
965 1. N is a descendant of some node in 'roots'
965 1. N is a descendant of some node in 'roots'
966 2. N is an ancestor of some node in 'heads'
966 2. N is an ancestor of some node in 'heads'
967
967
968 Every node is considered to be both a descendant and an ancestor
968 Every node is considered to be both a descendant and an ancestor
969 of itself, so every reachable node in 'roots' and 'heads' will be
969 of itself, so every reachable node in 'roots' and 'heads' will be
970 included in 'nodes'.
970 included in 'nodes'.
971
971
972 'outroots' is the list of reachable nodes in 'roots', i.e., the
972 'outroots' is the list of reachable nodes in 'roots', i.e., the
973 subset of 'roots' that is returned in 'nodes'. Likewise,
973 subset of 'roots' that is returned in 'nodes'. Likewise,
974 'outheads' is the subset of 'heads' that is also in 'nodes'.
974 'outheads' is the subset of 'heads' that is also in 'nodes'.
975
975
976 'roots' and 'heads' are both lists of node IDs. If 'roots' is
976 'roots' and 'heads' are both lists of node IDs. If 'roots' is
977 unspecified, uses nullid as the only root. If 'heads' is
977 unspecified, uses nullid as the only root. If 'heads' is
978 unspecified, uses list of all of the revlog's heads."""
978 unspecified, uses list of all of the revlog's heads."""
979 nonodes = ([], [], [])
979 nonodes = ([], [], [])
980 if roots is not None:
980 if roots is not None:
981 roots = list(roots)
981 roots = list(roots)
982 if not roots:
982 if not roots:
983 return nonodes
983 return nonodes
984 lowestrev = min([self.rev(n) for n in roots])
984 lowestrev = min([self.rev(n) for n in roots])
985 else:
985 else:
986 roots = [nullid] # Everybody's a descendant of nullid
986 roots = [nullid] # Everybody's a descendant of nullid
987 lowestrev = nullrev
987 lowestrev = nullrev
988 if (lowestrev == nullrev) and (heads is None):
988 if (lowestrev == nullrev) and (heads is None):
989 # We want _all_ the nodes!
989 # We want _all_ the nodes!
990 return ([self.node(r) for r in self], [nullid], list(self.heads()))
990 return ([self.node(r) for r in self], [nullid], list(self.heads()))
991 if heads is None:
991 if heads is None:
992 # All nodes are ancestors, so the latest ancestor is the last
992 # All nodes are ancestors, so the latest ancestor is the last
993 # node.
993 # node.
994 highestrev = len(self) - 1
994 highestrev = len(self) - 1
995 # Set ancestors to None to signal that every node is an ancestor.
995 # Set ancestors to None to signal that every node is an ancestor.
996 ancestors = None
996 ancestors = None
997 # Set heads to an empty dictionary for later discovery of heads
997 # Set heads to an empty dictionary for later discovery of heads
998 heads = {}
998 heads = {}
999 else:
999 else:
1000 heads = list(heads)
1000 heads = list(heads)
1001 if not heads:
1001 if not heads:
1002 return nonodes
1002 return nonodes
1003 ancestors = set()
1003 ancestors = set()
1004 # Turn heads into a dictionary so we can remove 'fake' heads.
1004 # Turn heads into a dictionary so we can remove 'fake' heads.
1005 # Also, later we will be using it to filter out the heads we can't
1005 # Also, later we will be using it to filter out the heads we can't
1006 # find from roots.
1006 # find from roots.
1007 heads = dict.fromkeys(heads, False)
1007 heads = dict.fromkeys(heads, False)
1008 # Start at the top and keep marking parents until we're done.
1008 # Start at the top and keep marking parents until we're done.
1009 nodestotag = set(heads)
1009 nodestotag = set(heads)
1010 # Remember where the top was so we can use it as a limit later.
1010 # Remember where the top was so we can use it as a limit later.
1011 highestrev = max([self.rev(n) for n in nodestotag])
1011 highestrev = max([self.rev(n) for n in nodestotag])
1012 while nodestotag:
1012 while nodestotag:
1013 # grab a node to tag
1013 # grab a node to tag
1014 n = nodestotag.pop()
1014 n = nodestotag.pop()
1015 # Never tag nullid
1015 # Never tag nullid
1016 if n == nullid:
1016 if n == nullid:
1017 continue
1017 continue
1018 # A node's revision number represents its place in a
1018 # A node's revision number represents its place in a
1019 # topologically sorted list of nodes.
1019 # topologically sorted list of nodes.
1020 r = self.rev(n)
1020 r = self.rev(n)
1021 if r >= lowestrev:
1021 if r >= lowestrev:
1022 if n not in ancestors:
1022 if n not in ancestors:
1023 # If we are possibly a descendant of one of the roots
1023 # If we are possibly a descendant of one of the roots
1024 # and we haven't already been marked as an ancestor
1024 # and we haven't already been marked as an ancestor
1025 ancestors.add(n) # Mark as ancestor
1025 ancestors.add(n) # Mark as ancestor
1026 # Add non-nullid parents to list of nodes to tag.
1026 # Add non-nullid parents to list of nodes to tag.
1027 nodestotag.update([p for p in self.parents(n) if
1027 nodestotag.update([p for p in self.parents(n) if
1028 p != nullid])
1028 p != nullid])
1029 elif n in heads: # We've seen it before, is it a fake head?
1029 elif n in heads: # We've seen it before, is it a fake head?
1030 # So it is, real heads should not be the ancestors of
1030 # So it is, real heads should not be the ancestors of
1031 # any other heads.
1031 # any other heads.
1032 heads.pop(n)
1032 heads.pop(n)
1033 if not ancestors:
1033 if not ancestors:
1034 return nonodes
1034 return nonodes
1035 # Now that we have our set of ancestors, we want to remove any
1035 # Now that we have our set of ancestors, we want to remove any
1036 # roots that are not ancestors.
1036 # roots that are not ancestors.
1037
1037
1038 # If one of the roots was nullid, everything is included anyway.
1038 # If one of the roots was nullid, everything is included anyway.
1039 if lowestrev > nullrev:
1039 if lowestrev > nullrev:
1040 # But, since we weren't, let's recompute the lowest rev to not
1040 # But, since we weren't, let's recompute the lowest rev to not
1041 # include roots that aren't ancestors.
1041 # include roots that aren't ancestors.
1042
1042
1043 # Filter out roots that aren't ancestors of heads
1043 # Filter out roots that aren't ancestors of heads
1044 roots = [root for root in roots if root in ancestors]
1044 roots = [root for root in roots if root in ancestors]
1045 # Recompute the lowest revision
1045 # Recompute the lowest revision
1046 if roots:
1046 if roots:
1047 lowestrev = min([self.rev(root) for root in roots])
1047 lowestrev = min([self.rev(root) for root in roots])
1048 else:
1048 else:
1049 # No more roots? Return empty list
1049 # No more roots? Return empty list
1050 return nonodes
1050 return nonodes
1051 else:
1051 else:
1052 # We are descending from nullid, and don't need to care about
1052 # We are descending from nullid, and don't need to care about
1053 # any other roots.
1053 # any other roots.
1054 lowestrev = nullrev
1054 lowestrev = nullrev
1055 roots = [nullid]
1055 roots = [nullid]
1056 # Transform our roots list into a set.
1056 # Transform our roots list into a set.
1057 descendants = set(roots)
1057 descendants = set(roots)
1058 # Also, keep the original roots so we can filter out roots that aren't
1058 # Also, keep the original roots so we can filter out roots that aren't
1059 # 'real' roots (i.e. are descended from other roots).
1059 # 'real' roots (i.e. are descended from other roots).
1060 roots = descendants.copy()
1060 roots = descendants.copy()
1061 # Our topologically sorted list of output nodes.
1061 # Our topologically sorted list of output nodes.
1062 orderedout = []
1062 orderedout = []
1063 # Don't start at nullid since we don't want nullid in our output list,
1063 # Don't start at nullid since we don't want nullid in our output list,
1064 # and if nullid shows up in descendants, empty parents will look like
1064 # and if nullid shows up in descendants, empty parents will look like
1065 # they're descendants.
1065 # they're descendants.
1066 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1066 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1067 n = self.node(r)
1067 n = self.node(r)
1068 isdescendant = False
1068 isdescendant = False
1069 if lowestrev == nullrev: # Everybody is a descendant of nullid
1069 if lowestrev == nullrev: # Everybody is a descendant of nullid
1070 isdescendant = True
1070 isdescendant = True
1071 elif n in descendants:
1071 elif n in descendants:
1072 # n is already a descendant
1072 # n is already a descendant
1073 isdescendant = True
1073 isdescendant = True
1074 # This check only needs to be done here because all the roots
1074 # This check only needs to be done here because all the roots
1075 # will start being marked is descendants before the loop.
1075 # will start being marked is descendants before the loop.
1076 if n in roots:
1076 if n in roots:
1077 # If n was a root, check if it's a 'real' root.
1077 # If n was a root, check if it's a 'real' root.
1078 p = tuple(self.parents(n))
1078 p = tuple(self.parents(n))
1079 # If any of its parents are descendants, it's not a root.
1079 # If any of its parents are descendants, it's not a root.
1080 if (p[0] in descendants) or (p[1] in descendants):
1080 if (p[0] in descendants) or (p[1] in descendants):
1081 roots.remove(n)
1081 roots.remove(n)
1082 else:
1082 else:
1083 p = tuple(self.parents(n))
1083 p = tuple(self.parents(n))
1084 # A node is a descendant if either of its parents are
1084 # A node is a descendant if either of its parents are
1085 # descendants. (We seeded the dependents list with the roots
1085 # descendants. (We seeded the dependents list with the roots
1086 # up there, remember?)
1086 # up there, remember?)
1087 if (p[0] in descendants) or (p[1] in descendants):
1087 if (p[0] in descendants) or (p[1] in descendants):
1088 descendants.add(n)
1088 descendants.add(n)
1089 isdescendant = True
1089 isdescendant = True
1090 if isdescendant and ((ancestors is None) or (n in ancestors)):
1090 if isdescendant and ((ancestors is None) or (n in ancestors)):
1091 # Only include nodes that are both descendants and ancestors.
1091 # Only include nodes that are both descendants and ancestors.
1092 orderedout.append(n)
1092 orderedout.append(n)
1093 if (ancestors is not None) and (n in heads):
1093 if (ancestors is not None) and (n in heads):
1094 # We're trying to figure out which heads are reachable
1094 # We're trying to figure out which heads are reachable
1095 # from roots.
1095 # from roots.
1096 # Mark this head as having been reached
1096 # Mark this head as having been reached
1097 heads[n] = True
1097 heads[n] = True
1098 elif ancestors is None:
1098 elif ancestors is None:
1099 # Otherwise, we're trying to discover the heads.
1099 # Otherwise, we're trying to discover the heads.
1100 # Assume this is a head because if it isn't, the next step
1100 # Assume this is a head because if it isn't, the next step
1101 # will eventually remove it.
1101 # will eventually remove it.
1102 heads[n] = True
1102 heads[n] = True
1103 # But, obviously its parents aren't.
1103 # But, obviously its parents aren't.
1104 for p in self.parents(n):
1104 for p in self.parents(n):
1105 heads.pop(p, None)
1105 heads.pop(p, None)
1106 heads = [head for head, flag in heads.iteritems() if flag]
1106 heads = [head for head, flag in heads.iteritems() if flag]
1107 roots = list(roots)
1107 roots = list(roots)
1108 assert orderedout
1108 assert orderedout
1109 assert roots
1109 assert roots
1110 assert heads
1110 assert heads
1111 return (orderedout, roots, heads)
1111 return (orderedout, roots, heads)
1112
1112
1113 def headrevs(self):
1113 def headrevs(self):
1114 try:
1114 try:
1115 return self.index.headrevs()
1115 return self.index.headrevs()
1116 except AttributeError:
1116 except AttributeError:
1117 return self._headrevs()
1117 return self._headrevs()
1118
1118
1119 def computephases(self, roots):
1119 def computephases(self, roots):
1120 return self.index.computephasesmapsets(roots)
1120 return self.index.computephasesmapsets(roots)
1121
1121
1122 def _headrevs(self):
1122 def _headrevs(self):
1123 count = len(self)
1123 count = len(self)
1124 if not count:
1124 if not count:
1125 return [nullrev]
1125 return [nullrev]
1126 # we won't iter over filtered rev so nobody is a head at start
1126 # we won't iter over filtered rev so nobody is a head at start
1127 ishead = [0] * (count + 1)
1127 ishead = [0] * (count + 1)
1128 index = self.index
1128 index = self.index
1129 for r in self:
1129 for r in self:
1130 ishead[r] = 1 # I may be an head
1130 ishead[r] = 1 # I may be an head
1131 e = index[r]
1131 e = index[r]
1132 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1132 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1133 return [r for r, val in enumerate(ishead) if val]
1133 return [r for r, val in enumerate(ishead) if val]
1134
1134
1135 def heads(self, start=None, stop=None):
1135 def heads(self, start=None, stop=None):
1136 """return the list of all nodes that have no children
1136 """return the list of all nodes that have no children
1137
1137
1138 if start is specified, only heads that are descendants of
1138 if start is specified, only heads that are descendants of
1139 start will be returned
1139 start will be returned
1140 if stop is specified, it will consider all the revs from stop
1140 if stop is specified, it will consider all the revs from stop
1141 as if they had no children
1141 as if they had no children
1142 """
1142 """
1143 if start is None and stop is None:
1143 if start is None and stop is None:
1144 if not len(self):
1144 if not len(self):
1145 return [nullid]
1145 return [nullid]
1146 return [self.node(r) for r in self.headrevs()]
1146 return [self.node(r) for r in self.headrevs()]
1147
1147
1148 if start is None:
1148 if start is None:
1149 start = nullid
1149 start = nullid
1150 if stop is None:
1150 if stop is None:
1151 stop = []
1151 stop = []
1152 stoprevs = set([self.rev(n) for n in stop])
1152 stoprevs = set([self.rev(n) for n in stop])
1153 startrev = self.rev(start)
1153 startrev = self.rev(start)
1154 reachable = {startrev}
1154 reachable = {startrev}
1155 heads = {startrev}
1155 heads = {startrev}
1156
1156
1157 parentrevs = self.parentrevs
1157 parentrevs = self.parentrevs
1158 for r in self.revs(start=startrev + 1):
1158 for r in self.revs(start=startrev + 1):
1159 for p in parentrevs(r):
1159 for p in parentrevs(r):
1160 if p in reachable:
1160 if p in reachable:
1161 if r not in stoprevs:
1161 if r not in stoprevs:
1162 reachable.add(r)
1162 reachable.add(r)
1163 heads.add(r)
1163 heads.add(r)
1164 if p in heads and p not in stoprevs:
1164 if p in heads and p not in stoprevs:
1165 heads.remove(p)
1165 heads.remove(p)
1166
1166
1167 return [self.node(r) for r in heads]
1167 return [self.node(r) for r in heads]
1168
1168
1169 def children(self, node):
1169 def children(self, node):
1170 """find the children of a given node"""
1170 """find the children of a given node"""
1171 c = []
1171 c = []
1172 p = self.rev(node)
1172 p = self.rev(node)
1173 for r in self.revs(start=p + 1):
1173 for r in self.revs(start=p + 1):
1174 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1174 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1175 if prevs:
1175 if prevs:
1176 for pr in prevs:
1176 for pr in prevs:
1177 if pr == p:
1177 if pr == p:
1178 c.append(self.node(r))
1178 c.append(self.node(r))
1179 elif p == nullrev:
1179 elif p == nullrev:
1180 c.append(self.node(r))
1180 c.append(self.node(r))
1181 return c
1181 return c
1182
1182
1183 def commonancestorsheads(self, a, b):
1183 def commonancestorsheads(self, a, b):
1184 """calculate all the heads of the common ancestors of nodes a and b"""
1184 """calculate all the heads of the common ancestors of nodes a and b"""
1185 a, b = self.rev(a), self.rev(b)
1185 a, b = self.rev(a), self.rev(b)
1186 ancs = self._commonancestorsheads(a, b)
1186 ancs = self._commonancestorsheads(a, b)
1187 return pycompat.maplist(self.node, ancs)
1187 return pycompat.maplist(self.node, ancs)
1188
1188
1189 def _commonancestorsheads(self, *revs):
1189 def _commonancestorsheads(self, *revs):
1190 """calculate all the heads of the common ancestors of revs"""
1190 """calculate all the heads of the common ancestors of revs"""
1191 try:
1191 try:
1192 ancs = self.index.commonancestorsheads(*revs)
1192 ancs = self.index.commonancestorsheads(*revs)
1193 except (AttributeError, OverflowError): # C implementation failed
1193 except (AttributeError, OverflowError): # C implementation failed
1194 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1194 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1195 return ancs
1195 return ancs
1196
1196
1197 def isancestor(self, a, b):
1197 def isancestor(self, a, b):
1198 """return True if node a is an ancestor of node b
1198 """return True if node a is an ancestor of node b
1199
1199
1200 A revision is considered an ancestor of itself."""
1200 A revision is considered an ancestor of itself."""
1201 a, b = self.rev(a), self.rev(b)
1201 a, b = self.rev(a), self.rev(b)
1202 return self.isancestorrev(a, b)
1202 return self.isancestorrev(a, b)
1203
1203
1204 def isancestorrev(self, a, b):
1204 def isancestorrev(self, a, b):
1205 """return True if revision a is an ancestor of revision b
1205 """return True if revision a is an ancestor of revision b
1206
1206
1207 A revision is considered an ancestor of itself.
1207 A revision is considered an ancestor of itself.
1208
1208
1209 The implementation of this is trivial but the use of
1209 The implementation of this is trivial but the use of
1210 commonancestorsheads is not."""
1210 commonancestorsheads is not."""
1211 if a == nullrev:
1211 if a == nullrev:
1212 return True
1212 return True
1213 elif a == b:
1213 elif a == b:
1214 return True
1214 return True
1215 elif a > b:
1215 elif a > b:
1216 return False
1216 return False
1217 return a in self._commonancestorsheads(a, b)
1217 return a in self._commonancestorsheads(a, b)
1218
1218
1219 def ancestor(self, a, b):
1219 def ancestor(self, a, b):
1220 """calculate the "best" common ancestor of nodes a and b"""
1220 """calculate the "best" common ancestor of nodes a and b"""
1221
1221
1222 a, b = self.rev(a), self.rev(b)
1222 a, b = self.rev(a), self.rev(b)
1223 try:
1223 try:
1224 ancs = self.index.ancestors(a, b)
1224 ancs = self.index.ancestors(a, b)
1225 except (AttributeError, OverflowError):
1225 except (AttributeError, OverflowError):
1226 ancs = ancestor.ancestors(self.parentrevs, a, b)
1226 ancs = ancestor.ancestors(self.parentrevs, a, b)
1227 if ancs:
1227 if ancs:
1228 # choose a consistent winner when there's a tie
1228 # choose a consistent winner when there's a tie
1229 return min(map(self.node, ancs))
1229 return min(map(self.node, ancs))
1230 return nullid
1230 return nullid
1231
1231
1232 def _match(self, id):
1232 def _match(self, id):
1233 if isinstance(id, int):
1233 if isinstance(id, int):
1234 # rev
1234 # rev
1235 return self.node(id)
1235 return self.node(id)
1236 if len(id) == 20:
1236 if len(id) == 20:
1237 # possibly a binary node
1237 # possibly a binary node
1238 # odds of a binary node being all hex in ASCII are 1 in 10**25
1238 # odds of a binary node being all hex in ASCII are 1 in 10**25
1239 try:
1239 try:
1240 node = id
1240 node = id
1241 self.rev(node) # quick search the index
1241 self.rev(node) # quick search the index
1242 return node
1242 return node
1243 except error.LookupError:
1243 except error.LookupError:
1244 pass # may be partial hex id
1244 pass # may be partial hex id
1245 try:
1245 try:
1246 # str(rev)
1246 # str(rev)
1247 rev = int(id)
1247 rev = int(id)
1248 if "%d" % rev != id:
1248 if "%d" % rev != id:
1249 raise ValueError
1249 raise ValueError
1250 if rev < 0:
1250 if rev < 0:
1251 rev = len(self) + rev
1251 rev = len(self) + rev
1252 if rev < 0 or rev >= len(self):
1252 if rev < 0 or rev >= len(self):
1253 raise ValueError
1253 raise ValueError
1254 return self.node(rev)
1254 return self.node(rev)
1255 except (ValueError, OverflowError):
1255 except (ValueError, OverflowError):
1256 pass
1256 pass
1257 if len(id) == 40:
1257 if len(id) == 40:
1258 try:
1258 try:
1259 # a full hex nodeid?
1259 # a full hex nodeid?
1260 node = bin(id)
1260 node = bin(id)
1261 self.rev(node)
1261 self.rev(node)
1262 return node
1262 return node
1263 except (TypeError, error.LookupError):
1263 except (TypeError, error.LookupError):
1264 pass
1264 pass
1265
1265
1266 def _partialmatch(self, id):
1266 def _partialmatch(self, id):
1267 # we don't care wdirfilenodeids as they should be always full hash
1267 # we don't care wdirfilenodeids as they should be always full hash
1268 maybewdir = wdirhex.startswith(id)
1268 maybewdir = wdirhex.startswith(id)
1269 try:
1269 try:
1270 partial = self.index.partialmatch(id)
1270 partial = self.index.partialmatch(id)
1271 if partial and self.hasnode(partial):
1271 if partial and self.hasnode(partial):
1272 if maybewdir:
1272 if maybewdir:
1273 # single 'ff...' match in radix tree, ambiguous with wdir
1273 # single 'ff...' match in radix tree, ambiguous with wdir
1274 raise error.RevlogError
1274 raise error.RevlogError
1275 return partial
1275 return partial
1276 if maybewdir:
1276 if maybewdir:
1277 # no 'ff...' match in radix tree, wdir identified
1277 # no 'ff...' match in radix tree, wdir identified
1278 raise error.WdirUnsupported
1278 raise error.WdirUnsupported
1279 return None
1279 return None
1280 except error.RevlogError:
1280 except error.RevlogError:
1281 # parsers.c radix tree lookup gave multiple matches
1281 # parsers.c radix tree lookup gave multiple matches
1282 # fast path: for unfiltered changelog, radix tree is accurate
1282 # fast path: for unfiltered changelog, radix tree is accurate
1283 if not getattr(self, 'filteredrevs', None):
1283 if not getattr(self, 'filteredrevs', None):
1284 raise error.AmbiguousPrefixLookupError(
1284 raise error.AmbiguousPrefixLookupError(
1285 id, self.indexfile, _('ambiguous identifier'))
1285 id, self.indexfile, _('ambiguous identifier'))
1286 # fall through to slow path that filters hidden revisions
1286 # fall through to slow path that filters hidden revisions
1287 except (AttributeError, ValueError):
1287 except (AttributeError, ValueError):
1288 # we are pure python, or key was too short to search radix tree
1288 # we are pure python, or key was too short to search radix tree
1289 pass
1289 pass
1290
1290
1291 if id in self._pcache:
1291 if id in self._pcache:
1292 return self._pcache[id]
1292 return self._pcache[id]
1293
1293
1294 if len(id) <= 40:
1294 if len(id) <= 40:
1295 try:
1295 try:
1296 # hex(node)[:...]
1296 # hex(node)[:...]
1297 l = len(id) // 2 # grab an even number of digits
1297 l = len(id) // 2 # grab an even number of digits
1298 prefix = bin(id[:l * 2])
1298 prefix = bin(id[:l * 2])
1299 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1299 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1300 nl = [n for n in nl if hex(n).startswith(id) and
1300 nl = [n for n in nl if hex(n).startswith(id) and
1301 self.hasnode(n)]
1301 self.hasnode(n)]
1302 if nullhex.startswith(id):
1302 if nullhex.startswith(id):
1303 nl.append(nullid)
1303 nl.append(nullid)
1304 if len(nl) > 0:
1304 if len(nl) > 0:
1305 if len(nl) == 1 and not maybewdir:
1305 if len(nl) == 1 and not maybewdir:
1306 self._pcache[id] = nl[0]
1306 self._pcache[id] = nl[0]
1307 return nl[0]
1307 return nl[0]
1308 raise error.AmbiguousPrefixLookupError(
1308 raise error.AmbiguousPrefixLookupError(
1309 id, self.indexfile, _('ambiguous identifier'))
1309 id, self.indexfile, _('ambiguous identifier'))
1310 if maybewdir:
1310 if maybewdir:
1311 raise error.WdirUnsupported
1311 raise error.WdirUnsupported
1312 return None
1312 return None
1313 except TypeError:
1313 except TypeError:
1314 pass
1314 pass
1315
1315
1316 def lookup(self, id):
1316 def lookup(self, id):
1317 """locate a node based on:
1317 """locate a node based on:
1318 - revision number or str(revision number)
1318 - revision number or str(revision number)
1319 - nodeid or subset of hex nodeid
1319 - nodeid or subset of hex nodeid
1320 """
1320 """
1321 n = self._match(id)
1321 n = self._match(id)
1322 if n is not None:
1322 if n is not None:
1323 return n
1323 return n
1324 n = self._partialmatch(id)
1324 n = self._partialmatch(id)
1325 if n:
1325 if n:
1326 return n
1326 return n
1327
1327
1328 raise error.LookupError(id, self.indexfile, _('no match found'))
1328 raise error.LookupError(id, self.indexfile, _('no match found'))
1329
1329
1330 def shortest(self, node, minlength=1):
1330 def shortest(self, node, minlength=1):
1331 """Find the shortest unambiguous prefix that matches node."""
1331 """Find the shortest unambiguous prefix that matches node."""
1332 def isvalid(prefix):
1332 def isvalid(prefix):
1333 try:
1333 try:
1334 node = self._partialmatch(prefix)
1334 node = self._partialmatch(prefix)
1335 except error.AmbiguousPrefixLookupError:
1335 except error.AmbiguousPrefixLookupError:
1336 return False
1336 return False
1337 except error.WdirUnsupported:
1337 except error.WdirUnsupported:
1338 # single 'ff...' match
1338 # single 'ff...' match
1339 return True
1339 return True
1340 if node is None:
1340 if node is None:
1341 raise error.LookupError(node, self.indexfile, _('no node'))
1341 raise error.LookupError(node, self.indexfile, _('no node'))
1342 return True
1342 return True
1343
1343
1344 def maybewdir(prefix):
1344 def maybewdir(prefix):
1345 return all(c == 'f' for c in prefix)
1345 return all(c == 'f' for c in prefix)
1346
1346
1347 hexnode = hex(node)
1347 hexnode = hex(node)
1348
1348
1349 def disambiguate(hexnode, minlength):
1349 def disambiguate(hexnode, minlength):
1350 """Disambiguate against wdirid."""
1350 """Disambiguate against wdirid."""
1351 for length in range(minlength, 41):
1351 for length in range(minlength, 41):
1352 prefix = hexnode[:length]
1352 prefix = hexnode[:length]
1353 if not maybewdir(prefix):
1353 if not maybewdir(prefix):
1354 return prefix
1354 return prefix
1355
1355
1356 if not getattr(self, 'filteredrevs', None):
1356 if not getattr(self, 'filteredrevs', None):
1357 try:
1357 try:
1358 length = max(self.index.shortest(node), minlength)
1358 length = max(self.index.shortest(node), minlength)
1359 return disambiguate(hexnode, length)
1359 return disambiguate(hexnode, length)
1360 except error.RevlogError:
1360 except error.RevlogError:
1361 if node != wdirid:
1361 if node != wdirid:
1362 raise error.LookupError(node, self.indexfile, _('no node'))
1362 raise error.LookupError(node, self.indexfile, _('no node'))
1363 except AttributeError:
1363 except AttributeError:
1364 # Fall through to pure code
1364 # Fall through to pure code
1365 pass
1365 pass
1366
1366
1367 if node == wdirid:
1367 if node == wdirid:
1368 for length in range(minlength, 41):
1368 for length in range(minlength, 41):
1369 prefix = hexnode[:length]
1369 prefix = hexnode[:length]
1370 if isvalid(prefix):
1370 if isvalid(prefix):
1371 return prefix
1371 return prefix
1372
1372
1373 for length in range(minlength, 41):
1373 for length in range(minlength, 41):
1374 prefix = hexnode[:length]
1374 prefix = hexnode[:length]
1375 if isvalid(prefix):
1375 if isvalid(prefix):
1376 return disambiguate(hexnode, length)
1376 return disambiguate(hexnode, length)
1377
1377
1378 def cmp(self, node, text):
1378 def cmp(self, node, text):
1379 """compare text with a given file revision
1379 """compare text with a given file revision
1380
1380
1381 returns True if text is different than what is stored.
1381 returns True if text is different than what is stored.
1382 """
1382 """
1383 p1, p2 = self.parents(node)
1383 p1, p2 = self.parents(node)
1384 return hash(text, p1, p2) != node
1384 return hash(text, p1, p2) != node
1385
1385
1386 def _cachesegment(self, offset, data):
1386 def _cachesegment(self, offset, data):
1387 """Add a segment to the revlog cache.
1387 """Add a segment to the revlog cache.
1388
1388
1389 Accepts an absolute offset and the data that is at that location.
1389 Accepts an absolute offset and the data that is at that location.
1390 """
1390 """
1391 o, d = self._chunkcache
1391 o, d = self._chunkcache
1392 # try to add to existing cache
1392 # try to add to existing cache
1393 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1393 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1394 self._chunkcache = o, d + data
1394 self._chunkcache = o, d + data
1395 else:
1395 else:
1396 self._chunkcache = offset, data
1396 self._chunkcache = offset, data
1397
1397
1398 def _readsegment(self, offset, length, df=None):
1398 def _readsegment(self, offset, length, df=None):
1399 """Load a segment of raw data from the revlog.
1399 """Load a segment of raw data from the revlog.
1400
1400
1401 Accepts an absolute offset, length to read, and an optional existing
1401 Accepts an absolute offset, length to read, and an optional existing
1402 file handle to read from.
1402 file handle to read from.
1403
1403
1404 If an existing file handle is passed, it will be seeked and the
1404 If an existing file handle is passed, it will be seeked and the
1405 original seek position will NOT be restored.
1405 original seek position will NOT be restored.
1406
1406
1407 Returns a str or buffer of raw byte data.
1407 Returns a str or buffer of raw byte data.
1408 """
1408 """
1409 # Cache data both forward and backward around the requested
1409 # Cache data both forward and backward around the requested
1410 # data, in a fixed size window. This helps speed up operations
1410 # data, in a fixed size window. This helps speed up operations
1411 # involving reading the revlog backwards.
1411 # involving reading the revlog backwards.
1412 cachesize = self._chunkcachesize
1412 cachesize = self._chunkcachesize
1413 realoffset = offset & ~(cachesize - 1)
1413 realoffset = offset & ~(cachesize - 1)
1414 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1414 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1415 - realoffset)
1415 - realoffset)
1416 with self._datareadfp(df) as df:
1416 with self._datareadfp(df) as df:
1417 df.seek(realoffset)
1417 df.seek(realoffset)
1418 d = df.read(reallength)
1418 d = df.read(reallength)
1419 self._cachesegment(realoffset, d)
1419 self._cachesegment(realoffset, d)
1420 if offset != realoffset or reallength != length:
1420 if offset != realoffset or reallength != length:
1421 return util.buffer(d, offset - realoffset, length)
1421 return util.buffer(d, offset - realoffset, length)
1422 return d
1422 return d
1423
1423
1424 def _getsegment(self, offset, length, df=None):
1424 def _getsegment(self, offset, length, df=None):
1425 """Obtain a segment of raw data from the revlog.
1425 """Obtain a segment of raw data from the revlog.
1426
1426
1427 Accepts an absolute offset, length of bytes to obtain, and an
1427 Accepts an absolute offset, length of bytes to obtain, and an
1428 optional file handle to the already-opened revlog. If the file
1428 optional file handle to the already-opened revlog. If the file
1429 handle is used, it's original seek position will not be preserved.
1429 handle is used, it's original seek position will not be preserved.
1430
1430
1431 Requests for data may be returned from a cache.
1431 Requests for data may be returned from a cache.
1432
1432
1433 Returns a str or a buffer instance of raw byte data.
1433 Returns a str or a buffer instance of raw byte data.
1434 """
1434 """
1435 o, d = self._chunkcache
1435 o, d = self._chunkcache
1436 l = len(d)
1436 l = len(d)
1437
1437
1438 # is it in the cache?
1438 # is it in the cache?
1439 cachestart = offset - o
1439 cachestart = offset - o
1440 cacheend = cachestart + length
1440 cacheend = cachestart + length
1441 if cachestart >= 0 and cacheend <= l:
1441 if cachestart >= 0 and cacheend <= l:
1442 if cachestart == 0 and cacheend == l:
1442 if cachestart == 0 and cacheend == l:
1443 return d # avoid a copy
1443 return d # avoid a copy
1444 return util.buffer(d, cachestart, cacheend - cachestart)
1444 return util.buffer(d, cachestart, cacheend - cachestart)
1445
1445
1446 return self._readsegment(offset, length, df=df)
1446 return self._readsegment(offset, length, df=df)
1447
1447
1448 def _getsegmentforrevs(self, startrev, endrev, df=None):
1448 def _getsegmentforrevs(self, startrev, endrev, df=None):
1449 """Obtain a segment of raw data corresponding to a range of revisions.
1449 """Obtain a segment of raw data corresponding to a range of revisions.
1450
1450
1451 Accepts the start and end revisions and an optional already-open
1451 Accepts the start and end revisions and an optional already-open
1452 file handle to be used for reading. If the file handle is read, its
1452 file handle to be used for reading. If the file handle is read, its
1453 seek position will not be preserved.
1453 seek position will not be preserved.
1454
1454
1455 Requests for data may be satisfied by a cache.
1455 Requests for data may be satisfied by a cache.
1456
1456
1457 Returns a 2-tuple of (offset, data) for the requested range of
1457 Returns a 2-tuple of (offset, data) for the requested range of
1458 revisions. Offset is the integer offset from the beginning of the
1458 revisions. Offset is the integer offset from the beginning of the
1459 revlog and data is a str or buffer of the raw byte data.
1459 revlog and data is a str or buffer of the raw byte data.
1460
1460
1461 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1461 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1462 to determine where each revision's data begins and ends.
1462 to determine where each revision's data begins and ends.
1463 """
1463 """
1464 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1464 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1465 # (functions are expensive).
1465 # (functions are expensive).
1466 index = self.index
1466 index = self.index
1467 istart = index[startrev]
1467 istart = index[startrev]
1468 start = int(istart[0] >> 16)
1468 start = int(istart[0] >> 16)
1469 if startrev == endrev:
1469 if startrev == endrev:
1470 end = start + istart[1]
1470 end = start + istart[1]
1471 else:
1471 else:
1472 iend = index[endrev]
1472 iend = index[endrev]
1473 end = int(iend[0] >> 16) + iend[1]
1473 end = int(iend[0] >> 16) + iend[1]
1474
1474
1475 if self._inline:
1475 if self._inline:
1476 start += (startrev + 1) * self._io.size
1476 start += (startrev + 1) * self._io.size
1477 end += (endrev + 1) * self._io.size
1477 end += (endrev + 1) * self._io.size
1478 length = end - start
1478 length = end - start
1479
1479
1480 return start, self._getsegment(start, length, df=df)
1480 return start, self._getsegment(start, length, df=df)
1481
1481
1482 def _chunk(self, rev, df=None):
1482 def _chunk(self, rev, df=None):
1483 """Obtain a single decompressed chunk for a revision.
1483 """Obtain a single decompressed chunk for a revision.
1484
1484
1485 Accepts an integer revision and an optional already-open file handle
1485 Accepts an integer revision and an optional already-open file handle
1486 to be used for reading. If used, the seek position of the file will not
1486 to be used for reading. If used, the seek position of the file will not
1487 be preserved.
1487 be preserved.
1488
1488
1489 Returns a str holding uncompressed data for the requested revision.
1489 Returns a str holding uncompressed data for the requested revision.
1490 """
1490 """
1491 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1491 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1492
1492
1493 def _chunks(self, revs, df=None, targetsize=None):
1493 def _chunks(self, revs, df=None, targetsize=None):
1494 """Obtain decompressed chunks for the specified revisions.
1494 """Obtain decompressed chunks for the specified revisions.
1495
1495
1496 Accepts an iterable of numeric revisions that are assumed to be in
1496 Accepts an iterable of numeric revisions that are assumed to be in
1497 ascending order. Also accepts an optional already-open file handle
1497 ascending order. Also accepts an optional already-open file handle
1498 to be used for reading. If used, the seek position of the file will
1498 to be used for reading. If used, the seek position of the file will
1499 not be preserved.
1499 not be preserved.
1500
1500
1501 This function is similar to calling ``self._chunk()`` multiple times,
1501 This function is similar to calling ``self._chunk()`` multiple times,
1502 but is faster.
1502 but is faster.
1503
1503
1504 Returns a list with decompressed data for each requested revision.
1504 Returns a list with decompressed data for each requested revision.
1505 """
1505 """
1506 if not revs:
1506 if not revs:
1507 return []
1507 return []
1508 start = self.start
1508 start = self.start
1509 length = self.length
1509 length = self.length
1510 inline = self._inline
1510 inline = self._inline
1511 iosize = self._io.size
1511 iosize = self._io.size
1512 buffer = util.buffer
1512 buffer = util.buffer
1513
1513
1514 l = []
1514 l = []
1515 ladd = l.append
1515 ladd = l.append
1516
1516
1517 if not self._withsparseread:
1517 if not self._withsparseread:
1518 slicedchunks = (revs,)
1518 slicedchunks = (revs,)
1519 else:
1519 else:
1520 slicedchunks = deltautil.slicechunk(self, revs,
1520 slicedchunks = deltautil.slicechunk(self, revs,
1521 targetsize=targetsize)
1521 targetsize=targetsize)
1522
1522
1523 for revschunk in slicedchunks:
1523 for revschunk in slicedchunks:
1524 firstrev = revschunk[0]
1524 firstrev = revschunk[0]
1525 # Skip trailing revisions with empty diff
1525 # Skip trailing revisions with empty diff
1526 for lastrev in revschunk[::-1]:
1526 for lastrev in revschunk[::-1]:
1527 if length(lastrev) != 0:
1527 if length(lastrev) != 0:
1528 break
1528 break
1529
1529
1530 try:
1530 try:
1531 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1531 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1532 except OverflowError:
1532 except OverflowError:
1533 # issue4215 - we can't cache a run of chunks greater than
1533 # issue4215 - we can't cache a run of chunks greater than
1534 # 2G on Windows
1534 # 2G on Windows
1535 return [self._chunk(rev, df=df) for rev in revschunk]
1535 return [self._chunk(rev, df=df) for rev in revschunk]
1536
1536
1537 decomp = self.decompress
1537 decomp = self.decompress
1538 for rev in revschunk:
1538 for rev in revschunk:
1539 chunkstart = start(rev)
1539 chunkstart = start(rev)
1540 if inline:
1540 if inline:
1541 chunkstart += (rev + 1) * iosize
1541 chunkstart += (rev + 1) * iosize
1542 chunklength = length(rev)
1542 chunklength = length(rev)
1543 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1543 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1544
1544
1545 return l
1545 return l
1546
1546
1547 def _chunkclear(self):
1547 def _chunkclear(self):
1548 """Clear the raw chunk cache."""
1548 """Clear the raw chunk cache."""
1549 self._chunkcache = (0, '')
1549 self._chunkcache = (0, '')
1550
1550
1551 def deltaparent(self, rev):
1551 def deltaparent(self, rev):
1552 """return deltaparent of the given revision"""
1552 """return deltaparent of the given revision"""
1553 base = self.index[rev][3]
1553 base = self.index[rev][3]
1554 if base == rev:
1554 if base == rev:
1555 return nullrev
1555 return nullrev
1556 elif self._generaldelta:
1556 elif self._generaldelta:
1557 return base
1557 return base
1558 else:
1558 else:
1559 return rev - 1
1559 return rev - 1
1560
1560
1561 def issnapshot(self, rev):
1561 def issnapshot(self, rev):
1562 """tells whether rev is a snapshot
1562 """tells whether rev is a snapshot
1563 """
1563 """
1564 if rev == nullrev:
1564 if rev == nullrev:
1565 return True
1565 return True
1566 deltap = self.deltaparent(rev)
1566 deltap = self.deltaparent(rev)
1567 if deltap == nullrev:
1567 if deltap == nullrev:
1568 return True
1568 return True
1569 p1, p2 = self.parentrevs(rev)
1569 p1, p2 = self.parentrevs(rev)
1570 if deltap in (p1, p2):
1570 if deltap in (p1, p2):
1571 return False
1571 return False
1572 return self.issnapshot(deltap)
1572 return self.issnapshot(deltap)
1573
1573
1574 def snapshotdepth(self, rev):
1574 def snapshotdepth(self, rev):
1575 """number of snapshot in the chain before this one"""
1575 """number of snapshot in the chain before this one"""
1576 if not self.issnapshot(rev):
1576 if not self.issnapshot(rev):
1577 raise error.ProgrammingError('revision %d not a snapshot')
1577 raise error.ProgrammingError('revision %d not a snapshot')
1578 return len(self._deltachain(rev)[0]) - 1
1578 return len(self._deltachain(rev)[0]) - 1
1579
1579
1580 def revdiff(self, rev1, rev2):
1580 def revdiff(self, rev1, rev2):
1581 """return or calculate a delta between two revisions
1581 """return or calculate a delta between two revisions
1582
1582
1583 The delta calculated is in binary form and is intended to be written to
1583 The delta calculated is in binary form and is intended to be written to
1584 revlog data directly. So this function needs raw revision data.
1584 revlog data directly. So this function needs raw revision data.
1585 """
1585 """
1586 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1586 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1587 return bytes(self._chunk(rev2))
1587 return bytes(self._chunk(rev2))
1588
1588
1589 return mdiff.textdiff(self.revision(rev1, raw=True),
1589 return mdiff.textdiff(self.revision(rev1, raw=True),
1590 self.revision(rev2, raw=True))
1590 self.revision(rev2, raw=True))
1591
1591
1592 def revision(self, nodeorrev, _df=None, raw=False):
1592 def revision(self, nodeorrev, _df=None, raw=False):
1593 """return an uncompressed revision of a given node or revision
1593 """return an uncompressed revision of a given node or revision
1594 number.
1594 number.
1595
1595
1596 _df - an existing file handle to read from. (internal-only)
1596 _df - an existing file handle to read from. (internal-only)
1597 raw - an optional argument specifying if the revision data is to be
1597 raw - an optional argument specifying if the revision data is to be
1598 treated as raw data when applying flag transforms. 'raw' should be set
1598 treated as raw data when applying flag transforms. 'raw' should be set
1599 to True when generating changegroups or in debug commands.
1599 to True when generating changegroups or in debug commands.
1600 """
1600 """
1601 if isinstance(nodeorrev, int):
1601 if isinstance(nodeorrev, int):
1602 rev = nodeorrev
1602 rev = nodeorrev
1603 node = self.node(rev)
1603 node = self.node(rev)
1604 else:
1604 else:
1605 node = nodeorrev
1605 node = nodeorrev
1606 rev = None
1606 rev = None
1607
1607
1608 cachedrev = None
1608 cachedrev = None
1609 flags = None
1609 flags = None
1610 rawtext = None
1610 rawtext = None
1611 if node == nullid:
1611 if node == nullid:
1612 return ""
1612 return ""
1613 if self._cache:
1613 if self._cache:
1614 if self._cache[0] == node:
1614 if self._cache[0] == node:
1615 # _cache only stores rawtext
1615 # _cache only stores rawtext
1616 if raw:
1616 if raw:
1617 return self._cache[2]
1617 return self._cache[2]
1618 # duplicated, but good for perf
1618 # duplicated, but good for perf
1619 if rev is None:
1619 if rev is None:
1620 rev = self.rev(node)
1620 rev = self.rev(node)
1621 if flags is None:
1621 if flags is None:
1622 flags = self.flags(rev)
1622 flags = self.flags(rev)
1623 # no extra flags set, no flag processor runs, text = rawtext
1623 # no extra flags set, no flag processor runs, text = rawtext
1624 if flags == REVIDX_DEFAULT_FLAGS:
1624 if flags == REVIDX_DEFAULT_FLAGS:
1625 return self._cache[2]
1625 return self._cache[2]
1626 # rawtext is reusable. need to run flag processor
1626 # rawtext is reusable. need to run flag processor
1627 rawtext = self._cache[2]
1627 rawtext = self._cache[2]
1628
1628
1629 cachedrev = self._cache[1]
1629 cachedrev = self._cache[1]
1630
1630
1631 # look up what we need to read
1631 # look up what we need to read
1632 if rawtext is None:
1632 if rawtext is None:
1633 if rev is None:
1633 if rev is None:
1634 rev = self.rev(node)
1634 rev = self.rev(node)
1635
1635
1636 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1636 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1637 if stopped:
1637 if stopped:
1638 rawtext = self._cache[2]
1638 rawtext = self._cache[2]
1639
1639
1640 # drop cache to save memory
1640 # drop cache to save memory
1641 self._cache = None
1641 self._cache = None
1642
1642
1643 targetsize = None
1643 targetsize = None
1644 rawsize = self.index[rev][2]
1644 rawsize = self.index[rev][2]
1645 if 0 <= rawsize:
1645 if 0 <= rawsize:
1646 targetsize = 4 * rawsize
1646 targetsize = 4 * rawsize
1647
1647
1648 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1648 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1649 if rawtext is None:
1649 if rawtext is None:
1650 rawtext = bytes(bins[0])
1650 rawtext = bytes(bins[0])
1651 bins = bins[1:]
1651 bins = bins[1:]
1652
1652
1653 rawtext = mdiff.patches(rawtext, bins)
1653 rawtext = mdiff.patches(rawtext, bins)
1654 self._cache = (node, rev, rawtext)
1654 self._cache = (node, rev, rawtext)
1655
1655
1656 if flags is None:
1656 if flags is None:
1657 if rev is None:
1657 if rev is None:
1658 rev = self.rev(node)
1658 rev = self.rev(node)
1659 flags = self.flags(rev)
1659 flags = self.flags(rev)
1660
1660
1661 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1661 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1662 if validatehash:
1662 if validatehash:
1663 self.checkhash(text, node, rev=rev)
1663 self.checkhash(text, node, rev=rev)
1664
1664
1665 return text
1665 return text
1666
1666
1667 def hash(self, text, p1, p2):
1667 def hash(self, text, p1, p2):
1668 """Compute a node hash.
1668 """Compute a node hash.
1669
1669
1670 Available as a function so that subclasses can replace the hash
1670 Available as a function so that subclasses can replace the hash
1671 as needed.
1671 as needed.
1672 """
1672 """
1673 return hash(text, p1, p2)
1673 return hash(text, p1, p2)
1674
1674
1675 def _processflags(self, text, flags, operation, raw=False):
1675 def _processflags(self, text, flags, operation, raw=False):
1676 """Inspect revision data flags and applies transforms defined by
1676 """Inspect revision data flags and applies transforms defined by
1677 registered flag processors.
1677 registered flag processors.
1678
1678
1679 ``text`` - the revision data to process
1679 ``text`` - the revision data to process
1680 ``flags`` - the revision flags
1680 ``flags`` - the revision flags
1681 ``operation`` - the operation being performed (read or write)
1681 ``operation`` - the operation being performed (read or write)
1682 ``raw`` - an optional argument describing if the raw transform should be
1682 ``raw`` - an optional argument describing if the raw transform should be
1683 applied.
1683 applied.
1684
1684
1685 This method processes the flags in the order (or reverse order if
1685 This method processes the flags in the order (or reverse order if
1686 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1686 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1687 flag processors registered for present flags. The order of flags defined
1687 flag processors registered for present flags. The order of flags defined
1688 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1688 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1689
1689
1690 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1690 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1691 processed text and ``validatehash`` is a bool indicating whether the
1691 processed text and ``validatehash`` is a bool indicating whether the
1692 returned text should be checked for hash integrity.
1692 returned text should be checked for hash integrity.
1693
1693
1694 Note: If the ``raw`` argument is set, it has precedence over the
1694 Note: If the ``raw`` argument is set, it has precedence over the
1695 operation and will only update the value of ``validatehash``.
1695 operation and will only update the value of ``validatehash``.
1696 """
1696 """
1697 # fast path: no flag processors will run
1697 # fast path: no flag processors will run
1698 if flags == 0:
1698 if flags == 0:
1699 return text, True
1699 return text, True
1700 if not operation in ('read', 'write'):
1700 if not operation in ('read', 'write'):
1701 raise error.ProgrammingError(_("invalid '%s' operation") %
1701 raise error.ProgrammingError(_("invalid '%s' operation") %
1702 operation)
1702 operation)
1703 # Check all flags are known.
1703 # Check all flags are known.
1704 if flags & ~REVIDX_KNOWN_FLAGS:
1704 if flags & ~REVIDX_KNOWN_FLAGS:
1705 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1705 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1706 (flags & ~REVIDX_KNOWN_FLAGS))
1706 (flags & ~REVIDX_KNOWN_FLAGS))
1707 validatehash = True
1707 validatehash = True
1708 # Depending on the operation (read or write), the order might be
1708 # Depending on the operation (read or write), the order might be
1709 # reversed due to non-commutative transforms.
1709 # reversed due to non-commutative transforms.
1710 orderedflags = REVIDX_FLAGS_ORDER
1710 orderedflags = REVIDX_FLAGS_ORDER
1711 if operation == 'write':
1711 if operation == 'write':
1712 orderedflags = reversed(orderedflags)
1712 orderedflags = reversed(orderedflags)
1713
1713
1714 for flag in orderedflags:
1714 for flag in orderedflags:
1715 # If a flagprocessor has been registered for a known flag, apply the
1715 # If a flagprocessor has been registered for a known flag, apply the
1716 # related operation transform and update result tuple.
1716 # related operation transform and update result tuple.
1717 if flag & flags:
1717 if flag & flags:
1718 vhash = True
1718 vhash = True
1719
1719
1720 if flag not in self._flagprocessors:
1720 if flag not in self._flagprocessors:
1721 message = _("missing processor for flag '%#x'") % (flag)
1721 message = _("missing processor for flag '%#x'") % (flag)
1722 raise error.RevlogError(message)
1722 raise error.RevlogError(message)
1723
1723
1724 processor = self._flagprocessors[flag]
1724 processor = self._flagprocessors[flag]
1725 if processor is not None:
1725 if processor is not None:
1726 readtransform, writetransform, rawtransform = processor
1726 readtransform, writetransform, rawtransform = processor
1727
1727
1728 if raw:
1728 if raw:
1729 vhash = rawtransform(self, text)
1729 vhash = rawtransform(self, text)
1730 elif operation == 'read':
1730 elif operation == 'read':
1731 text, vhash = readtransform(self, text)
1731 text, vhash = readtransform(self, text)
1732 else: # write operation
1732 else: # write operation
1733 text, vhash = writetransform(self, text)
1733 text, vhash = writetransform(self, text)
1734 validatehash = validatehash and vhash
1734 validatehash = validatehash and vhash
1735
1735
1736 return text, validatehash
1736 return text, validatehash
1737
1737
1738 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1738 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1739 """Check node hash integrity.
1739 """Check node hash integrity.
1740
1740
1741 Available as a function so that subclasses can extend hash mismatch
1741 Available as a function so that subclasses can extend hash mismatch
1742 behaviors as needed.
1742 behaviors as needed.
1743 """
1743 """
1744 try:
1744 try:
1745 if p1 is None and p2 is None:
1745 if p1 is None and p2 is None:
1746 p1, p2 = self.parents(node)
1746 p1, p2 = self.parents(node)
1747 if node != self.hash(text, p1, p2):
1747 if node != self.hash(text, p1, p2):
1748 revornode = rev
1748 revornode = rev
1749 if revornode is None:
1749 if revornode is None:
1750 revornode = templatefilters.short(hex(node))
1750 revornode = templatefilters.short(hex(node))
1751 raise error.RevlogError(_("integrity check failed on %s:%s")
1751 raise error.RevlogError(_("integrity check failed on %s:%s")
1752 % (self.indexfile, pycompat.bytestr(revornode)))
1752 % (self.indexfile, pycompat.bytestr(revornode)))
1753 except error.RevlogError:
1753 except error.RevlogError:
1754 if self._censorable and _censoredtext(text):
1754 if self._censorable and _censoredtext(text):
1755 raise error.CensoredNodeError(self.indexfile, node, text)
1755 raise error.CensoredNodeError(self.indexfile, node, text)
1756 raise
1756 raise
1757
1757
1758 def _enforceinlinesize(self, tr, fp=None):
1758 def _enforceinlinesize(self, tr, fp=None):
1759 """Check if the revlog is too big for inline and convert if so.
1759 """Check if the revlog is too big for inline and convert if so.
1760
1760
1761 This should be called after revisions are added to the revlog. If the
1761 This should be called after revisions are added to the revlog. If the
1762 revlog has grown too large to be an inline revlog, it will convert it
1762 revlog has grown too large to be an inline revlog, it will convert it
1763 to use multiple index and data files.
1763 to use multiple index and data files.
1764 """
1764 """
1765 tiprev = len(self) - 1
1765 tiprev = len(self) - 1
1766 if (not self._inline or
1766 if (not self._inline or
1767 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1767 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1768 return
1768 return
1769
1769
1770 trinfo = tr.find(self.indexfile)
1770 trinfo = tr.find(self.indexfile)
1771 if trinfo is None:
1771 if trinfo is None:
1772 raise error.RevlogError(_("%s not found in the transaction")
1772 raise error.RevlogError(_("%s not found in the transaction")
1773 % self.indexfile)
1773 % self.indexfile)
1774
1774
1775 trindex = trinfo[2]
1775 trindex = trinfo[2]
1776 if trindex is not None:
1776 if trindex is not None:
1777 dataoff = self.start(trindex)
1777 dataoff = self.start(trindex)
1778 else:
1778 else:
1779 # revlog was stripped at start of transaction, use all leftover data
1779 # revlog was stripped at start of transaction, use all leftover data
1780 trindex = len(self) - 1
1780 trindex = len(self) - 1
1781 dataoff = self.end(tiprev)
1781 dataoff = self.end(tiprev)
1782
1782
1783 tr.add(self.datafile, dataoff)
1783 tr.add(self.datafile, dataoff)
1784
1784
1785 if fp:
1785 if fp:
1786 fp.flush()
1786 fp.flush()
1787 fp.close()
1787 fp.close()
1788
1788
1789 with self._datafp('w') as df:
1789 with self._datafp('w') as df:
1790 for r in self:
1790 for r in self:
1791 df.write(self._getsegmentforrevs(r, r)[1])
1791 df.write(self._getsegmentforrevs(r, r)[1])
1792
1792
1793 with self._indexfp('w') as fp:
1793 with self._indexfp('w') as fp:
1794 self.version &= ~FLAG_INLINE_DATA
1794 self.version &= ~FLAG_INLINE_DATA
1795 self._inline = False
1795 self._inline = False
1796 io = self._io
1796 io = self._io
1797 for i in self:
1797 for i in self:
1798 e = io.packentry(self.index[i], self.node, self.version, i)
1798 e = io.packentry(self.index[i], self.node, self.version, i)
1799 fp.write(e)
1799 fp.write(e)
1800
1800
1801 # the temp file replace the real index when we exit the context
1801 # the temp file replace the real index when we exit the context
1802 # manager
1802 # manager
1803
1803
1804 tr.replace(self.indexfile, trindex * self._io.size)
1804 tr.replace(self.indexfile, trindex * self._io.size)
1805 self._chunkclear()
1805 self._chunkclear()
1806
1806
1807 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1807 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1808 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1808 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1809 """add a revision to the log
1809 """add a revision to the log
1810
1810
1811 text - the revision data to add
1811 text - the revision data to add
1812 transaction - the transaction object used for rollback
1812 transaction - the transaction object used for rollback
1813 link - the linkrev data to add
1813 link - the linkrev data to add
1814 p1, p2 - the parent nodeids of the revision
1814 p1, p2 - the parent nodeids of the revision
1815 cachedelta - an optional precomputed delta
1815 cachedelta - an optional precomputed delta
1816 node - nodeid of revision; typically node is not specified, and it is
1816 node - nodeid of revision; typically node is not specified, and it is
1817 computed by default as hash(text, p1, p2), however subclasses might
1817 computed by default as hash(text, p1, p2), however subclasses might
1818 use different hashing method (and override checkhash() in such case)
1818 use different hashing method (and override checkhash() in such case)
1819 flags - the known flags to set on the revision
1819 flags - the known flags to set on the revision
1820 deltacomputer - an optional deltacomputer instance shared between
1820 deltacomputer - an optional deltacomputer instance shared between
1821 multiple calls
1821 multiple calls
1822 """
1822 """
1823 if link == nullrev:
1823 if link == nullrev:
1824 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1824 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1825 % self.indexfile)
1825 % self.indexfile)
1826
1826
1827 if flags:
1827 if flags:
1828 node = node or self.hash(text, p1, p2)
1828 node = node or self.hash(text, p1, p2)
1829
1829
1830 rawtext, validatehash = self._processflags(text, flags, 'write')
1830 rawtext, validatehash = self._processflags(text, flags, 'write')
1831
1831
1832 # If the flag processor modifies the revision data, ignore any provided
1832 # If the flag processor modifies the revision data, ignore any provided
1833 # cachedelta.
1833 # cachedelta.
1834 if rawtext != text:
1834 if rawtext != text:
1835 cachedelta = None
1835 cachedelta = None
1836
1836
1837 if len(rawtext) > _maxentrysize:
1837 if len(rawtext) > _maxentrysize:
1838 raise error.RevlogError(
1838 raise error.RevlogError(
1839 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1839 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1840 % (self.indexfile, len(rawtext)))
1840 % (self.indexfile, len(rawtext)))
1841
1841
1842 node = node or self.hash(rawtext, p1, p2)
1842 node = node or self.hash(rawtext, p1, p2)
1843 if node in self.nodemap:
1843 if node in self.nodemap:
1844 return node
1844 return node
1845
1845
1846 if validatehash:
1846 if validatehash:
1847 self.checkhash(rawtext, node, p1=p1, p2=p2)
1847 self.checkhash(rawtext, node, p1=p1, p2=p2)
1848
1848
1849 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1849 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1850 flags, cachedelta=cachedelta,
1850 flags, cachedelta=cachedelta,
1851 deltacomputer=deltacomputer)
1851 deltacomputer=deltacomputer)
1852
1852
1853 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1853 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1854 cachedelta=None, deltacomputer=None):
1854 cachedelta=None, deltacomputer=None):
1855 """add a raw revision with known flags, node and parents
1855 """add a raw revision with known flags, node and parents
1856 useful when reusing a revision not stored in this revlog (ex: received
1856 useful when reusing a revision not stored in this revlog (ex: received
1857 over wire, or read from an external bundle).
1857 over wire, or read from an external bundle).
1858 """
1858 """
1859 dfh = None
1859 dfh = None
1860 if not self._inline:
1860 if not self._inline:
1861 dfh = self._datafp("a+")
1861 dfh = self._datafp("a+")
1862 ifh = self._indexfp("a+")
1862 ifh = self._indexfp("a+")
1863 try:
1863 try:
1864 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1864 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1865 flags, cachedelta, ifh, dfh,
1865 flags, cachedelta, ifh, dfh,
1866 deltacomputer=deltacomputer)
1866 deltacomputer=deltacomputer)
1867 finally:
1867 finally:
1868 if dfh:
1868 if dfh:
1869 dfh.close()
1869 dfh.close()
1870 ifh.close()
1870 ifh.close()
1871
1871
1872 def compress(self, data):
1872 def compress(self, data):
1873 """Generate a possibly-compressed representation of data."""
1873 """Generate a possibly-compressed representation of data."""
1874 if not data:
1874 if not data:
1875 return '', data
1875 return '', data
1876
1876
1877 compressed = self._compressor.compress(data)
1877 compressed = self._compressor.compress(data)
1878
1878
1879 if compressed:
1879 if compressed:
1880 # The revlog compressor added the header in the returned data.
1880 # The revlog compressor added the header in the returned data.
1881 return '', compressed
1881 return '', compressed
1882
1882
1883 if data[0:1] == '\0':
1883 if data[0:1] == '\0':
1884 return '', data
1884 return '', data
1885 return 'u', data
1885 return 'u', data
1886
1886
1887 def decompress(self, data):
1887 def decompress(self, data):
1888 """Decompress a revlog chunk.
1888 """Decompress a revlog chunk.
1889
1889
1890 The chunk is expected to begin with a header identifying the
1890 The chunk is expected to begin with a header identifying the
1891 format type so it can be routed to an appropriate decompressor.
1891 format type so it can be routed to an appropriate decompressor.
1892 """
1892 """
1893 if not data:
1893 if not data:
1894 return data
1894 return data
1895
1895
1896 # Revlogs are read much more frequently than they are written and many
1896 # Revlogs are read much more frequently than they are written and many
1897 # chunks only take microseconds to decompress, so performance is
1897 # chunks only take microseconds to decompress, so performance is
1898 # important here.
1898 # important here.
1899 #
1899 #
1900 # We can make a few assumptions about revlogs:
1900 # We can make a few assumptions about revlogs:
1901 #
1901 #
1902 # 1) the majority of chunks will be compressed (as opposed to inline
1902 # 1) the majority of chunks will be compressed (as opposed to inline
1903 # raw data).
1903 # raw data).
1904 # 2) decompressing *any* data will likely by at least 10x slower than
1904 # 2) decompressing *any* data will likely by at least 10x slower than
1905 # returning raw inline data.
1905 # returning raw inline data.
1906 # 3) we want to prioritize common and officially supported compression
1906 # 3) we want to prioritize common and officially supported compression
1907 # engines
1907 # engines
1908 #
1908 #
1909 # It follows that we want to optimize for "decompress compressed data
1909 # It follows that we want to optimize for "decompress compressed data
1910 # when encoded with common and officially supported compression engines"
1910 # when encoded with common and officially supported compression engines"
1911 # case over "raw data" and "data encoded by less common or non-official
1911 # case over "raw data" and "data encoded by less common or non-official
1912 # compression engines." That is why we have the inline lookup first
1912 # compression engines." That is why we have the inline lookup first
1913 # followed by the compengines lookup.
1913 # followed by the compengines lookup.
1914 #
1914 #
1915 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1915 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1916 # compressed chunks. And this matters for changelog and manifest reads.
1916 # compressed chunks. And this matters for changelog and manifest reads.
1917 t = data[0:1]
1917 t = data[0:1]
1918
1918
1919 if t == 'x':
1919 if t == 'x':
1920 try:
1920 try:
1921 return _zlibdecompress(data)
1921 return _zlibdecompress(data)
1922 except zlib.error as e:
1922 except zlib.error as e:
1923 raise error.RevlogError(_('revlog decompress error: %s') %
1923 raise error.RevlogError(_('revlog decompress error: %s') %
1924 stringutil.forcebytestr(e))
1924 stringutil.forcebytestr(e))
1925 # '\0' is more common than 'u' so it goes first.
1925 # '\0' is more common than 'u' so it goes first.
1926 elif t == '\0':
1926 elif t == '\0':
1927 return data
1927 return data
1928 elif t == 'u':
1928 elif t == 'u':
1929 return util.buffer(data, 1)
1929 return util.buffer(data, 1)
1930
1930
1931 try:
1931 try:
1932 compressor = self._decompressors[t]
1932 compressor = self._decompressors[t]
1933 except KeyError:
1933 except KeyError:
1934 try:
1934 try:
1935 engine = util.compengines.forrevlogheader(t)
1935 engine = util.compengines.forrevlogheader(t)
1936 compressor = engine.revlogcompressor()
1936 compressor = engine.revlogcompressor()
1937 self._decompressors[t] = compressor
1937 self._decompressors[t] = compressor
1938 except KeyError:
1938 except KeyError:
1939 raise error.RevlogError(_('unknown compression type %r') % t)
1939 raise error.RevlogError(_('unknown compression type %r') % t)
1940
1940
1941 return compressor.decompress(data)
1941 return compressor.decompress(data)
1942
1942
1943 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1943 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1944 cachedelta, ifh, dfh, alwayscache=False,
1944 cachedelta, ifh, dfh, alwayscache=False,
1945 deltacomputer=None):
1945 deltacomputer=None):
1946 """internal function to add revisions to the log
1946 """internal function to add revisions to the log
1947
1947
1948 see addrevision for argument descriptions.
1948 see addrevision for argument descriptions.
1949
1949
1950 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1950 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1951
1951
1952 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1952 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1953 be used.
1953 be used.
1954
1954
1955 invariants:
1955 invariants:
1956 - rawtext is optional (can be None); if not set, cachedelta must be set.
1956 - rawtext is optional (can be None); if not set, cachedelta must be set.
1957 if both are set, they must correspond to each other.
1957 if both are set, they must correspond to each other.
1958 """
1958 """
1959 if node == nullid:
1959 if node == nullid:
1960 raise error.RevlogError(_("%s: attempt to add null revision") %
1960 raise error.RevlogError(_("%s: attempt to add null revision") %
1961 self.indexfile)
1961 self.indexfile)
1962 if node == wdirid or node in wdirfilenodeids:
1962 if node == wdirid or node in wdirfilenodeids:
1963 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1963 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1964 self.indexfile)
1964 self.indexfile)
1965
1965
1966 if self._inline:
1966 if self._inline:
1967 fh = ifh
1967 fh = ifh
1968 else:
1968 else:
1969 fh = dfh
1969 fh = dfh
1970
1970
1971 btext = [rawtext]
1971 btext = [rawtext]
1972
1972
1973 curr = len(self)
1973 curr = len(self)
1974 prev = curr - 1
1974 prev = curr - 1
1975 offset = self.end(prev)
1975 offset = self.end(prev)
1976 p1r, p2r = self.rev(p1), self.rev(p2)
1976 p1r, p2r = self.rev(p1), self.rev(p2)
1977
1977
1978 # full versions are inserted when the needed deltas
1978 # full versions are inserted when the needed deltas
1979 # become comparable to the uncompressed text
1979 # become comparable to the uncompressed text
1980 if rawtext is None:
1980 if rawtext is None:
1981 # need rawtext size, before changed by flag processors, which is
1981 # need rawtext size, before changed by flag processors, which is
1982 # the non-raw size. use revlog explicitly to avoid filelog's extra
1982 # the non-raw size. use revlog explicitly to avoid filelog's extra
1983 # logic that might remove metadata size.
1983 # logic that might remove metadata size.
1984 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1984 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1985 cachedelta[1])
1985 cachedelta[1])
1986 else:
1986 else:
1987 textlen = len(rawtext)
1987 textlen = len(rawtext)
1988
1988
1989 if deltacomputer is None:
1989 if deltacomputer is None:
1990 deltacomputer = deltautil.deltacomputer(self)
1990 deltacomputer = deltautil.deltacomputer(self)
1991
1991
1992 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1992 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1993
1993
1994 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1994 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1995
1995
1996 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
1996 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
1997 deltainfo.base, link, p1r, p2r, node)
1997 deltainfo.base, link, p1r, p2r, node)
1998 self.index.append(e)
1998 self.index.append(e)
1999 self.nodemap[node] = curr
1999 self.nodemap[node] = curr
2000
2000
2001 entry = self._io.packentry(e, self.node, self.version, curr)
2001 entry = self._io.packentry(e, self.node, self.version, curr)
2002 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2002 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2003 link, offset)
2003 link, offset)
2004
2004
2005 rawtext = btext[0]
2005 rawtext = btext[0]
2006
2006
2007 if alwayscache and rawtext is None:
2007 if alwayscache and rawtext is None:
2008 rawtext = deltacomputer.buildtext(revinfo, fh)
2008 rawtext = deltacomputer.buildtext(revinfo, fh)
2009
2009
2010 if type(rawtext) == bytes: # only accept immutable objects
2010 if type(rawtext) == bytes: # only accept immutable objects
2011 self._cache = (node, curr, rawtext)
2011 self._cache = (node, curr, rawtext)
2012 self._chainbasecache[curr] = deltainfo.chainbase
2012 self._chainbasecache[curr] = deltainfo.chainbase
2013 return node
2013 return node
2014
2014
2015 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2015 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2016 # Files opened in a+ mode have inconsistent behavior on various
2016 # Files opened in a+ mode have inconsistent behavior on various
2017 # platforms. Windows requires that a file positioning call be made
2017 # platforms. Windows requires that a file positioning call be made
2018 # when the file handle transitions between reads and writes. See
2018 # when the file handle transitions between reads and writes. See
2019 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2019 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2020 # platforms, Python or the platform itself can be buggy. Some versions
2020 # platforms, Python or the platform itself can be buggy. Some versions
2021 # of Solaris have been observed to not append at the end of the file
2021 # of Solaris have been observed to not append at the end of the file
2022 # if the file was seeked to before the end. See issue4943 for more.
2022 # if the file was seeked to before the end. See issue4943 for more.
2023 #
2023 #
2024 # We work around this issue by inserting a seek() before writing.
2024 # We work around this issue by inserting a seek() before writing.
2025 # Note: This is likely not necessary on Python 3.
2025 # Note: This is likely not necessary on Python 3.
2026 ifh.seek(0, os.SEEK_END)
2026 ifh.seek(0, os.SEEK_END)
2027 if dfh:
2027 if dfh:
2028 dfh.seek(0, os.SEEK_END)
2028 dfh.seek(0, os.SEEK_END)
2029
2029
2030 curr = len(self) - 1
2030 curr = len(self) - 1
2031 if not self._inline:
2031 if not self._inline:
2032 transaction.add(self.datafile, offset)
2032 transaction.add(self.datafile, offset)
2033 transaction.add(self.indexfile, curr * len(entry))
2033 transaction.add(self.indexfile, curr * len(entry))
2034 if data[0]:
2034 if data[0]:
2035 dfh.write(data[0])
2035 dfh.write(data[0])
2036 dfh.write(data[1])
2036 dfh.write(data[1])
2037 ifh.write(entry)
2037 ifh.write(entry)
2038 else:
2038 else:
2039 offset += curr * self._io.size
2039 offset += curr * self._io.size
2040 transaction.add(self.indexfile, offset, curr)
2040 transaction.add(self.indexfile, offset, curr)
2041 ifh.write(entry)
2041 ifh.write(entry)
2042 ifh.write(data[0])
2042 ifh.write(data[0])
2043 ifh.write(data[1])
2043 ifh.write(data[1])
2044 self._enforceinlinesize(transaction, ifh)
2044 self._enforceinlinesize(transaction, ifh)
2045
2045
2046 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2046 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2047 """
2047 """
2048 add a delta group
2048 add a delta group
2049
2049
2050 given a set of deltas, add them to the revision log. the
2050 given a set of deltas, add them to the revision log. the
2051 first delta is against its parent, which should be in our
2051 first delta is against its parent, which should be in our
2052 log, the rest are against the previous delta.
2052 log, the rest are against the previous delta.
2053
2053
2054 If ``addrevisioncb`` is defined, it will be called with arguments of
2054 If ``addrevisioncb`` is defined, it will be called with arguments of
2055 this revlog and the node that was added.
2055 this revlog and the node that was added.
2056 """
2056 """
2057
2057
2058 nodes = []
2058 nodes = []
2059
2059
2060 r = len(self)
2060 r = len(self)
2061 end = 0
2061 end = 0
2062 if r:
2062 if r:
2063 end = self.end(r - 1)
2063 end = self.end(r - 1)
2064 ifh = self._indexfp("a+")
2064 ifh = self._indexfp("a+")
2065 isize = r * self._io.size
2065 isize = r * self._io.size
2066 if self._inline:
2066 if self._inline:
2067 transaction.add(self.indexfile, end + isize, r)
2067 transaction.add(self.indexfile, end + isize, r)
2068 dfh = None
2068 dfh = None
2069 else:
2069 else:
2070 transaction.add(self.indexfile, isize, r)
2070 transaction.add(self.indexfile, isize, r)
2071 transaction.add(self.datafile, end)
2071 transaction.add(self.datafile, end)
2072 dfh = self._datafp("a+")
2072 dfh = self._datafp("a+")
2073 def flush():
2073 def flush():
2074 if dfh:
2074 if dfh:
2075 dfh.flush()
2075 dfh.flush()
2076 ifh.flush()
2076 ifh.flush()
2077 try:
2077 try:
2078 deltacomputer = deltautil.deltacomputer(self)
2078 deltacomputer = deltautil.deltacomputer(self)
2079 # loop through our set of deltas
2079 # loop through our set of deltas
2080 for data in deltas:
2080 for data in deltas:
2081 node, p1, p2, linknode, deltabase, delta, flags = data
2081 node, p1, p2, linknode, deltabase, delta, flags = data
2082 link = linkmapper(linknode)
2082 link = linkmapper(linknode)
2083 flags = flags or REVIDX_DEFAULT_FLAGS
2083 flags = flags or REVIDX_DEFAULT_FLAGS
2084
2084
2085 nodes.append(node)
2085 nodes.append(node)
2086
2086
2087 if node in self.nodemap:
2087 if node in self.nodemap:
2088 # this can happen if two branches make the same change
2088 # this can happen if two branches make the same change
2089 continue
2089 continue
2090
2090
2091 for p in (p1, p2):
2091 for p in (p1, p2):
2092 if p not in self.nodemap:
2092 if p not in self.nodemap:
2093 raise error.LookupError(p, self.indexfile,
2093 raise error.LookupError(p, self.indexfile,
2094 _('unknown parent'))
2094 _('unknown parent'))
2095
2095
2096 if deltabase not in self.nodemap:
2096 if deltabase not in self.nodemap:
2097 raise error.LookupError(deltabase, self.indexfile,
2097 raise error.LookupError(deltabase, self.indexfile,
2098 _('unknown delta base'))
2098 _('unknown delta base'))
2099
2099
2100 baserev = self.rev(deltabase)
2100 baserev = self.rev(deltabase)
2101
2101
2102 if baserev != nullrev and self.iscensored(baserev):
2102 if baserev != nullrev and self.iscensored(baserev):
2103 # if base is censored, delta must be full replacement in a
2103 # if base is censored, delta must be full replacement in a
2104 # single patch operation
2104 # single patch operation
2105 hlen = struct.calcsize(">lll")
2105 hlen = struct.calcsize(">lll")
2106 oldlen = self.rawsize(baserev)
2106 oldlen = self.rawsize(baserev)
2107 newlen = len(delta) - hlen
2107 newlen = len(delta) - hlen
2108 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2108 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2109 raise error.CensoredBaseError(self.indexfile,
2109 raise error.CensoredBaseError(self.indexfile,
2110 self.node(baserev))
2110 self.node(baserev))
2111
2111
2112 if not flags and self._peek_iscensored(baserev, delta, flush):
2112 if not flags and self._peek_iscensored(baserev, delta, flush):
2113 flags |= REVIDX_ISCENSORED
2113 flags |= REVIDX_ISCENSORED
2114
2114
2115 # We assume consumers of addrevisioncb will want to retrieve
2115 # We assume consumers of addrevisioncb will want to retrieve
2116 # the added revision, which will require a call to
2116 # the added revision, which will require a call to
2117 # revision(). revision() will fast path if there is a cache
2117 # revision(). revision() will fast path if there is a cache
2118 # hit. So, we tell _addrevision() to always cache in this case.
2118 # hit. So, we tell _addrevision() to always cache in this case.
2119 # We're only using addgroup() in the context of changegroup
2119 # We're only using addgroup() in the context of changegroup
2120 # generation so the revision data can always be handled as raw
2120 # generation so the revision data can always be handled as raw
2121 # by the flagprocessor.
2121 # by the flagprocessor.
2122 self._addrevision(node, None, transaction, link,
2122 self._addrevision(node, None, transaction, link,
2123 p1, p2, flags, (baserev, delta),
2123 p1, p2, flags, (baserev, delta),
2124 ifh, dfh,
2124 ifh, dfh,
2125 alwayscache=bool(addrevisioncb),
2125 alwayscache=bool(addrevisioncb),
2126 deltacomputer=deltacomputer)
2126 deltacomputer=deltacomputer)
2127
2127
2128 if addrevisioncb:
2128 if addrevisioncb:
2129 addrevisioncb(self, node)
2129 addrevisioncb(self, node)
2130
2130
2131 if not dfh and not self._inline:
2131 if not dfh and not self._inline:
2132 # addrevision switched from inline to conventional
2132 # addrevision switched from inline to conventional
2133 # reopen the index
2133 # reopen the index
2134 ifh.close()
2134 ifh.close()
2135 dfh = self._datafp("a+")
2135 dfh = self._datafp("a+")
2136 ifh = self._indexfp("a+")
2136 ifh = self._indexfp("a+")
2137 finally:
2137 finally:
2138 if dfh:
2138 if dfh:
2139 dfh.close()
2139 dfh.close()
2140 ifh.close()
2140 ifh.close()
2141
2141
2142 return nodes
2142 return nodes
2143
2143
2144 def iscensored(self, rev):
2144 def iscensored(self, rev):
2145 """Check if a file revision is censored."""
2145 """Check if a file revision is censored."""
2146 if not self._censorable:
2146 if not self._censorable:
2147 return False
2147 return False
2148
2148
2149 return self.flags(rev) & REVIDX_ISCENSORED
2149 return self.flags(rev) & REVIDX_ISCENSORED
2150
2150
2151 def _peek_iscensored(self, baserev, delta, flush):
2151 def _peek_iscensored(self, baserev, delta, flush):
2152 """Quickly check if a delta produces a censored revision."""
2152 """Quickly check if a delta produces a censored revision."""
2153 if not self._censorable:
2153 if not self._censorable:
2154 return False
2154 return False
2155
2155
2156 # Fragile heuristic: unless new file meta keys are added alphabetically
2156 # Fragile heuristic: unless new file meta keys are added alphabetically
2157 # preceding "censored", all censored revisions are prefixed by
2157 # preceding "censored", all censored revisions are prefixed by
2158 # "\1\ncensored:". A delta producing such a censored revision must be a
2158 # "\1\ncensored:". A delta producing such a censored revision must be a
2159 # full-replacement delta, so we inspect the first and only patch in the
2159 # full-replacement delta, so we inspect the first and only patch in the
2160 # delta for this prefix.
2160 # delta for this prefix.
2161 hlen = struct.calcsize(">lll")
2161 hlen = struct.calcsize(">lll")
2162 if len(delta) <= hlen:
2162 if len(delta) <= hlen:
2163 return False
2163 return False
2164
2164
2165 oldlen = self.rawsize(baserev)
2165 oldlen = self.rawsize(baserev)
2166 newlen = len(delta) - hlen
2166 newlen = len(delta) - hlen
2167 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2167 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2168 return False
2168 return False
2169
2169
2170 add = "\1\ncensored:"
2170 add = "\1\ncensored:"
2171 addlen = len(add)
2171 addlen = len(add)
2172 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2172 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2173
2173
2174 def getstrippoint(self, minlink):
2174 def getstrippoint(self, minlink):
2175 """find the minimum rev that must be stripped to strip the linkrev
2175 """find the minimum rev that must be stripped to strip the linkrev
2176
2176
2177 Returns a tuple containing the minimum rev and a set of all revs that
2177 Returns a tuple containing the minimum rev and a set of all revs that
2178 have linkrevs that will be broken by this strip.
2178 have linkrevs that will be broken by this strip.
2179 """
2179 """
2180 brokenrevs = set()
2180 brokenrevs = set()
2181 strippoint = len(self)
2181 strippoint = len(self)
2182
2182
2183 heads = {}
2183 heads = {}
2184 futurelargelinkrevs = set()
2184 futurelargelinkrevs = set()
2185 for head in self.headrevs():
2185 for head in self.headrevs():
2186 headlinkrev = self.linkrev(head)
2186 headlinkrev = self.linkrev(head)
2187 heads[head] = headlinkrev
2187 heads[head] = headlinkrev
2188 if headlinkrev >= minlink:
2188 if headlinkrev >= minlink:
2189 futurelargelinkrevs.add(headlinkrev)
2189 futurelargelinkrevs.add(headlinkrev)
2190
2190
2191 # This algorithm involves walking down the rev graph, starting at the
2191 # This algorithm involves walking down the rev graph, starting at the
2192 # heads. Since the revs are topologically sorted according to linkrev,
2192 # heads. Since the revs are topologically sorted according to linkrev,
2193 # once all head linkrevs are below the minlink, we know there are
2193 # once all head linkrevs are below the minlink, we know there are
2194 # no more revs that could have a linkrev greater than minlink.
2194 # no more revs that could have a linkrev greater than minlink.
2195 # So we can stop walking.
2195 # So we can stop walking.
2196 while futurelargelinkrevs:
2196 while futurelargelinkrevs:
2197 strippoint -= 1
2197 strippoint -= 1
2198 linkrev = heads.pop(strippoint)
2198 linkrev = heads.pop(strippoint)
2199
2199
2200 if linkrev < minlink:
2200 if linkrev < minlink:
2201 brokenrevs.add(strippoint)
2201 brokenrevs.add(strippoint)
2202 else:
2202 else:
2203 futurelargelinkrevs.remove(linkrev)
2203 futurelargelinkrevs.remove(linkrev)
2204
2204
2205 for p in self.parentrevs(strippoint):
2205 for p in self.parentrevs(strippoint):
2206 if p != nullrev:
2206 if p != nullrev:
2207 plinkrev = self.linkrev(p)
2207 plinkrev = self.linkrev(p)
2208 heads[p] = plinkrev
2208 heads[p] = plinkrev
2209 if plinkrev >= minlink:
2209 if plinkrev >= minlink:
2210 futurelargelinkrevs.add(plinkrev)
2210 futurelargelinkrevs.add(plinkrev)
2211
2211
2212 return strippoint, brokenrevs
2212 return strippoint, brokenrevs
2213
2213
2214 def strip(self, minlink, transaction):
2214 def strip(self, minlink, transaction):
2215 """truncate the revlog on the first revision with a linkrev >= minlink
2215 """truncate the revlog on the first revision with a linkrev >= minlink
2216
2216
2217 This function is called when we're stripping revision minlink and
2217 This function is called when we're stripping revision minlink and
2218 its descendants from the repository.
2218 its descendants from the repository.
2219
2219
2220 We have to remove all revisions with linkrev >= minlink, because
2220 We have to remove all revisions with linkrev >= minlink, because
2221 the equivalent changelog revisions will be renumbered after the
2221 the equivalent changelog revisions will be renumbered after the
2222 strip.
2222 strip.
2223
2223
2224 So we truncate the revlog on the first of these revisions, and
2224 So we truncate the revlog on the first of these revisions, and
2225 trust that the caller has saved the revisions that shouldn't be
2225 trust that the caller has saved the revisions that shouldn't be
2226 removed and that it'll re-add them after this truncation.
2226 removed and that it'll re-add them after this truncation.
2227 """
2227 """
2228 if len(self) == 0:
2228 if len(self) == 0:
2229 return
2229 return
2230
2230
2231 rev, _ = self.getstrippoint(minlink)
2231 rev, _ = self.getstrippoint(minlink)
2232 if rev == len(self):
2232 if rev == len(self):
2233 return
2233 return
2234
2234
2235 # first truncate the files on disk
2235 # first truncate the files on disk
2236 end = self.start(rev)
2236 end = self.start(rev)
2237 if not self._inline:
2237 if not self._inline:
2238 transaction.add(self.datafile, end)
2238 transaction.add(self.datafile, end)
2239 end = rev * self._io.size
2239 end = rev * self._io.size
2240 else:
2240 else:
2241 end += rev * self._io.size
2241 end += rev * self._io.size
2242
2242
2243 transaction.add(self.indexfile, end)
2243 transaction.add(self.indexfile, end)
2244
2244
2245 # then reset internal state in memory to forget those revisions
2245 # then reset internal state in memory to forget those revisions
2246 self._cache = None
2246 self._cache = None
2247 self._chaininfocache = {}
2247 self._chaininfocache = {}
2248 self._chunkclear()
2248 self._chunkclear()
2249 for x in pycompat.xrange(rev, len(self)):
2249 for x in pycompat.xrange(rev, len(self)):
2250 del self.nodemap[self.node(x)]
2250 del self.nodemap[self.node(x)]
2251
2251
2252 del self.index[rev:-1]
2252 del self.index[rev:-1]
2253 self._nodepos = None
2253 self._nodepos = None
2254
2254
2255 def checksize(self):
2255 def checksize(self):
2256 expected = 0
2256 expected = 0
2257 if len(self):
2257 if len(self):
2258 expected = max(0, self.end(len(self) - 1))
2258 expected = max(0, self.end(len(self) - 1))
2259
2259
2260 try:
2260 try:
2261 with self._datafp() as f:
2261 with self._datafp() as f:
2262 f.seek(0, 2)
2262 f.seek(0, 2)
2263 actual = f.tell()
2263 actual = f.tell()
2264 dd = actual - expected
2264 dd = actual - expected
2265 except IOError as inst:
2265 except IOError as inst:
2266 if inst.errno != errno.ENOENT:
2266 if inst.errno != errno.ENOENT:
2267 raise
2267 raise
2268 dd = 0
2268 dd = 0
2269
2269
2270 try:
2270 try:
2271 f = self.opener(self.indexfile)
2271 f = self.opener(self.indexfile)
2272 f.seek(0, 2)
2272 f.seek(0, 2)
2273 actual = f.tell()
2273 actual = f.tell()
2274 f.close()
2274 f.close()
2275 s = self._io.size
2275 s = self._io.size
2276 i = max(0, actual // s)
2276 i = max(0, actual // s)
2277 di = actual - (i * s)
2277 di = actual - (i * s)
2278 if self._inline:
2278 if self._inline:
2279 databytes = 0
2279 databytes = 0
2280 for r in self:
2280 for r in self:
2281 databytes += max(0, self.length(r))
2281 databytes += max(0, self.length(r))
2282 dd = 0
2282 dd = 0
2283 di = actual - len(self) * s - databytes
2283 di = actual - len(self) * s - databytes
2284 except IOError as inst:
2284 except IOError as inst:
2285 if inst.errno != errno.ENOENT:
2285 if inst.errno != errno.ENOENT:
2286 raise
2286 raise
2287 di = 0
2287 di = 0
2288
2288
2289 return (dd, di)
2289 return (dd, di)
2290
2290
2291 def files(self):
2291 def files(self):
2292 res = [self.indexfile]
2292 res = [self.indexfile]
2293 if not self._inline:
2293 if not self._inline:
2294 res.append(self.datafile)
2294 res.append(self.datafile)
2295 return res
2295 return res
2296
2296
2297 def emitrevisiondeltas(self, requests):
2298 frev = self.rev
2299
2300 prevrev = None
2301 for request in requests:
2302 node = request.node
2303 rev = frev(node)
2304
2305 if prevrev is None:
2306 prevrev = self.index[rev][5]
2307
2308 # Requesting a full revision.
2309 if request.basenode == nullid:
2310 baserev = nullrev
2311 # Requesting an explicit revision.
2312 elif request.basenode is not None:
2313 baserev = frev(request.basenode)
2314 # Allowing us to choose.
2315 else:
2316 p1rev, p2rev = self.parentrevs(rev)
2317 deltaparentrev = self.deltaparent(rev)
2318
2319 # Avoid sending full revisions when delta parent is null. Pick
2320 # prev in that case. It's tempting to pick p1 in this case, as
2321 # p1 will be smaller in the common case. However, computing a
2322 # delta against p1 may require resolving the raw text of p1,
2323 # which could be expensive. The revlog caches should have prev
2324 # cached, meaning less CPU for delta generation. There is
2325 # likely room to add a flag and/or config option to control this
2326 # behavior.
2327 if deltaparentrev == nullrev and self._storedeltachains:
2328 baserev = prevrev
2329
2330 # Revlog is configured to use full snapshot for a reason.
2331 # Stick to full snapshot.
2332 elif deltaparentrev == nullrev:
2333 baserev = nullrev
2334
2335 # Pick previous when we can't be sure the base is available
2336 # on consumer.
2337 elif deltaparentrev not in (p1rev, p2rev, prevrev):
2338 baserev = prevrev
2339 else:
2340 baserev = deltaparentrev
2341
2342 if baserev != nullrev and not self.candelta(baserev, rev):
2343 baserev = nullrev
2344
2345 revision = None
2346 delta = None
2347 baserevisionsize = None
2348
2349 if self.iscensored(baserev) or self.iscensored(rev):
2350 try:
2351 revision = self.revision(node, raw=True)
2352 except error.CensoredNodeError as e:
2353 revision = e.tombstone
2354
2355 if baserev != nullrev:
2356 baserevisionsize = self.rawsize(baserev)
2357
2358 elif baserev == nullrev:
2359 revision = self.revision(node, raw=True)
2360 else:
2361 delta = self.revdiff(baserev, rev)
2362
2363 extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
2364
2365 yield revlogrevisiondelta(
2366 node=node,
2367 p1node=request.p1node,
2368 p2node=request.p2node,
2369 linknode=request.linknode,
2370 basenode=self.node(baserev),
2371 flags=self.flags(rev) | extraflags,
2372 baserevisionsize=baserevisionsize,
2373 revision=revision,
2374 delta=delta)
2375
2376 prevrev = rev
2377
2378 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2297 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2379 assumehaveparentrevisions=False, deltaprevious=False):
2298 assumehaveparentrevisions=False, deltaprevious=False):
2380 if nodesorder not in ('nodes', 'storage', None):
2299 if nodesorder not in ('nodes', 'storage', None):
2381 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2300 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2382 nodesorder)
2301 nodesorder)
2383
2302
2384 if nodesorder is None and not self._generaldelta:
2303 if nodesorder is None and not self._generaldelta:
2385 nodesorder = 'storage'
2304 nodesorder = 'storage'
2386
2305
2387 frev = self.rev
2306 frev = self.rev
2388 fnode = self.node
2307 fnode = self.node
2389
2308
2390 if nodesorder == 'nodes':
2309 if nodesorder == 'nodes':
2391 revs = [frev(n) for n in nodes]
2310 revs = [frev(n) for n in nodes]
2392 elif nodesorder == 'storage':
2311 elif nodesorder == 'storage':
2393 revs = sorted(frev(n) for n in nodes)
2312 revs = sorted(frev(n) for n in nodes)
2394 else:
2313 else:
2395 assert self._generaldelta
2314 assert self._generaldelta
2396 revs = set(frev(n) for n in nodes)
2315 revs = set(frev(n) for n in nodes)
2397 revs = dagop.linearize(revs, self.parentrevs)
2316 revs = dagop.linearize(revs, self.parentrevs)
2398
2317
2399 prevrev = None
2318 prevrev = None
2400
2319
2401 if deltaprevious or assumehaveparentrevisions:
2320 if deltaprevious or assumehaveparentrevisions:
2402 prevrev = self.parentrevs(revs[0])[0]
2321 prevrev = self.parentrevs(revs[0])[0]
2403
2322
2404 # Set of revs available to delta against.
2323 # Set of revs available to delta against.
2405 available = set()
2324 available = set()
2406
2325
2407 for rev in revs:
2326 for rev in revs:
2408 if rev == nullrev:
2327 if rev == nullrev:
2409 continue
2328 continue
2410
2329
2411 node = fnode(rev)
2330 node = fnode(rev)
2412 deltaparentrev = self.deltaparent(rev)
2331 deltaparentrev = self.deltaparent(rev)
2413 p1rev, p2rev = self.parentrevs(rev)
2332 p1rev, p2rev = self.parentrevs(rev)
2414
2333
2415 # Forced delta against previous mode.
2334 # Forced delta against previous mode.
2416 if deltaprevious:
2335 if deltaprevious:
2417 baserev = prevrev
2336 baserev = prevrev
2418
2337
2419 # Revlog is configured to use full snapshots. Stick to that.
2338 # Revlog is configured to use full snapshots. Stick to that.
2420 elif not self._storedeltachains:
2339 elif not self._storedeltachains:
2421 baserev = nullrev
2340 baserev = nullrev
2422
2341
2423 # There is a delta in storage. We try to use that because it
2342 # There is a delta in storage. We try to use that because it
2424 # amounts to effectively copying data from storage and is
2343 # amounts to effectively copying data from storage and is
2425 # therefore the fastest.
2344 # therefore the fastest.
2426 elif deltaparentrev != nullrev:
2345 elif deltaparentrev != nullrev:
2427 # Base revision was already emitted in this group. We can
2346 # Base revision was already emitted in this group. We can
2428 # always safely use the delta.
2347 # always safely use the delta.
2429 if deltaparentrev in available:
2348 if deltaparentrev in available:
2430 baserev = deltaparentrev
2349 baserev = deltaparentrev
2431
2350
2432 # Base revision is a parent that hasn't been emitted already.
2351 # Base revision is a parent that hasn't been emitted already.
2433 # Use it if we can assume the receiver has the parent revision.
2352 # Use it if we can assume the receiver has the parent revision.
2434 elif (assumehaveparentrevisions
2353 elif (assumehaveparentrevisions
2435 and deltaparentrev in (p1rev, p2rev)):
2354 and deltaparentrev in (p1rev, p2rev)):
2436 baserev = deltaparentrev
2355 baserev = deltaparentrev
2437
2356
2438 # No guarantee the receiver has the delta parent. Send delta
2357 # No guarantee the receiver has the delta parent. Send delta
2439 # against last revision (if possible), which in the common case
2358 # against last revision (if possible), which in the common case
2440 # should be similar enough to this revision that the delta is
2359 # should be similar enough to this revision that the delta is
2441 # reasonable.
2360 # reasonable.
2442 elif prevrev is not None:
2361 elif prevrev is not None:
2443 baserev = prevrev
2362 baserev = prevrev
2444 else:
2363 else:
2445 baserev = nullrev
2364 baserev = nullrev
2446
2365
2447 # Storage has a fulltext revision.
2366 # Storage has a fulltext revision.
2448
2367
2449 # Let's use the previous revision, which is as good a guess as any.
2368 # Let's use the previous revision, which is as good a guess as any.
2450 # There is definitely room to improve this logic.
2369 # There is definitely room to improve this logic.
2451 elif prevrev is not None:
2370 elif prevrev is not None:
2452 baserev = prevrev
2371 baserev = prevrev
2453 else:
2372 else:
2454 baserev = nullrev
2373 baserev = nullrev
2455
2374
2456 # But we can't actually use our chosen delta base for whatever
2375 # But we can't actually use our chosen delta base for whatever
2457 # reason. Reset to fulltext.
2376 # reason. Reset to fulltext.
2458 if baserev != nullrev and not self.candelta(baserev, rev):
2377 if baserev != nullrev and not self.candelta(baserev, rev):
2459 baserev = nullrev
2378 baserev = nullrev
2460
2379
2461 revision = None
2380 revision = None
2462 delta = None
2381 delta = None
2463 baserevisionsize = None
2382 baserevisionsize = None
2464
2383
2465 if revisiondata:
2384 if revisiondata:
2466 if self.iscensored(baserev) or self.iscensored(rev):
2385 if self.iscensored(baserev) or self.iscensored(rev):
2467 try:
2386 try:
2468 revision = self.revision(node, raw=True)
2387 revision = self.revision(node, raw=True)
2469 except error.CensoredNodeError as e:
2388 except error.CensoredNodeError as e:
2470 revision = e.tombstone
2389 revision = e.tombstone
2471
2390
2472 if baserev != nullrev:
2391 if baserev != nullrev:
2473 baserevisionsize = self.rawsize(baserev)
2392 baserevisionsize = self.rawsize(baserev)
2474
2393
2475 elif baserev == nullrev and not deltaprevious:
2394 elif baserev == nullrev and not deltaprevious:
2476 revision = self.revision(node, raw=True)
2395 revision = self.revision(node, raw=True)
2477 available.add(rev)
2396 available.add(rev)
2478 else:
2397 else:
2479 delta = self.revdiff(baserev, rev)
2398 delta = self.revdiff(baserev, rev)
2480 available.add(rev)
2399 available.add(rev)
2481
2400
2482 yield revlogrevisiondelta(
2401 yield revlogrevisiondelta(
2483 node=node,
2402 node=node,
2484 p1node=fnode(p1rev),
2403 p1node=fnode(p1rev),
2485 p2node=fnode(p2rev),
2404 p2node=fnode(p2rev),
2486 basenode=fnode(baserev),
2405 basenode=fnode(baserev),
2487 flags=self.flags(rev),
2406 flags=self.flags(rev),
2488 baserevisionsize=baserevisionsize,
2407 baserevisionsize=baserevisionsize,
2489 revision=revision,
2408 revision=revision,
2490 delta=delta)
2409 delta=delta)
2491
2410
2492 prevrev = rev
2411 prevrev = rev
2493
2412
2494 DELTAREUSEALWAYS = 'always'
2413 DELTAREUSEALWAYS = 'always'
2495 DELTAREUSESAMEREVS = 'samerevs'
2414 DELTAREUSESAMEREVS = 'samerevs'
2496 DELTAREUSENEVER = 'never'
2415 DELTAREUSENEVER = 'never'
2497
2416
2498 DELTAREUSEFULLADD = 'fulladd'
2417 DELTAREUSEFULLADD = 'fulladd'
2499
2418
2500 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2419 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2501
2420
2502 def clone(self, tr, destrevlog, addrevisioncb=None,
2421 def clone(self, tr, destrevlog, addrevisioncb=None,
2503 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2422 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2504 """Copy this revlog to another, possibly with format changes.
2423 """Copy this revlog to another, possibly with format changes.
2505
2424
2506 The destination revlog will contain the same revisions and nodes.
2425 The destination revlog will contain the same revisions and nodes.
2507 However, it may not be bit-for-bit identical due to e.g. delta encoding
2426 However, it may not be bit-for-bit identical due to e.g. delta encoding
2508 differences.
2427 differences.
2509
2428
2510 The ``deltareuse`` argument control how deltas from the existing revlog
2429 The ``deltareuse`` argument control how deltas from the existing revlog
2511 are preserved in the destination revlog. The argument can have the
2430 are preserved in the destination revlog. The argument can have the
2512 following values:
2431 following values:
2513
2432
2514 DELTAREUSEALWAYS
2433 DELTAREUSEALWAYS
2515 Deltas will always be reused (if possible), even if the destination
2434 Deltas will always be reused (if possible), even if the destination
2516 revlog would not select the same revisions for the delta. This is the
2435 revlog would not select the same revisions for the delta. This is the
2517 fastest mode of operation.
2436 fastest mode of operation.
2518 DELTAREUSESAMEREVS
2437 DELTAREUSESAMEREVS
2519 Deltas will be reused if the destination revlog would pick the same
2438 Deltas will be reused if the destination revlog would pick the same
2520 revisions for the delta. This mode strikes a balance between speed
2439 revisions for the delta. This mode strikes a balance between speed
2521 and optimization.
2440 and optimization.
2522 DELTAREUSENEVER
2441 DELTAREUSENEVER
2523 Deltas will never be reused. This is the slowest mode of execution.
2442 Deltas will never be reused. This is the slowest mode of execution.
2524 This mode can be used to recompute deltas (e.g. if the diff/delta
2443 This mode can be used to recompute deltas (e.g. if the diff/delta
2525 algorithm changes).
2444 algorithm changes).
2526
2445
2527 Delta computation can be slow, so the choice of delta reuse policy can
2446 Delta computation can be slow, so the choice of delta reuse policy can
2528 significantly affect run time.
2447 significantly affect run time.
2529
2448
2530 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2449 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2531 two extremes. Deltas will be reused if they are appropriate. But if the
2450 two extremes. Deltas will be reused if they are appropriate. But if the
2532 delta could choose a better revision, it will do so. This means if you
2451 delta could choose a better revision, it will do so. This means if you
2533 are converting a non-generaldelta revlog to a generaldelta revlog,
2452 are converting a non-generaldelta revlog to a generaldelta revlog,
2534 deltas will be recomputed if the delta's parent isn't a parent of the
2453 deltas will be recomputed if the delta's parent isn't a parent of the
2535 revision.
2454 revision.
2536
2455
2537 In addition to the delta policy, the ``deltabothparents`` argument
2456 In addition to the delta policy, the ``deltabothparents`` argument
2538 controls whether to compute deltas against both parents for merges.
2457 controls whether to compute deltas against both parents for merges.
2539 By default, the current default is used.
2458 By default, the current default is used.
2540 """
2459 """
2541 if deltareuse not in self.DELTAREUSEALL:
2460 if deltareuse not in self.DELTAREUSEALL:
2542 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2461 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2543
2462
2544 if len(destrevlog):
2463 if len(destrevlog):
2545 raise ValueError(_('destination revlog is not empty'))
2464 raise ValueError(_('destination revlog is not empty'))
2546
2465
2547 if getattr(self, 'filteredrevs', None):
2466 if getattr(self, 'filteredrevs', None):
2548 raise ValueError(_('source revlog has filtered revisions'))
2467 raise ValueError(_('source revlog has filtered revisions'))
2549 if getattr(destrevlog, 'filteredrevs', None):
2468 if getattr(destrevlog, 'filteredrevs', None):
2550 raise ValueError(_('destination revlog has filtered revisions'))
2469 raise ValueError(_('destination revlog has filtered revisions'))
2551
2470
2552 # lazydeltabase controls whether to reuse a cached delta, if possible.
2471 # lazydeltabase controls whether to reuse a cached delta, if possible.
2553 oldlazydeltabase = destrevlog._lazydeltabase
2472 oldlazydeltabase = destrevlog._lazydeltabase
2554 oldamd = destrevlog._deltabothparents
2473 oldamd = destrevlog._deltabothparents
2555
2474
2556 try:
2475 try:
2557 if deltareuse == self.DELTAREUSEALWAYS:
2476 if deltareuse == self.DELTAREUSEALWAYS:
2558 destrevlog._lazydeltabase = True
2477 destrevlog._lazydeltabase = True
2559 elif deltareuse == self.DELTAREUSESAMEREVS:
2478 elif deltareuse == self.DELTAREUSESAMEREVS:
2560 destrevlog._lazydeltabase = False
2479 destrevlog._lazydeltabase = False
2561
2480
2562 destrevlog._deltabothparents = deltabothparents or oldamd
2481 destrevlog._deltabothparents = deltabothparents or oldamd
2563
2482
2564 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2483 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2565 self.DELTAREUSESAMEREVS)
2484 self.DELTAREUSESAMEREVS)
2566
2485
2567 deltacomputer = deltautil.deltacomputer(destrevlog)
2486 deltacomputer = deltautil.deltacomputer(destrevlog)
2568 index = self.index
2487 index = self.index
2569 for rev in self:
2488 for rev in self:
2570 entry = index[rev]
2489 entry = index[rev]
2571
2490
2572 # Some classes override linkrev to take filtered revs into
2491 # Some classes override linkrev to take filtered revs into
2573 # account. Use raw entry from index.
2492 # account. Use raw entry from index.
2574 flags = entry[0] & 0xffff
2493 flags = entry[0] & 0xffff
2575 linkrev = entry[4]
2494 linkrev = entry[4]
2576 p1 = index[entry[5]][7]
2495 p1 = index[entry[5]][7]
2577 p2 = index[entry[6]][7]
2496 p2 = index[entry[6]][7]
2578 node = entry[7]
2497 node = entry[7]
2579
2498
2580 # (Possibly) reuse the delta from the revlog if allowed and
2499 # (Possibly) reuse the delta from the revlog if allowed and
2581 # the revlog chunk is a delta.
2500 # the revlog chunk is a delta.
2582 cachedelta = None
2501 cachedelta = None
2583 rawtext = None
2502 rawtext = None
2584 if populatecachedelta:
2503 if populatecachedelta:
2585 dp = self.deltaparent(rev)
2504 dp = self.deltaparent(rev)
2586 if dp != nullrev:
2505 if dp != nullrev:
2587 cachedelta = (dp, bytes(self._chunk(rev)))
2506 cachedelta = (dp, bytes(self._chunk(rev)))
2588
2507
2589 if not cachedelta:
2508 if not cachedelta:
2590 rawtext = self.revision(rev, raw=True)
2509 rawtext = self.revision(rev, raw=True)
2591
2510
2592
2511
2593 if deltareuse == self.DELTAREUSEFULLADD:
2512 if deltareuse == self.DELTAREUSEFULLADD:
2594 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2513 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2595 cachedelta=cachedelta,
2514 cachedelta=cachedelta,
2596 node=node, flags=flags,
2515 node=node, flags=flags,
2597 deltacomputer=deltacomputer)
2516 deltacomputer=deltacomputer)
2598 else:
2517 else:
2599 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2518 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2600 checkambig=False)
2519 checkambig=False)
2601 dfh = None
2520 dfh = None
2602 if not destrevlog._inline:
2521 if not destrevlog._inline:
2603 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2522 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2604 try:
2523 try:
2605 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2524 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2606 p2, flags, cachedelta, ifh, dfh,
2525 p2, flags, cachedelta, ifh, dfh,
2607 deltacomputer=deltacomputer)
2526 deltacomputer=deltacomputer)
2608 finally:
2527 finally:
2609 if dfh:
2528 if dfh:
2610 dfh.close()
2529 dfh.close()
2611 ifh.close()
2530 ifh.close()
2612
2531
2613 if addrevisioncb:
2532 if addrevisioncb:
2614 addrevisioncb(self, rev, node)
2533 addrevisioncb(self, rev, node)
2615 finally:
2534 finally:
2616 destrevlog._lazydeltabase = oldlazydeltabase
2535 destrevlog._lazydeltabase = oldlazydeltabase
2617 destrevlog._deltabothparents = oldamd
2536 destrevlog._deltabothparents = oldamd
2618
2537
2619 def censorrevision(self, node, tombstone=b''):
2538 def censorrevision(self, node, tombstone=b''):
2620 if (self.version & 0xFFFF) == REVLOGV0:
2539 if (self.version & 0xFFFF) == REVLOGV0:
2621 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2540 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2622 self.version)
2541 self.version)
2623
2542
2624 rev = self.rev(node)
2543 rev = self.rev(node)
2625 tombstone = packmeta({b'censored': tombstone}, b'')
2544 tombstone = packmeta({b'censored': tombstone}, b'')
2626
2545
2627 if len(tombstone) > self.rawsize(rev):
2546 if len(tombstone) > self.rawsize(rev):
2628 raise error.Abort(_('censor tombstone must be no longer than '
2547 raise error.Abort(_('censor tombstone must be no longer than '
2629 'censored data'))
2548 'censored data'))
2630
2549
2631 # Using two files instead of one makes it easy to rewrite entry-by-entry
2550 # Using two files instead of one makes it easy to rewrite entry-by-entry
2632 idxread = self.opener(self.indexfile, 'r')
2551 idxread = self.opener(self.indexfile, 'r')
2633 idxwrite = self.opener(self.indexfile, 'wb', atomictemp=True)
2552 idxwrite = self.opener(self.indexfile, 'wb', atomictemp=True)
2634 if self.version & FLAG_INLINE_DATA:
2553 if self.version & FLAG_INLINE_DATA:
2635 dataread, datawrite = idxread, idxwrite
2554 dataread, datawrite = idxread, idxwrite
2636 else:
2555 else:
2637 dataread = self.opener(self.datafile, 'r')
2556 dataread = self.opener(self.datafile, 'r')
2638 datawrite = self.opener(self.datafile, 'wb', atomictemp=True)
2557 datawrite = self.opener(self.datafile, 'wb', atomictemp=True)
2639
2558
2640 # Copy all revlog data up to the entry to be censored.
2559 # Copy all revlog data up to the entry to be censored.
2641 offset = self.start(rev)
2560 offset = self.start(rev)
2642
2561
2643 for chunk in util.filechunkiter(idxread, limit=rev * self._io.size):
2562 for chunk in util.filechunkiter(idxread, limit=rev * self._io.size):
2644 idxwrite.write(chunk)
2563 idxwrite.write(chunk)
2645 for chunk in util.filechunkiter(dataread, limit=offset):
2564 for chunk in util.filechunkiter(dataread, limit=offset):
2646 datawrite.write(chunk)
2565 datawrite.write(chunk)
2647
2566
2648 def rewriteindex(r, newoffs, newdata=None):
2567 def rewriteindex(r, newoffs, newdata=None):
2649 """Rewrite the index entry with a new data offset and new data.
2568 """Rewrite the index entry with a new data offset and new data.
2650
2569
2651 The newdata argument, if given, is a tuple of three positive
2570 The newdata argument, if given, is a tuple of three positive
2652 integers: (new compressed, new uncompressed, added flag bits).
2571 integers: (new compressed, new uncompressed, added flag bits).
2653 """
2572 """
2654 offlags, comp, uncomp, base, link, p1, p2, nodeid = self.index[r]
2573 offlags, comp, uncomp, base, link, p1, p2, nodeid = self.index[r]
2655 flags = gettype(offlags)
2574 flags = gettype(offlags)
2656 if newdata:
2575 if newdata:
2657 comp, uncomp, nflags = newdata
2576 comp, uncomp, nflags = newdata
2658 flags |= nflags
2577 flags |= nflags
2659 offlags = offset_type(newoffs, flags)
2578 offlags = offset_type(newoffs, flags)
2660 e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
2579 e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
2661 idxwrite.write(self._io.packentry(e, None, self.version, r))
2580 idxwrite.write(self._io.packentry(e, None, self.version, r))
2662 idxread.seek(self._io.size, 1)
2581 idxread.seek(self._io.size, 1)
2663
2582
2664 def rewrite(r, offs, data, nflags=REVIDX_DEFAULT_FLAGS):
2583 def rewrite(r, offs, data, nflags=REVIDX_DEFAULT_FLAGS):
2665 """Write the given fulltext with the given data offset.
2584 """Write the given fulltext with the given data offset.
2666
2585
2667 Returns:
2586 Returns:
2668 The integer number of data bytes written, for tracking data
2587 The integer number of data bytes written, for tracking data
2669 offsets.
2588 offsets.
2670 """
2589 """
2671 flag, compdata = self.compress(data)
2590 flag, compdata = self.compress(data)
2672 newcomp = len(flag) + len(compdata)
2591 newcomp = len(flag) + len(compdata)
2673 rewriteindex(r, offs, (newcomp, len(data), nflags))
2592 rewriteindex(r, offs, (newcomp, len(data), nflags))
2674 datawrite.write(flag)
2593 datawrite.write(flag)
2675 datawrite.write(compdata)
2594 datawrite.write(compdata)
2676 dataread.seek(self.length(r), 1)
2595 dataread.seek(self.length(r), 1)
2677 return newcomp
2596 return newcomp
2678
2597
2679 # Rewrite censored entry with (padded) tombstone data.
2598 # Rewrite censored entry with (padded) tombstone data.
2680 pad = ' ' * (self.rawsize(rev) - len(tombstone))
2599 pad = ' ' * (self.rawsize(rev) - len(tombstone))
2681 offset += rewrite(rev, offset, tombstone + pad, REVIDX_ISCENSORED)
2600 offset += rewrite(rev, offset, tombstone + pad, REVIDX_ISCENSORED)
2682
2601
2683 # Rewrite all following filelog revisions fixing up offsets and deltas.
2602 # Rewrite all following filelog revisions fixing up offsets and deltas.
2684 for srev in pycompat.xrange(rev + 1, len(self)):
2603 for srev in pycompat.xrange(rev + 1, len(self)):
2685 if rev in self.parentrevs(srev):
2604 if rev in self.parentrevs(srev):
2686 # Immediate children of censored node must be re-added as
2605 # Immediate children of censored node must be re-added as
2687 # fulltext.
2606 # fulltext.
2688 try:
2607 try:
2689 revdata = self.revision(srev)
2608 revdata = self.revision(srev)
2690 except error.CensoredNodeError as e:
2609 except error.CensoredNodeError as e:
2691 revdata = e.tombstone
2610 revdata = e.tombstone
2692 dlen = rewrite(srev, offset, revdata)
2611 dlen = rewrite(srev, offset, revdata)
2693 else:
2612 else:
2694 # Copy any other revision data verbatim after fixing up the
2613 # Copy any other revision data verbatim after fixing up the
2695 # offset.
2614 # offset.
2696 rewriteindex(srev, offset)
2615 rewriteindex(srev, offset)
2697 dlen = self.length(srev)
2616 dlen = self.length(srev)
2698 for chunk in util.filechunkiter(dataread, limit=dlen):
2617 for chunk in util.filechunkiter(dataread, limit=dlen):
2699 datawrite.write(chunk)
2618 datawrite.write(chunk)
2700 offset += dlen
2619 offset += dlen
2701
2620
2702 idxread.close()
2621 idxread.close()
2703 idxwrite.close()
2622 idxwrite.close()
2704 if dataread is not idxread:
2623 if dataread is not idxread:
2705 dataread.close()
2624 dataread.close()
2706 datawrite.close()
2625 datawrite.close()
2707
2626
2708 def verifyintegrity(self, state):
2627 def verifyintegrity(self, state):
2709 """Verifies the integrity of the revlog.
2628 """Verifies the integrity of the revlog.
2710
2629
2711 Yields ``revlogproblem`` instances describing problems that are
2630 Yields ``revlogproblem`` instances describing problems that are
2712 found.
2631 found.
2713 """
2632 """
2714 dd, di = self.checksize()
2633 dd, di = self.checksize()
2715 if dd:
2634 if dd:
2716 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2635 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2717 if di:
2636 if di:
2718 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2637 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2719
2638
2720 version = self.version & 0xFFFF
2639 version = self.version & 0xFFFF
2721
2640
2722 # The verifier tells us what version revlog we should be.
2641 # The verifier tells us what version revlog we should be.
2723 if version != state['expectedversion']:
2642 if version != state['expectedversion']:
2724 yield revlogproblem(
2643 yield revlogproblem(
2725 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2644 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2726 (self.indexfile, version, state['expectedversion']))
2645 (self.indexfile, version, state['expectedversion']))
@@ -1,1229 +1,1105 b''
1 # storage.py - Testing of storage primitives.
1 # storage.py - Testing of storage primitives.
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.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 unittest
10 import unittest
11
11
12 from ..node import (
12 from ..node import (
13 hex,
13 hex,
14 nullid,
14 nullid,
15 nullrev,
15 nullrev,
16 )
16 )
17 from .. import (
17 from .. import (
18 error,
18 error,
19 mdiff,
19 mdiff,
20 revlog,
20 revlog,
21 )
21 )
22
22
23 class basetestcase(unittest.TestCase):
23 class basetestcase(unittest.TestCase):
24 if not getattr(unittest.TestCase, r'assertRaisesRegex', False):
24 if not getattr(unittest.TestCase, r'assertRaisesRegex', False):
25 assertRaisesRegex = (# camelcase-required
25 assertRaisesRegex = (# camelcase-required
26 unittest.TestCase.assertRaisesRegexp)
26 unittest.TestCase.assertRaisesRegexp)
27
27
28 class revisiondeltarequest(object):
29 def __init__(self, node, p1, p2, linknode, basenode, ellipsis):
30 self.node = node
31 self.p1node = p1
32 self.p2node = p2
33 self.linknode = linknode
34 self.basenode = basenode
35 self.ellipsis = ellipsis
36
37 class ifileindextests(basetestcase):
28 class ifileindextests(basetestcase):
38 """Generic tests for the ifileindex interface.
29 """Generic tests for the ifileindex interface.
39
30
40 All file storage backends for index data should conform to the tests in this
31 All file storage backends for index data should conform to the tests in this
41 class.
32 class.
42
33
43 Use ``makeifileindextests()`` to create an instance of this type.
34 Use ``makeifileindextests()`` to create an instance of this type.
44 """
35 """
45 def testempty(self):
36 def testempty(self):
46 f = self._makefilefn()
37 f = self._makefilefn()
47 self.assertEqual(len(f), 0, 'new file store has 0 length by default')
38 self.assertEqual(len(f), 0, 'new file store has 0 length by default')
48 self.assertEqual(list(f), [], 'iter yields nothing by default')
39 self.assertEqual(list(f), [], 'iter yields nothing by default')
49
40
50 gen = iter(f)
41 gen = iter(f)
51 with self.assertRaises(StopIteration):
42 with self.assertRaises(StopIteration):
52 next(gen)
43 next(gen)
53
44
54 # revs() should evaluate to an empty list.
45 # revs() should evaluate to an empty list.
55 self.assertEqual(list(f.revs()), [])
46 self.assertEqual(list(f.revs()), [])
56
47
57 revs = iter(f.revs())
48 revs = iter(f.revs())
58 with self.assertRaises(StopIteration):
49 with self.assertRaises(StopIteration):
59 next(revs)
50 next(revs)
60
51
61 self.assertEqual(list(f.revs(start=20)), [])
52 self.assertEqual(list(f.revs(start=20)), [])
62
53
63 # parents() and parentrevs() work with nullid/nullrev.
54 # parents() and parentrevs() work with nullid/nullrev.
64 self.assertEqual(f.parents(nullid), (nullid, nullid))
55 self.assertEqual(f.parents(nullid), (nullid, nullid))
65 self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
56 self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
66
57
67 with self.assertRaises(error.LookupError):
58 with self.assertRaises(error.LookupError):
68 f.parents(b'\x01' * 20)
59 f.parents(b'\x01' * 20)
69
60
70 for i in range(-5, 5):
61 for i in range(-5, 5):
71 if i == nullrev:
62 if i == nullrev:
72 continue
63 continue
73
64
74 with self.assertRaises(IndexError):
65 with self.assertRaises(IndexError):
75 f.parentrevs(i)
66 f.parentrevs(i)
76
67
77 # nullid/nullrev lookup always works.
68 # nullid/nullrev lookup always works.
78 self.assertEqual(f.rev(nullid), nullrev)
69 self.assertEqual(f.rev(nullid), nullrev)
79 self.assertEqual(f.node(nullrev), nullid)
70 self.assertEqual(f.node(nullrev), nullid)
80
71
81 with self.assertRaises(error.LookupError):
72 with self.assertRaises(error.LookupError):
82 f.rev(b'\x01' * 20)
73 f.rev(b'\x01' * 20)
83
74
84 for i in range(-5, 5):
75 for i in range(-5, 5):
85 if i == nullrev:
76 if i == nullrev:
86 continue
77 continue
87
78
88 with self.assertRaises(IndexError):
79 with self.assertRaises(IndexError):
89 f.node(i)
80 f.node(i)
90
81
91 self.assertEqual(f.lookup(nullid), nullid)
82 self.assertEqual(f.lookup(nullid), nullid)
92 self.assertEqual(f.lookup(nullrev), nullid)
83 self.assertEqual(f.lookup(nullrev), nullid)
93 self.assertEqual(f.lookup(hex(nullid)), nullid)
84 self.assertEqual(f.lookup(hex(nullid)), nullid)
94
85
95 # String converted to integer doesn't work for nullrev.
86 # String converted to integer doesn't work for nullrev.
96 with self.assertRaises(error.LookupError):
87 with self.assertRaises(error.LookupError):
97 f.lookup(b'%d' % nullrev)
88 f.lookup(b'%d' % nullrev)
98
89
99 self.assertEqual(f.linkrev(nullrev), nullrev)
90 self.assertEqual(f.linkrev(nullrev), nullrev)
100
91
101 for i in range(-5, 5):
92 for i in range(-5, 5):
102 if i == nullrev:
93 if i == nullrev:
103 continue
94 continue
104
95
105 with self.assertRaises(IndexError):
96 with self.assertRaises(IndexError):
106 f.linkrev(i)
97 f.linkrev(i)
107
98
108 self.assertEqual(f.flags(nullrev), 0)
99 self.assertEqual(f.flags(nullrev), 0)
109
100
110 for i in range(-5, 5):
101 for i in range(-5, 5):
111 if i == nullrev:
102 if i == nullrev:
112 continue
103 continue
113
104
114 with self.assertRaises(IndexError):
105 with self.assertRaises(IndexError):
115 f.flags(i)
106 f.flags(i)
116
107
117 self.assertFalse(f.iscensored(nullrev))
108 self.assertFalse(f.iscensored(nullrev))
118
109
119 for i in range(-5, 5):
110 for i in range(-5, 5):
120 if i == nullrev:
111 if i == nullrev:
121 continue
112 continue
122
113
123 with self.assertRaises(IndexError):
114 with self.assertRaises(IndexError):
124 f.iscensored(i)
115 f.iscensored(i)
125
116
126 self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
117 self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
127
118
128 with self.assertRaises(ValueError):
119 with self.assertRaises(ValueError):
129 self.assertEqual(list(f.descendants([])), [])
120 self.assertEqual(list(f.descendants([])), [])
130
121
131 self.assertEqual(list(f.descendants([nullrev])), [])
122 self.assertEqual(list(f.descendants([nullrev])), [])
132
123
133 self.assertEqual(f.heads(), [nullid])
124 self.assertEqual(f.heads(), [nullid])
134 self.assertEqual(f.heads(nullid), [nullid])
125 self.assertEqual(f.heads(nullid), [nullid])
135 self.assertEqual(f.heads(None, [nullid]), [nullid])
126 self.assertEqual(f.heads(None, [nullid]), [nullid])
136 self.assertEqual(f.heads(nullid, [nullid]), [nullid])
127 self.assertEqual(f.heads(nullid, [nullid]), [nullid])
137
128
138 self.assertEqual(f.children(nullid), [])
129 self.assertEqual(f.children(nullid), [])
139
130
140 with self.assertRaises(error.LookupError):
131 with self.assertRaises(error.LookupError):
141 f.children(b'\x01' * 20)
132 f.children(b'\x01' * 20)
142
133
143 self.assertEqual(f.deltaparent(nullrev), nullrev)
134 self.assertEqual(f.deltaparent(nullrev), nullrev)
144
135
145 for i in range(-5, 5):
136 for i in range(-5, 5):
146 if i == nullrev:
137 if i == nullrev:
147 continue
138 continue
148
139
149 with self.assertRaises(IndexError):
140 with self.assertRaises(IndexError):
150 f.deltaparent(i)
141 f.deltaparent(i)
151
142
152 def testsinglerevision(self):
143 def testsinglerevision(self):
153 f = self._makefilefn()
144 f = self._makefilefn()
154 with self._maketransactionfn() as tr:
145 with self._maketransactionfn() as tr:
155 node = f.add(b'initial', None, tr, 0, nullid, nullid)
146 node = f.add(b'initial', None, tr, 0, nullid, nullid)
156
147
157 self.assertEqual(len(f), 1)
148 self.assertEqual(len(f), 1)
158 self.assertEqual(list(f), [0])
149 self.assertEqual(list(f), [0])
159
150
160 gen = iter(f)
151 gen = iter(f)
161 self.assertEqual(next(gen), 0)
152 self.assertEqual(next(gen), 0)
162
153
163 with self.assertRaises(StopIteration):
154 with self.assertRaises(StopIteration):
164 next(gen)
155 next(gen)
165
156
166 self.assertEqual(list(f.revs()), [0])
157 self.assertEqual(list(f.revs()), [0])
167 self.assertEqual(list(f.revs(start=1)), [])
158 self.assertEqual(list(f.revs(start=1)), [])
168 self.assertEqual(list(f.revs(start=0)), [0])
159 self.assertEqual(list(f.revs(start=0)), [0])
169 self.assertEqual(list(f.revs(stop=0)), [0])
160 self.assertEqual(list(f.revs(stop=0)), [0])
170 self.assertEqual(list(f.revs(stop=1)), [0])
161 self.assertEqual(list(f.revs(stop=1)), [0])
171 self.assertEqual(list(f.revs(1, 1)), [])
162 self.assertEqual(list(f.revs(1, 1)), [])
172 # TODO buggy
163 # TODO buggy
173 self.assertEqual(list(f.revs(1, 0)), [1, 0])
164 self.assertEqual(list(f.revs(1, 0)), [1, 0])
174 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
165 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
175
166
176 self.assertEqual(f.parents(node), (nullid, nullid))
167 self.assertEqual(f.parents(node), (nullid, nullid))
177 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
168 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
178
169
179 with self.assertRaises(error.LookupError):
170 with self.assertRaises(error.LookupError):
180 f.parents(b'\x01' * 20)
171 f.parents(b'\x01' * 20)
181
172
182 with self.assertRaises(IndexError):
173 with self.assertRaises(IndexError):
183 f.parentrevs(1)
174 f.parentrevs(1)
184
175
185 self.assertEqual(f.rev(node), 0)
176 self.assertEqual(f.rev(node), 0)
186
177
187 with self.assertRaises(error.LookupError):
178 with self.assertRaises(error.LookupError):
188 f.rev(b'\x01' * 20)
179 f.rev(b'\x01' * 20)
189
180
190 self.assertEqual(f.node(0), node)
181 self.assertEqual(f.node(0), node)
191
182
192 with self.assertRaises(IndexError):
183 with self.assertRaises(IndexError):
193 f.node(1)
184 f.node(1)
194
185
195 self.assertEqual(f.lookup(node), node)
186 self.assertEqual(f.lookup(node), node)
196 self.assertEqual(f.lookup(0), node)
187 self.assertEqual(f.lookup(0), node)
197 self.assertEqual(f.lookup(b'0'), node)
188 self.assertEqual(f.lookup(b'0'), node)
198 self.assertEqual(f.lookup(hex(node)), node)
189 self.assertEqual(f.lookup(hex(node)), node)
199
190
200 self.assertEqual(f.linkrev(0), 0)
191 self.assertEqual(f.linkrev(0), 0)
201
192
202 with self.assertRaises(IndexError):
193 with self.assertRaises(IndexError):
203 f.linkrev(1)
194 f.linkrev(1)
204
195
205 self.assertEqual(f.flags(0), 0)
196 self.assertEqual(f.flags(0), 0)
206
197
207 with self.assertRaises(IndexError):
198 with self.assertRaises(IndexError):
208 f.flags(1)
199 f.flags(1)
209
200
210 self.assertFalse(f.iscensored(0))
201 self.assertFalse(f.iscensored(0))
211
202
212 with self.assertRaises(IndexError):
203 with self.assertRaises(IndexError):
213 f.iscensored(1)
204 f.iscensored(1)
214
205
215 self.assertEqual(list(f.descendants([0])), [])
206 self.assertEqual(list(f.descendants([0])), [])
216
207
217 self.assertEqual(f.heads(), [node])
208 self.assertEqual(f.heads(), [node])
218 self.assertEqual(f.heads(node), [node])
209 self.assertEqual(f.heads(node), [node])
219 self.assertEqual(f.heads(stop=[node]), [node])
210 self.assertEqual(f.heads(stop=[node]), [node])
220
211
221 with self.assertRaises(error.LookupError):
212 with self.assertRaises(error.LookupError):
222 f.heads(stop=[b'\x01' * 20])
213 f.heads(stop=[b'\x01' * 20])
223
214
224 self.assertEqual(f.children(node), [])
215 self.assertEqual(f.children(node), [])
225
216
226 self.assertEqual(f.deltaparent(0), nullrev)
217 self.assertEqual(f.deltaparent(0), nullrev)
227
218
228 def testmultiplerevisions(self):
219 def testmultiplerevisions(self):
229 fulltext0 = b'x' * 1024
220 fulltext0 = b'x' * 1024
230 fulltext1 = fulltext0 + b'y'
221 fulltext1 = fulltext0 + b'y'
231 fulltext2 = b'y' + fulltext0 + b'z'
222 fulltext2 = b'y' + fulltext0 + b'z'
232
223
233 f = self._makefilefn()
224 f = self._makefilefn()
234 with self._maketransactionfn() as tr:
225 with self._maketransactionfn() as tr:
235 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
226 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
236 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
227 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
237 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
228 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
238
229
239 self.assertEqual(len(f), 3)
230 self.assertEqual(len(f), 3)
240 self.assertEqual(list(f), [0, 1, 2])
231 self.assertEqual(list(f), [0, 1, 2])
241
232
242 gen = iter(f)
233 gen = iter(f)
243 self.assertEqual(next(gen), 0)
234 self.assertEqual(next(gen), 0)
244 self.assertEqual(next(gen), 1)
235 self.assertEqual(next(gen), 1)
245 self.assertEqual(next(gen), 2)
236 self.assertEqual(next(gen), 2)
246
237
247 with self.assertRaises(StopIteration):
238 with self.assertRaises(StopIteration):
248 next(gen)
239 next(gen)
249
240
250 self.assertEqual(list(f.revs()), [0, 1, 2])
241 self.assertEqual(list(f.revs()), [0, 1, 2])
251 self.assertEqual(list(f.revs(0)), [0, 1, 2])
242 self.assertEqual(list(f.revs(0)), [0, 1, 2])
252 self.assertEqual(list(f.revs(1)), [1, 2])
243 self.assertEqual(list(f.revs(1)), [1, 2])
253 self.assertEqual(list(f.revs(2)), [2])
244 self.assertEqual(list(f.revs(2)), [2])
254 self.assertEqual(list(f.revs(3)), [])
245 self.assertEqual(list(f.revs(3)), [])
255 self.assertEqual(list(f.revs(stop=1)), [0, 1])
246 self.assertEqual(list(f.revs(stop=1)), [0, 1])
256 self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
247 self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
257 self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
248 self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
258 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
249 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
259 self.assertEqual(list(f.revs(2, 1)), [2, 1])
250 self.assertEqual(list(f.revs(2, 1)), [2, 1])
260 # TODO this is wrong
251 # TODO this is wrong
261 self.assertEqual(list(f.revs(3, 2)), [3, 2])
252 self.assertEqual(list(f.revs(3, 2)), [3, 2])
262
253
263 self.assertEqual(f.parents(node0), (nullid, nullid))
254 self.assertEqual(f.parents(node0), (nullid, nullid))
264 self.assertEqual(f.parents(node1), (node0, nullid))
255 self.assertEqual(f.parents(node1), (node0, nullid))
265 self.assertEqual(f.parents(node2), (node1, nullid))
256 self.assertEqual(f.parents(node2), (node1, nullid))
266
257
267 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
258 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
268 self.assertEqual(f.parentrevs(1), (0, nullrev))
259 self.assertEqual(f.parentrevs(1), (0, nullrev))
269 self.assertEqual(f.parentrevs(2), (1, nullrev))
260 self.assertEqual(f.parentrevs(2), (1, nullrev))
270
261
271 self.assertEqual(f.rev(node0), 0)
262 self.assertEqual(f.rev(node0), 0)
272 self.assertEqual(f.rev(node1), 1)
263 self.assertEqual(f.rev(node1), 1)
273 self.assertEqual(f.rev(node2), 2)
264 self.assertEqual(f.rev(node2), 2)
274
265
275 with self.assertRaises(error.LookupError):
266 with self.assertRaises(error.LookupError):
276 f.rev(b'\x01' * 20)
267 f.rev(b'\x01' * 20)
277
268
278 self.assertEqual(f.node(0), node0)
269 self.assertEqual(f.node(0), node0)
279 self.assertEqual(f.node(1), node1)
270 self.assertEqual(f.node(1), node1)
280 self.assertEqual(f.node(2), node2)
271 self.assertEqual(f.node(2), node2)
281
272
282 with self.assertRaises(IndexError):
273 with self.assertRaises(IndexError):
283 f.node(3)
274 f.node(3)
284
275
285 self.assertEqual(f.lookup(node0), node0)
276 self.assertEqual(f.lookup(node0), node0)
286 self.assertEqual(f.lookup(0), node0)
277 self.assertEqual(f.lookup(0), node0)
287 self.assertEqual(f.lookup(b'0'), node0)
278 self.assertEqual(f.lookup(b'0'), node0)
288 self.assertEqual(f.lookup(hex(node0)), node0)
279 self.assertEqual(f.lookup(hex(node0)), node0)
289
280
290 self.assertEqual(f.lookup(node1), node1)
281 self.assertEqual(f.lookup(node1), node1)
291 self.assertEqual(f.lookup(1), node1)
282 self.assertEqual(f.lookup(1), node1)
292 self.assertEqual(f.lookup(b'1'), node1)
283 self.assertEqual(f.lookup(b'1'), node1)
293 self.assertEqual(f.lookup(hex(node1)), node1)
284 self.assertEqual(f.lookup(hex(node1)), node1)
294
285
295 self.assertEqual(f.linkrev(0), 0)
286 self.assertEqual(f.linkrev(0), 0)
296 self.assertEqual(f.linkrev(1), 1)
287 self.assertEqual(f.linkrev(1), 1)
297 self.assertEqual(f.linkrev(2), 3)
288 self.assertEqual(f.linkrev(2), 3)
298
289
299 with self.assertRaises(IndexError):
290 with self.assertRaises(IndexError):
300 f.linkrev(3)
291 f.linkrev(3)
301
292
302 self.assertEqual(f.flags(0), 0)
293 self.assertEqual(f.flags(0), 0)
303 self.assertEqual(f.flags(1), 0)
294 self.assertEqual(f.flags(1), 0)
304 self.assertEqual(f.flags(2), 0)
295 self.assertEqual(f.flags(2), 0)
305
296
306 with self.assertRaises(IndexError):
297 with self.assertRaises(IndexError):
307 f.flags(3)
298 f.flags(3)
308
299
309 self.assertFalse(f.iscensored(0))
300 self.assertFalse(f.iscensored(0))
310 self.assertFalse(f.iscensored(1))
301 self.assertFalse(f.iscensored(1))
311 self.assertFalse(f.iscensored(2))
302 self.assertFalse(f.iscensored(2))
312
303
313 with self.assertRaises(IndexError):
304 with self.assertRaises(IndexError):
314 f.iscensored(3)
305 f.iscensored(3)
315
306
316 self.assertEqual(f.commonancestorsheads(node1, nullid), [])
307 self.assertEqual(f.commonancestorsheads(node1, nullid), [])
317 self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
308 self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
318 self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
309 self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
319 self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
310 self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
320 self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
311 self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
321 self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
312 self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
322
313
323 self.assertEqual(list(f.descendants([0])), [1, 2])
314 self.assertEqual(list(f.descendants([0])), [1, 2])
324 self.assertEqual(list(f.descendants([1])), [2])
315 self.assertEqual(list(f.descendants([1])), [2])
325 self.assertEqual(list(f.descendants([0, 1])), [1, 2])
316 self.assertEqual(list(f.descendants([0, 1])), [1, 2])
326
317
327 self.assertEqual(f.heads(), [node2])
318 self.assertEqual(f.heads(), [node2])
328 self.assertEqual(f.heads(node0), [node2])
319 self.assertEqual(f.heads(node0), [node2])
329 self.assertEqual(f.heads(node1), [node2])
320 self.assertEqual(f.heads(node1), [node2])
330 self.assertEqual(f.heads(node2), [node2])
321 self.assertEqual(f.heads(node2), [node2])
331
322
332 # TODO this behavior seems wonky. Is it correct? If so, the
323 # TODO this behavior seems wonky. Is it correct? If so, the
333 # docstring for heads() should be updated to reflect desired
324 # docstring for heads() should be updated to reflect desired
334 # behavior.
325 # behavior.
335 self.assertEqual(f.heads(stop=[node1]), [node1, node2])
326 self.assertEqual(f.heads(stop=[node1]), [node1, node2])
336 self.assertEqual(f.heads(stop=[node0]), [node0, node2])
327 self.assertEqual(f.heads(stop=[node0]), [node0, node2])
337 self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
328 self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
338
329
339 with self.assertRaises(error.LookupError):
330 with self.assertRaises(error.LookupError):
340 f.heads(stop=[b'\x01' * 20])
331 f.heads(stop=[b'\x01' * 20])
341
332
342 self.assertEqual(f.children(node0), [node1])
333 self.assertEqual(f.children(node0), [node1])
343 self.assertEqual(f.children(node1), [node2])
334 self.assertEqual(f.children(node1), [node2])
344 self.assertEqual(f.children(node2), [])
335 self.assertEqual(f.children(node2), [])
345
336
346 self.assertEqual(f.deltaparent(0), nullrev)
337 self.assertEqual(f.deltaparent(0), nullrev)
347 self.assertEqual(f.deltaparent(1), 0)
338 self.assertEqual(f.deltaparent(1), 0)
348 self.assertEqual(f.deltaparent(2), 1)
339 self.assertEqual(f.deltaparent(2), 1)
349
340
350 def testmultipleheads(self):
341 def testmultipleheads(self):
351 f = self._makefilefn()
342 f = self._makefilefn()
352
343
353 with self._maketransactionfn() as tr:
344 with self._maketransactionfn() as tr:
354 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
345 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
355 node1 = f.add(b'1', None, tr, 1, node0, nullid)
346 node1 = f.add(b'1', None, tr, 1, node0, nullid)
356 node2 = f.add(b'2', None, tr, 2, node1, nullid)
347 node2 = f.add(b'2', None, tr, 2, node1, nullid)
357 node3 = f.add(b'3', None, tr, 3, node0, nullid)
348 node3 = f.add(b'3', None, tr, 3, node0, nullid)
358 node4 = f.add(b'4', None, tr, 4, node3, nullid)
349 node4 = f.add(b'4', None, tr, 4, node3, nullid)
359 node5 = f.add(b'5', None, tr, 5, node0, nullid)
350 node5 = f.add(b'5', None, tr, 5, node0, nullid)
360
351
361 self.assertEqual(len(f), 6)
352 self.assertEqual(len(f), 6)
362
353
363 self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
354 self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
364 self.assertEqual(list(f.descendants([1])), [2])
355 self.assertEqual(list(f.descendants([1])), [2])
365 self.assertEqual(list(f.descendants([2])), [])
356 self.assertEqual(list(f.descendants([2])), [])
366 self.assertEqual(list(f.descendants([3])), [4])
357 self.assertEqual(list(f.descendants([3])), [4])
367 self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
358 self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
368 self.assertEqual(list(f.descendants([1, 3])), [2, 4])
359 self.assertEqual(list(f.descendants([1, 3])), [2, 4])
369
360
370 self.assertEqual(f.heads(), [node2, node4, node5])
361 self.assertEqual(f.heads(), [node2, node4, node5])
371 self.assertEqual(f.heads(node0), [node2, node4, node5])
362 self.assertEqual(f.heads(node0), [node2, node4, node5])
372 self.assertEqual(f.heads(node1), [node2])
363 self.assertEqual(f.heads(node1), [node2])
373 self.assertEqual(f.heads(node2), [node2])
364 self.assertEqual(f.heads(node2), [node2])
374 self.assertEqual(f.heads(node3), [node4])
365 self.assertEqual(f.heads(node3), [node4])
375 self.assertEqual(f.heads(node4), [node4])
366 self.assertEqual(f.heads(node4), [node4])
376 self.assertEqual(f.heads(node5), [node5])
367 self.assertEqual(f.heads(node5), [node5])
377
368
378 # TODO this seems wrong.
369 # TODO this seems wrong.
379 self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
370 self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
380 self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
371 self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
381
372
382 self.assertEqual(f.children(node0), [node1, node3, node5])
373 self.assertEqual(f.children(node0), [node1, node3, node5])
383 self.assertEqual(f.children(node1), [node2])
374 self.assertEqual(f.children(node1), [node2])
384 self.assertEqual(f.children(node2), [])
375 self.assertEqual(f.children(node2), [])
385 self.assertEqual(f.children(node3), [node4])
376 self.assertEqual(f.children(node3), [node4])
386 self.assertEqual(f.children(node4), [])
377 self.assertEqual(f.children(node4), [])
387 self.assertEqual(f.children(node5), [])
378 self.assertEqual(f.children(node5), [])
388
379
389 class ifiledatatests(basetestcase):
380 class ifiledatatests(basetestcase):
390 """Generic tests for the ifiledata interface.
381 """Generic tests for the ifiledata interface.
391
382
392 All file storage backends for data should conform to the tests in this
383 All file storage backends for data should conform to the tests in this
393 class.
384 class.
394
385
395 Use ``makeifiledatatests()`` to create an instance of this type.
386 Use ``makeifiledatatests()`` to create an instance of this type.
396 """
387 """
397 def testempty(self):
388 def testempty(self):
398 f = self._makefilefn()
389 f = self._makefilefn()
399
390
400 self.assertEqual(f.rawsize(nullrev), 0)
391 self.assertEqual(f.rawsize(nullrev), 0)
401
392
402 for i in range(-5, 5):
393 for i in range(-5, 5):
403 if i == nullrev:
394 if i == nullrev:
404 continue
395 continue
405
396
406 with self.assertRaises(IndexError):
397 with self.assertRaises(IndexError):
407 f.rawsize(i)
398 f.rawsize(i)
408
399
409 self.assertEqual(f.size(nullrev), 0)
400 self.assertEqual(f.size(nullrev), 0)
410
401
411 for i in range(-5, 5):
402 for i in range(-5, 5):
412 if i == nullrev:
403 if i == nullrev:
413 continue
404 continue
414
405
415 with self.assertRaises(IndexError):
406 with self.assertRaises(IndexError):
416 f.size(i)
407 f.size(i)
417
408
418 with self.assertRaises(error.StorageError):
409 with self.assertRaises(error.StorageError):
419 f.checkhash(b'', nullid)
410 f.checkhash(b'', nullid)
420
411
421 with self.assertRaises(error.LookupError):
412 with self.assertRaises(error.LookupError):
422 f.checkhash(b'', b'\x01' * 20)
413 f.checkhash(b'', b'\x01' * 20)
423
414
424 self.assertEqual(f.revision(nullid), b'')
415 self.assertEqual(f.revision(nullid), b'')
425 self.assertEqual(f.revision(nullid, raw=True), b'')
416 self.assertEqual(f.revision(nullid, raw=True), b'')
426
417
427 with self.assertRaises(error.LookupError):
418 with self.assertRaises(error.LookupError):
428 f.revision(b'\x01' * 20)
419 f.revision(b'\x01' * 20)
429
420
430 self.assertEqual(f.read(nullid), b'')
421 self.assertEqual(f.read(nullid), b'')
431
422
432 with self.assertRaises(error.LookupError):
423 with self.assertRaises(error.LookupError):
433 f.read(b'\x01' * 20)
424 f.read(b'\x01' * 20)
434
425
435 self.assertFalse(f.renamed(nullid))
426 self.assertFalse(f.renamed(nullid))
436
427
437 with self.assertRaises(error.LookupError):
428 with self.assertRaises(error.LookupError):
438 f.read(b'\x01' * 20)
429 f.read(b'\x01' * 20)
439
430
440 self.assertTrue(f.cmp(nullid, b''))
431 self.assertTrue(f.cmp(nullid, b''))
441 self.assertTrue(f.cmp(nullid, b'foo'))
432 self.assertTrue(f.cmp(nullid, b'foo'))
442
433
443 with self.assertRaises(error.LookupError):
434 with self.assertRaises(error.LookupError):
444 f.cmp(b'\x01' * 20, b'irrelevant')
435 f.cmp(b'\x01' * 20, b'irrelevant')
445
436
446 self.assertEqual(f.revdiff(nullrev, nullrev), b'')
437 self.assertEqual(f.revdiff(nullrev, nullrev), b'')
447
438
448 with self.assertRaises(IndexError):
439 with self.assertRaises(IndexError):
449 f.revdiff(0, nullrev)
440 f.revdiff(0, nullrev)
450
441
451 with self.assertRaises(IndexError):
442 with self.assertRaises(IndexError):
452 f.revdiff(nullrev, 0)
443 f.revdiff(nullrev, 0)
453
444
454 with self.assertRaises(IndexError):
445 with self.assertRaises(IndexError):
455 f.revdiff(0, 0)
446 f.revdiff(0, 0)
456
447
457 gen = f.emitrevisiondeltas([])
458 with self.assertRaises(StopIteration):
459 next(gen)
460
461 requests = [
462 revisiondeltarequest(nullid, nullid, nullid, nullid, nullid, False),
463 ]
464 gen = f.emitrevisiondeltas(requests)
465
466 delta = next(gen)
467
468 self.assertEqual(delta.node, nullid)
469 self.assertEqual(delta.p1node, nullid)
470 self.assertEqual(delta.p2node, nullid)
471 self.assertEqual(delta.linknode, nullid)
472 self.assertEqual(delta.basenode, nullid)
473 self.assertIsNone(delta.baserevisionsize)
474 self.assertEqual(delta.revision, b'')
475 self.assertIsNone(delta.delta)
476
477 with self.assertRaises(StopIteration):
478 next(gen)
479
480 requests = [
481 revisiondeltarequest(nullid, nullid, nullid, nullid, nullid, False),
482 revisiondeltarequest(nullid, b'\x01' * 20, b'\x02' * 20,
483 b'\x03' * 20, nullid, False)
484 ]
485
486 gen = f.emitrevisiondeltas(requests)
487
488 next(gen)
489 delta = next(gen)
490
491 self.assertEqual(delta.node, nullid)
492 self.assertEqual(delta.p1node, b'\x01' * 20)
493 self.assertEqual(delta.p2node, b'\x02' * 20)
494 self.assertEqual(delta.linknode, b'\x03' * 20)
495 self.assertEqual(delta.basenode, nullid)
496 self.assertIsNone(delta.baserevisionsize)
497 self.assertEqual(delta.revision, b'')
498 self.assertIsNone(delta.delta)
499
500 with self.assertRaises(StopIteration):
501 next(gen)
502
503 # Emitting empty list is an empty generator.
448 # Emitting empty list is an empty generator.
504 gen = f.emitrevisions([])
449 gen = f.emitrevisions([])
505 with self.assertRaises(StopIteration):
450 with self.assertRaises(StopIteration):
506 next(gen)
451 next(gen)
507
452
508 # Emitting null node yields nothing.
453 # Emitting null node yields nothing.
509 gen = f.emitrevisions([nullid])
454 gen = f.emitrevisions([nullid])
510 with self.assertRaises(StopIteration):
455 with self.assertRaises(StopIteration):
511 next(gen)
456 next(gen)
512
457
513 # Requesting unknown node fails.
458 # Requesting unknown node fails.
514 with self.assertRaises(error.LookupError):
459 with self.assertRaises(error.LookupError):
515 list(f.emitrevisions([b'\x01' * 20]))
460 list(f.emitrevisions([b'\x01' * 20]))
516
461
517 def testsinglerevision(self):
462 def testsinglerevision(self):
518 fulltext = b'initial'
463 fulltext = b'initial'
519
464
520 f = self._makefilefn()
465 f = self._makefilefn()
521 with self._maketransactionfn() as tr:
466 with self._maketransactionfn() as tr:
522 node = f.add(fulltext, None, tr, 0, nullid, nullid)
467 node = f.add(fulltext, None, tr, 0, nullid, nullid)
523
468
524 self.assertEqual(f.rawsize(0), len(fulltext))
469 self.assertEqual(f.rawsize(0), len(fulltext))
525
470
526 with self.assertRaises(IndexError):
471 with self.assertRaises(IndexError):
527 f.rawsize(1)
472 f.rawsize(1)
528
473
529 self.assertEqual(f.size(0), len(fulltext))
474 self.assertEqual(f.size(0), len(fulltext))
530
475
531 with self.assertRaises(IndexError):
476 with self.assertRaises(IndexError):
532 f.size(1)
477 f.size(1)
533
478
534 f.checkhash(fulltext, node)
479 f.checkhash(fulltext, node)
535 f.checkhash(fulltext, node, nullid, nullid)
480 f.checkhash(fulltext, node, nullid, nullid)
536
481
537 with self.assertRaises(error.StorageError):
482 with self.assertRaises(error.StorageError):
538 f.checkhash(fulltext + b'extra', node)
483 f.checkhash(fulltext + b'extra', node)
539
484
540 with self.assertRaises(error.StorageError):
485 with self.assertRaises(error.StorageError):
541 f.checkhash(fulltext, node, b'\x01' * 20, nullid)
486 f.checkhash(fulltext, node, b'\x01' * 20, nullid)
542
487
543 with self.assertRaises(error.StorageError):
488 with self.assertRaises(error.StorageError):
544 f.checkhash(fulltext, node, nullid, b'\x01' * 20)
489 f.checkhash(fulltext, node, nullid, b'\x01' * 20)
545
490
546 self.assertEqual(f.revision(node), fulltext)
491 self.assertEqual(f.revision(node), fulltext)
547 self.assertEqual(f.revision(node, raw=True), fulltext)
492 self.assertEqual(f.revision(node, raw=True), fulltext)
548
493
549 self.assertEqual(f.read(node), fulltext)
494 self.assertEqual(f.read(node), fulltext)
550
495
551 self.assertFalse(f.renamed(node))
496 self.assertFalse(f.renamed(node))
552
497
553 self.assertFalse(f.cmp(node, fulltext))
498 self.assertFalse(f.cmp(node, fulltext))
554 self.assertTrue(f.cmp(node, fulltext + b'extra'))
499 self.assertTrue(f.cmp(node, fulltext + b'extra'))
555
500
556 self.assertEqual(f.revdiff(0, 0), b'')
501 self.assertEqual(f.revdiff(0, 0), b'')
557 self.assertEqual(f.revdiff(nullrev, 0),
502 self.assertEqual(f.revdiff(nullrev, 0),
558 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07%s' %
503 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07%s' %
559 fulltext)
504 fulltext)
560
505
561 self.assertEqual(f.revdiff(0, nullrev),
506 self.assertEqual(f.revdiff(0, nullrev),
562 b'\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00')
507 b'\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00')
563
508
564 requests = [
565 revisiondeltarequest(node, nullid, nullid, nullid, nullid, False),
566 ]
567 gen = f.emitrevisiondeltas(requests)
568
569 delta = next(gen)
570
571 self.assertEqual(delta.node, node)
572 self.assertEqual(delta.p1node, nullid)
573 self.assertEqual(delta.p2node, nullid)
574 self.assertEqual(delta.linknode, nullid)
575 self.assertEqual(delta.basenode, nullid)
576 self.assertIsNone(delta.baserevisionsize)
577 self.assertEqual(delta.revision, fulltext)
578 self.assertIsNone(delta.delta)
579
580 with self.assertRaises(StopIteration):
581 next(gen)
582
583 # Emitting a single revision works.
509 # Emitting a single revision works.
584 gen = f.emitrevisions([node])
510 gen = f.emitrevisions([node])
585 rev = next(gen)
511 rev = next(gen)
586
512
587 self.assertEqual(rev.node, node)
513 self.assertEqual(rev.node, node)
588 self.assertEqual(rev.p1node, nullid)
514 self.assertEqual(rev.p1node, nullid)
589 self.assertEqual(rev.p2node, nullid)
515 self.assertEqual(rev.p2node, nullid)
590 self.assertIsNone(rev.linknode)
516 self.assertIsNone(rev.linknode)
591 self.assertEqual(rev.basenode, nullid)
517 self.assertEqual(rev.basenode, nullid)
592 self.assertIsNone(rev.baserevisionsize)
518 self.assertIsNone(rev.baserevisionsize)
593 self.assertIsNone(rev.revision)
519 self.assertIsNone(rev.revision)
594 self.assertIsNone(rev.delta)
520 self.assertIsNone(rev.delta)
595
521
596 with self.assertRaises(StopIteration):
522 with self.assertRaises(StopIteration):
597 next(gen)
523 next(gen)
598
524
599 # Requesting revision data works.
525 # Requesting revision data works.
600 gen = f.emitrevisions([node], revisiondata=True)
526 gen = f.emitrevisions([node], revisiondata=True)
601 rev = next(gen)
527 rev = next(gen)
602
528
603 self.assertEqual(rev.node, node)
529 self.assertEqual(rev.node, node)
604 self.assertEqual(rev.p1node, nullid)
530 self.assertEqual(rev.p1node, nullid)
605 self.assertEqual(rev.p2node, nullid)
531 self.assertEqual(rev.p2node, nullid)
606 self.assertIsNone(rev.linknode)
532 self.assertIsNone(rev.linknode)
607 self.assertEqual(rev.basenode, nullid)
533 self.assertEqual(rev.basenode, nullid)
608 self.assertIsNone(rev.baserevisionsize)
534 self.assertIsNone(rev.baserevisionsize)
609 self.assertEqual(rev.revision, fulltext)
535 self.assertEqual(rev.revision, fulltext)
610 self.assertIsNone(rev.delta)
536 self.assertIsNone(rev.delta)
611
537
612 with self.assertRaises(StopIteration):
538 with self.assertRaises(StopIteration):
613 next(gen)
539 next(gen)
614
540
615 # Emitting an unknown node after a known revision results in error.
541 # Emitting an unknown node after a known revision results in error.
616 with self.assertRaises(error.LookupError):
542 with self.assertRaises(error.LookupError):
617 list(f.emitrevisions([node, b'\x01' * 20]))
543 list(f.emitrevisions([node, b'\x01' * 20]))
618
544
619 def testmultiplerevisions(self):
545 def testmultiplerevisions(self):
620 fulltext0 = b'x' * 1024
546 fulltext0 = b'x' * 1024
621 fulltext1 = fulltext0 + b'y'
547 fulltext1 = fulltext0 + b'y'
622 fulltext2 = b'y' + fulltext0 + b'z'
548 fulltext2 = b'y' + fulltext0 + b'z'
623
549
624 f = self._makefilefn()
550 f = self._makefilefn()
625 with self._maketransactionfn() as tr:
551 with self._maketransactionfn() as tr:
626 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
552 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
627 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
553 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
628 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
554 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
629
555
630 self.assertEqual(f.rawsize(0), len(fulltext0))
556 self.assertEqual(f.rawsize(0), len(fulltext0))
631 self.assertEqual(f.rawsize(1), len(fulltext1))
557 self.assertEqual(f.rawsize(1), len(fulltext1))
632 self.assertEqual(f.rawsize(2), len(fulltext2))
558 self.assertEqual(f.rawsize(2), len(fulltext2))
633
559
634 with self.assertRaises(IndexError):
560 with self.assertRaises(IndexError):
635 f.rawsize(3)
561 f.rawsize(3)
636
562
637 self.assertEqual(f.size(0), len(fulltext0))
563 self.assertEqual(f.size(0), len(fulltext0))
638 self.assertEqual(f.size(1), len(fulltext1))
564 self.assertEqual(f.size(1), len(fulltext1))
639 self.assertEqual(f.size(2), len(fulltext2))
565 self.assertEqual(f.size(2), len(fulltext2))
640
566
641 with self.assertRaises(IndexError):
567 with self.assertRaises(IndexError):
642 f.size(3)
568 f.size(3)
643
569
644 f.checkhash(fulltext0, node0)
570 f.checkhash(fulltext0, node0)
645 f.checkhash(fulltext1, node1)
571 f.checkhash(fulltext1, node1)
646 f.checkhash(fulltext1, node1, node0, nullid)
572 f.checkhash(fulltext1, node1, node0, nullid)
647 f.checkhash(fulltext2, node2, node1, nullid)
573 f.checkhash(fulltext2, node2, node1, nullid)
648
574
649 with self.assertRaises(error.StorageError):
575 with self.assertRaises(error.StorageError):
650 f.checkhash(fulltext1, b'\x01' * 20)
576 f.checkhash(fulltext1, b'\x01' * 20)
651
577
652 with self.assertRaises(error.StorageError):
578 with self.assertRaises(error.StorageError):
653 f.checkhash(fulltext1 + b'extra', node1, node0, nullid)
579 f.checkhash(fulltext1 + b'extra', node1, node0, nullid)
654
580
655 with self.assertRaises(error.StorageError):
581 with self.assertRaises(error.StorageError):
656 f.checkhash(fulltext1, node1, node0, node0)
582 f.checkhash(fulltext1, node1, node0, node0)
657
583
658 self.assertEqual(f.revision(node0), fulltext0)
584 self.assertEqual(f.revision(node0), fulltext0)
659 self.assertEqual(f.revision(node0, raw=True), fulltext0)
585 self.assertEqual(f.revision(node0, raw=True), fulltext0)
660 self.assertEqual(f.revision(node1), fulltext1)
586 self.assertEqual(f.revision(node1), fulltext1)
661 self.assertEqual(f.revision(node1, raw=True), fulltext1)
587 self.assertEqual(f.revision(node1, raw=True), fulltext1)
662 self.assertEqual(f.revision(node2), fulltext2)
588 self.assertEqual(f.revision(node2), fulltext2)
663 self.assertEqual(f.revision(node2, raw=True), fulltext2)
589 self.assertEqual(f.revision(node2, raw=True), fulltext2)
664
590
665 with self.assertRaises(error.LookupError):
591 with self.assertRaises(error.LookupError):
666 f.revision(b'\x01' * 20)
592 f.revision(b'\x01' * 20)
667
593
668 self.assertEqual(f.read(node0), fulltext0)
594 self.assertEqual(f.read(node0), fulltext0)
669 self.assertEqual(f.read(node1), fulltext1)
595 self.assertEqual(f.read(node1), fulltext1)
670 self.assertEqual(f.read(node2), fulltext2)
596 self.assertEqual(f.read(node2), fulltext2)
671
597
672 with self.assertRaises(error.LookupError):
598 with self.assertRaises(error.LookupError):
673 f.read(b'\x01' * 20)
599 f.read(b'\x01' * 20)
674
600
675 self.assertFalse(f.renamed(node0))
601 self.assertFalse(f.renamed(node0))
676 self.assertFalse(f.renamed(node1))
602 self.assertFalse(f.renamed(node1))
677 self.assertFalse(f.renamed(node2))
603 self.assertFalse(f.renamed(node2))
678
604
679 with self.assertRaises(error.LookupError):
605 with self.assertRaises(error.LookupError):
680 f.renamed(b'\x01' * 20)
606 f.renamed(b'\x01' * 20)
681
607
682 self.assertFalse(f.cmp(node0, fulltext0))
608 self.assertFalse(f.cmp(node0, fulltext0))
683 self.assertFalse(f.cmp(node1, fulltext1))
609 self.assertFalse(f.cmp(node1, fulltext1))
684 self.assertFalse(f.cmp(node2, fulltext2))
610 self.assertFalse(f.cmp(node2, fulltext2))
685
611
686 self.assertTrue(f.cmp(node1, fulltext0))
612 self.assertTrue(f.cmp(node1, fulltext0))
687 self.assertTrue(f.cmp(node2, fulltext1))
613 self.assertTrue(f.cmp(node2, fulltext1))
688
614
689 with self.assertRaises(error.LookupError):
615 with self.assertRaises(error.LookupError):
690 f.cmp(b'\x01' * 20, b'irrelevant')
616 f.cmp(b'\x01' * 20, b'irrelevant')
691
617
692 self.assertEqual(f.revdiff(0, 1),
618 self.assertEqual(f.revdiff(0, 1),
693 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
619 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
694 fulltext1)
620 fulltext1)
695
621
696 self.assertEqual(f.revdiff(0, 2),
622 self.assertEqual(f.revdiff(0, 2),
697 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x02' +
623 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x02' +
698 fulltext2)
624 fulltext2)
699
625
700 requests = [
701 revisiondeltarequest(node0, nullid, nullid, b'\x01' * 20, nullid,
702 False),
703 revisiondeltarequest(node1, node0, nullid, b'\x02' * 20, node0,
704 False),
705 revisiondeltarequest(node2, node1, nullid, b'\x03' * 20, node1,
706 False),
707 ]
708 gen = f.emitrevisiondeltas(requests)
709
710 delta = next(gen)
711
712 self.assertEqual(delta.node, node0)
713 self.assertEqual(delta.p1node, nullid)
714 self.assertEqual(delta.p2node, nullid)
715 self.assertEqual(delta.linknode, b'\x01' * 20)
716 self.assertEqual(delta.basenode, nullid)
717 self.assertIsNone(delta.baserevisionsize)
718 self.assertEqual(delta.revision, fulltext0)
719 self.assertIsNone(delta.delta)
720
721 delta = next(gen)
722
723 self.assertEqual(delta.node, node1)
724 self.assertEqual(delta.p1node, node0)
725 self.assertEqual(delta.p2node, nullid)
726 self.assertEqual(delta.linknode, b'\x02' * 20)
727 self.assertEqual(delta.basenode, node0)
728 self.assertIsNone(delta.baserevisionsize)
729 self.assertIsNone(delta.revision)
730 self.assertEqual(delta.delta,
731 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
732 fulltext1)
733
734 delta = next(gen)
735
736 self.assertEqual(delta.node, node2)
737 self.assertEqual(delta.p1node, node1)
738 self.assertEqual(delta.p2node, nullid)
739 self.assertEqual(delta.linknode, b'\x03' * 20)
740 self.assertEqual(delta.basenode, node1)
741 self.assertIsNone(delta.baserevisionsize)
742 self.assertIsNone(delta.revision)
743 self.assertEqual(delta.delta,
744 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
745 fulltext2)
746
747 with self.assertRaises(StopIteration):
748 next(gen)
749
750 # Nodes should be emitted in order.
626 # Nodes should be emitted in order.
751 gen = f.emitrevisions([node0, node1, node2], revisiondata=True)
627 gen = f.emitrevisions([node0, node1, node2], revisiondata=True)
752
628
753 rev = next(gen)
629 rev = next(gen)
754
630
755 self.assertEqual(rev.node, node0)
631 self.assertEqual(rev.node, node0)
756 self.assertEqual(rev.p1node, nullid)
632 self.assertEqual(rev.p1node, nullid)
757 self.assertEqual(rev.p2node, nullid)
633 self.assertEqual(rev.p2node, nullid)
758 self.assertIsNone(rev.linknode)
634 self.assertIsNone(rev.linknode)
759 self.assertEqual(rev.basenode, nullid)
635 self.assertEqual(rev.basenode, nullid)
760 self.assertIsNone(rev.baserevisionsize)
636 self.assertIsNone(rev.baserevisionsize)
761 self.assertEqual(rev.revision, fulltext0)
637 self.assertEqual(rev.revision, fulltext0)
762 self.assertIsNone(rev.delta)
638 self.assertIsNone(rev.delta)
763
639
764 rev = next(gen)
640 rev = next(gen)
765
641
766 self.assertEqual(rev.node, node1)
642 self.assertEqual(rev.node, node1)
767 self.assertEqual(rev.p1node, node0)
643 self.assertEqual(rev.p1node, node0)
768 self.assertEqual(rev.p2node, nullid)
644 self.assertEqual(rev.p2node, nullid)
769 self.assertIsNone(rev.linknode)
645 self.assertIsNone(rev.linknode)
770 self.assertEqual(rev.basenode, node0)
646 self.assertEqual(rev.basenode, node0)
771 self.assertIsNone(rev.baserevisionsize)
647 self.assertIsNone(rev.baserevisionsize)
772 self.assertIsNone(rev.revision)
648 self.assertIsNone(rev.revision)
773 self.assertEqual(rev.delta,
649 self.assertEqual(rev.delta,
774 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
650 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
775 fulltext1)
651 fulltext1)
776
652
777 rev = next(gen)
653 rev = next(gen)
778
654
779 self.assertEqual(rev.node, node2)
655 self.assertEqual(rev.node, node2)
780 self.assertEqual(rev.p1node, node1)
656 self.assertEqual(rev.p1node, node1)
781 self.assertEqual(rev.p2node, nullid)
657 self.assertEqual(rev.p2node, nullid)
782 self.assertIsNone(rev.linknode)
658 self.assertIsNone(rev.linknode)
783 self.assertEqual(rev.basenode, node1)
659 self.assertEqual(rev.basenode, node1)
784 self.assertIsNone(rev.baserevisionsize)
660 self.assertIsNone(rev.baserevisionsize)
785 self.assertIsNone(rev.revision)
661 self.assertIsNone(rev.revision)
786 self.assertEqual(rev.delta,
662 self.assertEqual(rev.delta,
787 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
663 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
788 fulltext2)
664 fulltext2)
789
665
790 with self.assertRaises(StopIteration):
666 with self.assertRaises(StopIteration):
791 next(gen)
667 next(gen)
792
668
793 # Request not in DAG order is reordered to be in DAG order.
669 # Request not in DAG order is reordered to be in DAG order.
794 gen = f.emitrevisions([node2, node1, node0], revisiondata=True)
670 gen = f.emitrevisions([node2, node1, node0], revisiondata=True)
795
671
796 rev = next(gen)
672 rev = next(gen)
797
673
798 self.assertEqual(rev.node, node0)
674 self.assertEqual(rev.node, node0)
799 self.assertEqual(rev.p1node, nullid)
675 self.assertEqual(rev.p1node, nullid)
800 self.assertEqual(rev.p2node, nullid)
676 self.assertEqual(rev.p2node, nullid)
801 self.assertIsNone(rev.linknode)
677 self.assertIsNone(rev.linknode)
802 self.assertEqual(rev.basenode, nullid)
678 self.assertEqual(rev.basenode, nullid)
803 self.assertIsNone(rev.baserevisionsize)
679 self.assertIsNone(rev.baserevisionsize)
804 self.assertEqual(rev.revision, fulltext0)
680 self.assertEqual(rev.revision, fulltext0)
805 self.assertIsNone(rev.delta)
681 self.assertIsNone(rev.delta)
806
682
807 rev = next(gen)
683 rev = next(gen)
808
684
809 self.assertEqual(rev.node, node1)
685 self.assertEqual(rev.node, node1)
810 self.assertEqual(rev.p1node, node0)
686 self.assertEqual(rev.p1node, node0)
811 self.assertEqual(rev.p2node, nullid)
687 self.assertEqual(rev.p2node, nullid)
812 self.assertIsNone(rev.linknode)
688 self.assertIsNone(rev.linknode)
813 self.assertEqual(rev.basenode, node0)
689 self.assertEqual(rev.basenode, node0)
814 self.assertIsNone(rev.baserevisionsize)
690 self.assertIsNone(rev.baserevisionsize)
815 self.assertIsNone(rev.revision)
691 self.assertIsNone(rev.revision)
816 self.assertEqual(rev.delta,
692 self.assertEqual(rev.delta,
817 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
693 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
818 fulltext1)
694 fulltext1)
819
695
820 rev = next(gen)
696 rev = next(gen)
821
697
822 self.assertEqual(rev.node, node2)
698 self.assertEqual(rev.node, node2)
823 self.assertEqual(rev.p1node, node1)
699 self.assertEqual(rev.p1node, node1)
824 self.assertEqual(rev.p2node, nullid)
700 self.assertEqual(rev.p2node, nullid)
825 self.assertIsNone(rev.linknode)
701 self.assertIsNone(rev.linknode)
826 self.assertEqual(rev.basenode, node1)
702 self.assertEqual(rev.basenode, node1)
827 self.assertIsNone(rev.baserevisionsize)
703 self.assertIsNone(rev.baserevisionsize)
828 self.assertIsNone(rev.revision)
704 self.assertIsNone(rev.revision)
829 self.assertEqual(rev.delta,
705 self.assertEqual(rev.delta,
830 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
706 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
831 fulltext2)
707 fulltext2)
832
708
833 with self.assertRaises(StopIteration):
709 with self.assertRaises(StopIteration):
834 next(gen)
710 next(gen)
835
711
836 # Unrecognized nodesorder value raises ProgrammingError.
712 # Unrecognized nodesorder value raises ProgrammingError.
837 with self.assertRaises(error.ProgrammingError):
713 with self.assertRaises(error.ProgrammingError):
838 list(f.emitrevisions([], nodesorder='bad'))
714 list(f.emitrevisions([], nodesorder='bad'))
839
715
840 # nodesorder=storage is recognized. But we can't test it thoroughly
716 # nodesorder=storage is recognized. But we can't test it thoroughly
841 # because behavior is storage-dependent.
717 # because behavior is storage-dependent.
842 res = list(f.emitrevisions([node2, node1, node0],
718 res = list(f.emitrevisions([node2, node1, node0],
843 nodesorder='storage'))
719 nodesorder='storage'))
844 self.assertEqual(len(res), 3)
720 self.assertEqual(len(res), 3)
845 self.assertEqual({o.node for o in res}, {node0, node1, node2})
721 self.assertEqual({o.node for o in res}, {node0, node1, node2})
846
722
847 # nodesorder=nodes forces the order.
723 # nodesorder=nodes forces the order.
848 gen = f.emitrevisions([node2, node0], nodesorder='nodes',
724 gen = f.emitrevisions([node2, node0], nodesorder='nodes',
849 revisiondata=True)
725 revisiondata=True)
850
726
851 rev = next(gen)
727 rev = next(gen)
852 self.assertEqual(rev.node, node2)
728 self.assertEqual(rev.node, node2)
853 self.assertEqual(rev.p1node, node1)
729 self.assertEqual(rev.p1node, node1)
854 self.assertEqual(rev.p2node, nullid)
730 self.assertEqual(rev.p2node, nullid)
855 self.assertEqual(rev.basenode, nullid)
731 self.assertEqual(rev.basenode, nullid)
856 self.assertIsNone(rev.baserevisionsize)
732 self.assertIsNone(rev.baserevisionsize)
857 self.assertEqual(rev.revision, fulltext2)
733 self.assertEqual(rev.revision, fulltext2)
858 self.assertIsNone(rev.delta)
734 self.assertIsNone(rev.delta)
859
735
860 rev = next(gen)
736 rev = next(gen)
861 self.assertEqual(rev.node, node0)
737 self.assertEqual(rev.node, node0)
862 self.assertEqual(rev.p1node, nullid)
738 self.assertEqual(rev.p1node, nullid)
863 self.assertEqual(rev.p2node, nullid)
739 self.assertEqual(rev.p2node, nullid)
864 # Delta behavior is storage dependent, so we can't easily test it.
740 # Delta behavior is storage dependent, so we can't easily test it.
865
741
866 with self.assertRaises(StopIteration):
742 with self.assertRaises(StopIteration):
867 next(gen)
743 next(gen)
868
744
869 # assumehaveparentrevisions=False (the default) won't send a delta for
745 # assumehaveparentrevisions=False (the default) won't send a delta for
870 # the first revision.
746 # the first revision.
871 gen = f.emitrevisions({node2, node1}, revisiondata=True)
747 gen = f.emitrevisions({node2, node1}, revisiondata=True)
872
748
873 rev = next(gen)
749 rev = next(gen)
874 self.assertEqual(rev.node, node1)
750 self.assertEqual(rev.node, node1)
875 self.assertEqual(rev.p1node, node0)
751 self.assertEqual(rev.p1node, node0)
876 self.assertEqual(rev.p2node, nullid)
752 self.assertEqual(rev.p2node, nullid)
877 self.assertEqual(rev.basenode, nullid)
753 self.assertEqual(rev.basenode, nullid)
878 self.assertIsNone(rev.baserevisionsize)
754 self.assertIsNone(rev.baserevisionsize)
879 self.assertEqual(rev.revision, fulltext1)
755 self.assertEqual(rev.revision, fulltext1)
880 self.assertIsNone(rev.delta)
756 self.assertIsNone(rev.delta)
881
757
882 rev = next(gen)
758 rev = next(gen)
883 self.assertEqual(rev.node, node2)
759 self.assertEqual(rev.node, node2)
884 self.assertEqual(rev.p1node, node1)
760 self.assertEqual(rev.p1node, node1)
885 self.assertEqual(rev.p2node, nullid)
761 self.assertEqual(rev.p2node, nullid)
886 self.assertEqual(rev.basenode, node1)
762 self.assertEqual(rev.basenode, node1)
887 self.assertIsNone(rev.baserevisionsize)
763 self.assertIsNone(rev.baserevisionsize)
888 self.assertIsNone(rev.revision)
764 self.assertIsNone(rev.revision)
889 self.assertEqual(rev.delta,
765 self.assertEqual(rev.delta,
890 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
766 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
891 fulltext2)
767 fulltext2)
892
768
893 with self.assertRaises(StopIteration):
769 with self.assertRaises(StopIteration):
894 next(gen)
770 next(gen)
895
771
896 # assumehaveparentrevisions=True allows delta against initial revision.
772 # assumehaveparentrevisions=True allows delta against initial revision.
897 gen = f.emitrevisions([node2, node1],
773 gen = f.emitrevisions([node2, node1],
898 revisiondata=True, assumehaveparentrevisions=True)
774 revisiondata=True, assumehaveparentrevisions=True)
899
775
900 rev = next(gen)
776 rev = next(gen)
901 self.assertEqual(rev.node, node1)
777 self.assertEqual(rev.node, node1)
902 self.assertEqual(rev.p1node, node0)
778 self.assertEqual(rev.p1node, node0)
903 self.assertEqual(rev.p2node, nullid)
779 self.assertEqual(rev.p2node, nullid)
904 self.assertEqual(rev.basenode, node0)
780 self.assertEqual(rev.basenode, node0)
905 self.assertIsNone(rev.baserevisionsize)
781 self.assertIsNone(rev.baserevisionsize)
906 self.assertIsNone(rev.revision)
782 self.assertIsNone(rev.revision)
907 self.assertEqual(rev.delta,
783 self.assertEqual(rev.delta,
908 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
784 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
909 fulltext1)
785 fulltext1)
910
786
911 # forceprevious=True forces a delta against the previous revision.
787 # forceprevious=True forces a delta against the previous revision.
912 # Special case for initial revision.
788 # Special case for initial revision.
913 gen = f.emitrevisions([node0], revisiondata=True, deltaprevious=True)
789 gen = f.emitrevisions([node0], revisiondata=True, deltaprevious=True)
914
790
915 rev = next(gen)
791 rev = next(gen)
916 self.assertEqual(rev.node, node0)
792 self.assertEqual(rev.node, node0)
917 self.assertEqual(rev.p1node, nullid)
793 self.assertEqual(rev.p1node, nullid)
918 self.assertEqual(rev.p2node, nullid)
794 self.assertEqual(rev.p2node, nullid)
919 self.assertEqual(rev.basenode, nullid)
795 self.assertEqual(rev.basenode, nullid)
920 self.assertIsNone(rev.baserevisionsize)
796 self.assertIsNone(rev.baserevisionsize)
921 self.assertIsNone(rev.revision)
797 self.assertIsNone(rev.revision)
922 self.assertEqual(rev.delta,
798 self.assertEqual(rev.delta,
923 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
799 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
924 fulltext0)
800 fulltext0)
925
801
926 with self.assertRaises(StopIteration):
802 with self.assertRaises(StopIteration):
927 next(gen)
803 next(gen)
928
804
929 gen = f.emitrevisions([node0, node2], revisiondata=True,
805 gen = f.emitrevisions([node0, node2], revisiondata=True,
930 deltaprevious=True)
806 deltaprevious=True)
931
807
932 rev = next(gen)
808 rev = next(gen)
933 self.assertEqual(rev.node, node0)
809 self.assertEqual(rev.node, node0)
934 self.assertEqual(rev.p1node, nullid)
810 self.assertEqual(rev.p1node, nullid)
935 self.assertEqual(rev.p2node, nullid)
811 self.assertEqual(rev.p2node, nullid)
936 self.assertEqual(rev.basenode, nullid)
812 self.assertEqual(rev.basenode, nullid)
937 self.assertIsNone(rev.baserevisionsize)
813 self.assertIsNone(rev.baserevisionsize)
938 self.assertIsNone(rev.revision)
814 self.assertIsNone(rev.revision)
939 self.assertEqual(rev.delta,
815 self.assertEqual(rev.delta,
940 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
816 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
941 fulltext0)
817 fulltext0)
942
818
943 rev = next(gen)
819 rev = next(gen)
944 self.assertEqual(rev.node, node2)
820 self.assertEqual(rev.node, node2)
945 self.assertEqual(rev.p1node, node1)
821 self.assertEqual(rev.p1node, node1)
946 self.assertEqual(rev.p2node, nullid)
822 self.assertEqual(rev.p2node, nullid)
947 self.assertEqual(rev.basenode, node0)
823 self.assertEqual(rev.basenode, node0)
948
824
949 with self.assertRaises(StopIteration):
825 with self.assertRaises(StopIteration):
950 next(gen)
826 next(gen)
951
827
952 def testrenamed(self):
828 def testrenamed(self):
953 fulltext0 = b'foo'
829 fulltext0 = b'foo'
954 fulltext1 = b'bar'
830 fulltext1 = b'bar'
955 fulltext2 = b'baz'
831 fulltext2 = b'baz'
956
832
957 meta1 = {
833 meta1 = {
958 b'copy': b'source0',
834 b'copy': b'source0',
959 b'copyrev': b'a' * 40,
835 b'copyrev': b'a' * 40,
960 }
836 }
961
837
962 meta2 = {
838 meta2 = {
963 b'copy': b'source1',
839 b'copy': b'source1',
964 b'copyrev': b'b' * 40,
840 b'copyrev': b'b' * 40,
965 }
841 }
966
842
967 stored1 = b''.join([
843 stored1 = b''.join([
968 b'\x01\ncopy: source0\n',
844 b'\x01\ncopy: source0\n',
969 b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
845 b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
970 fulltext1,
846 fulltext1,
971 ])
847 ])
972
848
973 stored2 = b''.join([
849 stored2 = b''.join([
974 b'\x01\ncopy: source1\n',
850 b'\x01\ncopy: source1\n',
975 b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
851 b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
976 fulltext2,
852 fulltext2,
977 ])
853 ])
978
854
979 f = self._makefilefn()
855 f = self._makefilefn()
980 with self._maketransactionfn() as tr:
856 with self._maketransactionfn() as tr:
981 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
857 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
982 node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
858 node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
983 node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
859 node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
984
860
985 self.assertEqual(f.rawsize(1), len(stored1))
861 self.assertEqual(f.rawsize(1), len(stored1))
986 self.assertEqual(f.rawsize(2), len(stored2))
862 self.assertEqual(f.rawsize(2), len(stored2))
987
863
988 # Metadata header isn't recognized when parent isn't nullid.
864 # Metadata header isn't recognized when parent isn't nullid.
989 self.assertEqual(f.size(1), len(stored1))
865 self.assertEqual(f.size(1), len(stored1))
990 self.assertEqual(f.size(2), len(fulltext2))
866 self.assertEqual(f.size(2), len(fulltext2))
991
867
992 self.assertEqual(f.revision(node1), stored1)
868 self.assertEqual(f.revision(node1), stored1)
993 self.assertEqual(f.revision(node1, raw=True), stored1)
869 self.assertEqual(f.revision(node1, raw=True), stored1)
994 self.assertEqual(f.revision(node2), stored2)
870 self.assertEqual(f.revision(node2), stored2)
995 self.assertEqual(f.revision(node2, raw=True), stored2)
871 self.assertEqual(f.revision(node2, raw=True), stored2)
996
872
997 self.assertEqual(f.read(node1), fulltext1)
873 self.assertEqual(f.read(node1), fulltext1)
998 self.assertEqual(f.read(node2), fulltext2)
874 self.assertEqual(f.read(node2), fulltext2)
999
875
1000 # Returns False when first parent is set.
876 # Returns False when first parent is set.
1001 self.assertFalse(f.renamed(node1))
877 self.assertFalse(f.renamed(node1))
1002 self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
878 self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
1003
879
1004 self.assertTrue(f.cmp(node1, fulltext1))
880 self.assertTrue(f.cmp(node1, fulltext1))
1005 self.assertTrue(f.cmp(node1, stored1))
881 self.assertTrue(f.cmp(node1, stored1))
1006 self.assertFalse(f.cmp(node2, fulltext2))
882 self.assertFalse(f.cmp(node2, fulltext2))
1007 self.assertTrue(f.cmp(node2, stored2))
883 self.assertTrue(f.cmp(node2, stored2))
1008
884
1009 def testmetadataprefix(self):
885 def testmetadataprefix(self):
1010 # Content with metadata prefix has extra prefix inserted in storage.
886 # Content with metadata prefix has extra prefix inserted in storage.
1011 fulltext0 = b'\x01\nfoo'
887 fulltext0 = b'\x01\nfoo'
1012 stored0 = b'\x01\n\x01\n\x01\nfoo'
888 stored0 = b'\x01\n\x01\n\x01\nfoo'
1013
889
1014 fulltext1 = b'\x01\nbar'
890 fulltext1 = b'\x01\nbar'
1015 meta1 = {
891 meta1 = {
1016 b'copy': b'source0',
892 b'copy': b'source0',
1017 b'copyrev': b'b' * 40,
893 b'copyrev': b'b' * 40,
1018 }
894 }
1019 stored1 = b''.join([
895 stored1 = b''.join([
1020 b'\x01\ncopy: source0\n',
896 b'\x01\ncopy: source0\n',
1021 b'copyrev: %s\n' % (b'b' * 40),
897 b'copyrev: %s\n' % (b'b' * 40),
1022 b'\x01\n\x01\nbar',
898 b'\x01\n\x01\nbar',
1023 ])
899 ])
1024
900
1025 f = self._makefilefn()
901 f = self._makefilefn()
1026 with self._maketransactionfn() as tr:
902 with self._maketransactionfn() as tr:
1027 node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
903 node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
1028 node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
904 node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
1029
905
1030 self.assertEqual(f.rawsize(0), len(stored0))
906 self.assertEqual(f.rawsize(0), len(stored0))
1031 self.assertEqual(f.rawsize(1), len(stored1))
907 self.assertEqual(f.rawsize(1), len(stored1))
1032
908
1033 # TODO this is buggy.
909 # TODO this is buggy.
1034 self.assertEqual(f.size(0), len(fulltext0) + 4)
910 self.assertEqual(f.size(0), len(fulltext0) + 4)
1035
911
1036 self.assertEqual(f.size(1), len(fulltext1))
912 self.assertEqual(f.size(1), len(fulltext1))
1037
913
1038 self.assertEqual(f.revision(node0), stored0)
914 self.assertEqual(f.revision(node0), stored0)
1039 self.assertEqual(f.revision(node0, raw=True), stored0)
915 self.assertEqual(f.revision(node0, raw=True), stored0)
1040
916
1041 self.assertEqual(f.revision(node1), stored1)
917 self.assertEqual(f.revision(node1), stored1)
1042 self.assertEqual(f.revision(node1, raw=True), stored1)
918 self.assertEqual(f.revision(node1, raw=True), stored1)
1043
919
1044 self.assertEqual(f.read(node0), fulltext0)
920 self.assertEqual(f.read(node0), fulltext0)
1045 self.assertEqual(f.read(node1), fulltext1)
921 self.assertEqual(f.read(node1), fulltext1)
1046
922
1047 self.assertFalse(f.cmp(node0, fulltext0))
923 self.assertFalse(f.cmp(node0, fulltext0))
1048 self.assertTrue(f.cmp(node0, stored0))
924 self.assertTrue(f.cmp(node0, stored0))
1049
925
1050 self.assertFalse(f.cmp(node1, fulltext1))
926 self.assertFalse(f.cmp(node1, fulltext1))
1051 self.assertTrue(f.cmp(node1, stored0))
927 self.assertTrue(f.cmp(node1, stored0))
1052
928
1053 def testcensored(self):
929 def testcensored(self):
1054 f = self._makefilefn()
930 f = self._makefilefn()
1055
931
1056 stored1 = revlog.packmeta({
932 stored1 = revlog.packmeta({
1057 b'censored': b'tombstone',
933 b'censored': b'tombstone',
1058 }, b'')
934 }, b'')
1059
935
1060 # TODO tests are incomplete because we need the node to be
936 # TODO tests are incomplete because we need the node to be
1061 # different due to presence of censor metadata. But we can't
937 # different due to presence of censor metadata. But we can't
1062 # do this with addrevision().
938 # do this with addrevision().
1063 with self._maketransactionfn() as tr:
939 with self._maketransactionfn() as tr:
1064 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
940 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1065 f.addrevision(stored1, tr, 1, node0, nullid,
941 f.addrevision(stored1, tr, 1, node0, nullid,
1066 flags=revlog.REVIDX_ISCENSORED)
942 flags=revlog.REVIDX_ISCENSORED)
1067
943
1068 self.assertEqual(f.flags(1), revlog.REVIDX_ISCENSORED)
944 self.assertEqual(f.flags(1), revlog.REVIDX_ISCENSORED)
1069 self.assertTrue(f.iscensored(1))
945 self.assertTrue(f.iscensored(1))
1070
946
1071 self.assertEqual(f.revision(1), stored1)
947 self.assertEqual(f.revision(1), stored1)
1072 self.assertEqual(f.revision(1, raw=True), stored1)
948 self.assertEqual(f.revision(1, raw=True), stored1)
1073
949
1074 self.assertEqual(f.read(1), b'')
950 self.assertEqual(f.read(1), b'')
1075
951
1076 class ifilemutationtests(basetestcase):
952 class ifilemutationtests(basetestcase):
1077 """Generic tests for the ifilemutation interface.
953 """Generic tests for the ifilemutation interface.
1078
954
1079 All file storage backends that support writing should conform to this
955 All file storage backends that support writing should conform to this
1080 interface.
956 interface.
1081
957
1082 Use ``makeifilemutationtests()`` to create an instance of this type.
958 Use ``makeifilemutationtests()`` to create an instance of this type.
1083 """
959 """
1084 def testaddnoop(self):
960 def testaddnoop(self):
1085 f = self._makefilefn()
961 f = self._makefilefn()
1086 with self._maketransactionfn() as tr:
962 with self._maketransactionfn() as tr:
1087 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
963 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1088 node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
964 node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
1089 # Varying by linkrev shouldn't impact hash.
965 # Varying by linkrev shouldn't impact hash.
1090 node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
966 node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
1091
967
1092 self.assertEqual(node1, node0)
968 self.assertEqual(node1, node0)
1093 self.assertEqual(node2, node0)
969 self.assertEqual(node2, node0)
1094 self.assertEqual(len(f), 1)
970 self.assertEqual(len(f), 1)
1095
971
1096 def testaddrevisionbadnode(self):
972 def testaddrevisionbadnode(self):
1097 f = self._makefilefn()
973 f = self._makefilefn()
1098 with self._maketransactionfn() as tr:
974 with self._maketransactionfn() as tr:
1099 # Adding a revision with bad node value fails.
975 # Adding a revision with bad node value fails.
1100 with self.assertRaises(error.StorageError):
976 with self.assertRaises(error.StorageError):
1101 f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
977 f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
1102
978
1103 def testaddrevisionunknownflag(self):
979 def testaddrevisionunknownflag(self):
1104 f = self._makefilefn()
980 f = self._makefilefn()
1105 with self._maketransactionfn() as tr:
981 with self._maketransactionfn() as tr:
1106 for i in range(15, 0, -1):
982 for i in range(15, 0, -1):
1107 if (1 << i) & ~revlog.REVIDX_KNOWN_FLAGS:
983 if (1 << i) & ~revlog.REVIDX_KNOWN_FLAGS:
1108 flags = 1 << i
984 flags = 1 << i
1109 break
985 break
1110
986
1111 with self.assertRaises(error.StorageError):
987 with self.assertRaises(error.StorageError):
1112 f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
988 f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
1113
989
1114 def testaddgroupsimple(self):
990 def testaddgroupsimple(self):
1115 f = self._makefilefn()
991 f = self._makefilefn()
1116
992
1117 callbackargs = []
993 callbackargs = []
1118 def cb(*args, **kwargs):
994 def cb(*args, **kwargs):
1119 callbackargs.append((args, kwargs))
995 callbackargs.append((args, kwargs))
1120
996
1121 def linkmapper(node):
997 def linkmapper(node):
1122 return 0
998 return 0
1123
999
1124 with self._maketransactionfn() as tr:
1000 with self._maketransactionfn() as tr:
1125 nodes = f.addgroup([], None, tr, addrevisioncb=cb)
1001 nodes = f.addgroup([], None, tr, addrevisioncb=cb)
1126
1002
1127 self.assertEqual(nodes, [])
1003 self.assertEqual(nodes, [])
1128 self.assertEqual(callbackargs, [])
1004 self.assertEqual(callbackargs, [])
1129 self.assertEqual(len(f), 0)
1005 self.assertEqual(len(f), 0)
1130
1006
1131 fulltext0 = b'foo'
1007 fulltext0 = b'foo'
1132 delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
1008 delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
1133
1009
1134 deltas = [
1010 deltas = [
1135 (b'\x01' * 20, nullid, nullid, nullid, nullid, delta0, 0),
1011 (b'\x01' * 20, nullid, nullid, nullid, nullid, delta0, 0),
1136 ]
1012 ]
1137
1013
1138 with self._maketransactionfn() as tr:
1014 with self._maketransactionfn() as tr:
1139 with self.assertRaises(error.StorageError):
1015 with self.assertRaises(error.StorageError):
1140 f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
1016 f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
1141
1017
1142 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
1018 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
1143
1019
1144 f = self._makefilefn()
1020 f = self._makefilefn()
1145
1021
1146 deltas = [
1022 deltas = [
1147 (node0, nullid, nullid, nullid, nullid, delta0, 0),
1023 (node0, nullid, nullid, nullid, nullid, delta0, 0),
1148 ]
1024 ]
1149
1025
1150 with self._maketransactionfn() as tr:
1026 with self._maketransactionfn() as tr:
1151 nodes = f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
1027 nodes = f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
1152
1028
1153 self.assertEqual(nodes, [
1029 self.assertEqual(nodes, [
1154 b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
1030 b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
1155 b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'])
1031 b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'])
1156
1032
1157 self.assertEqual(len(callbackargs), 1)
1033 self.assertEqual(len(callbackargs), 1)
1158 self.assertEqual(callbackargs[0][0][1], nodes[0])
1034 self.assertEqual(callbackargs[0][0][1], nodes[0])
1159
1035
1160 self.assertEqual(list(f.revs()), [0])
1036 self.assertEqual(list(f.revs()), [0])
1161 self.assertEqual(f.rev(nodes[0]), 0)
1037 self.assertEqual(f.rev(nodes[0]), 0)
1162 self.assertEqual(f.node(0), nodes[0])
1038 self.assertEqual(f.node(0), nodes[0])
1163
1039
1164 def testaddgroupmultiple(self):
1040 def testaddgroupmultiple(self):
1165 f = self._makefilefn()
1041 f = self._makefilefn()
1166
1042
1167 fulltexts = [
1043 fulltexts = [
1168 b'foo',
1044 b'foo',
1169 b'bar',
1045 b'bar',
1170 b'x' * 1024,
1046 b'x' * 1024,
1171 ]
1047 ]
1172
1048
1173 nodes = []
1049 nodes = []
1174 with self._maketransactionfn() as tr:
1050 with self._maketransactionfn() as tr:
1175 for fulltext in fulltexts:
1051 for fulltext in fulltexts:
1176 nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
1052 nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
1177
1053
1178 f = self._makefilefn()
1054 f = self._makefilefn()
1179 deltas = []
1055 deltas = []
1180 for i, fulltext in enumerate(fulltexts):
1056 for i, fulltext in enumerate(fulltexts):
1181 delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
1057 delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
1182
1058
1183 deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
1059 deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
1184
1060
1185 with self._maketransactionfn() as tr:
1061 with self._maketransactionfn() as tr:
1186 self.assertEqual(f.addgroup(deltas, lambda x: 0, tr), nodes)
1062 self.assertEqual(f.addgroup(deltas, lambda x: 0, tr), nodes)
1187
1063
1188 self.assertEqual(len(f), len(deltas))
1064 self.assertEqual(len(f), len(deltas))
1189 self.assertEqual(list(f.revs()), [0, 1, 2])
1065 self.assertEqual(list(f.revs()), [0, 1, 2])
1190 self.assertEqual(f.rev(nodes[0]), 0)
1066 self.assertEqual(f.rev(nodes[0]), 0)
1191 self.assertEqual(f.rev(nodes[1]), 1)
1067 self.assertEqual(f.rev(nodes[1]), 1)
1192 self.assertEqual(f.rev(nodes[2]), 2)
1068 self.assertEqual(f.rev(nodes[2]), 2)
1193 self.assertEqual(f.node(0), nodes[0])
1069 self.assertEqual(f.node(0), nodes[0])
1194 self.assertEqual(f.node(1), nodes[1])
1070 self.assertEqual(f.node(1), nodes[1])
1195 self.assertEqual(f.node(2), nodes[2])
1071 self.assertEqual(f.node(2), nodes[2])
1196
1072
1197 def makeifileindextests(makefilefn, maketransactionfn):
1073 def makeifileindextests(makefilefn, maketransactionfn):
1198 """Create a unittest.TestCase class suitable for testing file storage.
1074 """Create a unittest.TestCase class suitable for testing file storage.
1199
1075
1200 ``makefilefn`` is a callable which receives the test case as an
1076 ``makefilefn`` is a callable which receives the test case as an
1201 argument and returns an object implementing the ``ifilestorage`` interface.
1077 argument and returns an object implementing the ``ifilestorage`` interface.
1202
1078
1203 ``maketransactionfn`` is a callable which receives the test case as an
1079 ``maketransactionfn`` is a callable which receives the test case as an
1204 argument and returns a transaction object.
1080 argument and returns a transaction object.
1205
1081
1206 Returns a type that is a ``unittest.TestCase`` that can be used for
1082 Returns a type that is a ``unittest.TestCase`` that can be used for
1207 testing the object implementing the file storage interface. Simply
1083 testing the object implementing the file storage interface. Simply
1208 assign the returned value to a module-level attribute and a test loader
1084 assign the returned value to a module-level attribute and a test loader
1209 should find and run it automatically.
1085 should find and run it automatically.
1210 """
1086 """
1211 d = {
1087 d = {
1212 r'_makefilefn': makefilefn,
1088 r'_makefilefn': makefilefn,
1213 r'_maketransactionfn': maketransactionfn,
1089 r'_maketransactionfn': maketransactionfn,
1214 }
1090 }
1215 return type(r'ifileindextests', (ifileindextests,), d)
1091 return type(r'ifileindextests', (ifileindextests,), d)
1216
1092
1217 def makeifiledatatests(makefilefn, maketransactionfn):
1093 def makeifiledatatests(makefilefn, maketransactionfn):
1218 d = {
1094 d = {
1219 r'_makefilefn': makefilefn,
1095 r'_makefilefn': makefilefn,
1220 r'_maketransactionfn': maketransactionfn,
1096 r'_maketransactionfn': maketransactionfn,
1221 }
1097 }
1222 return type(r'ifiledatatests', (ifiledatatests,), d)
1098 return type(r'ifiledatatests', (ifiledatatests,), d)
1223
1099
1224 def makeifilemutationtests(makefilefn, maketransactionfn):
1100 def makeifilemutationtests(makefilefn, maketransactionfn):
1225 d = {
1101 d = {
1226 r'_makefilefn': makefilefn,
1102 r'_makefilefn': makefilefn,
1227 r'_maketransactionfn': maketransactionfn,
1103 r'_maketransactionfn': maketransactionfn,
1228 }
1104 }
1229 return type(r'ifilemutationtests', (ifilemutationtests,), d)
1105 return type(r'ifilemutationtests', (ifilemutationtests,), d)
@@ -1,735 +1,687 b''
1 # simplestorerepo.py - Extension that swaps in alternate repository storage.
1 # simplestorerepo.py - Extension that swaps in alternate repository storage.
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.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 # To use this with the test suite:
8 # To use this with the test suite:
9 #
9 #
10 # $ HGREPOFEATURES="simplestore" ./run-tests.py \
10 # $ HGREPOFEATURES="simplestore" ./run-tests.py \
11 # --extra-config-opt extensions.simplestore=`pwd`/simplestorerepo.py
11 # --extra-config-opt extensions.simplestore=`pwd`/simplestorerepo.py
12
12
13 from __future__ import absolute_import
13 from __future__ import absolute_import
14
14
15 import stat
15 import stat
16
16
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18 from mercurial.node import (
18 from mercurial.node import (
19 bin,
19 bin,
20 hex,
20 hex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 )
23 )
24 from mercurial.thirdparty import (
24 from mercurial.thirdparty import (
25 attr,
25 attr,
26 cbor,
26 cbor,
27 )
27 )
28 from mercurial import (
28 from mercurial import (
29 ancestor,
29 ancestor,
30 bundlerepo,
30 bundlerepo,
31 error,
31 error,
32 extensions,
32 extensions,
33 localrepo,
33 localrepo,
34 mdiff,
34 mdiff,
35 pycompat,
35 pycompat,
36 repository,
36 repository,
37 revlog,
37 revlog,
38 store,
38 store,
39 verify,
39 verify,
40 )
40 )
41 from mercurial.utils import (
41 from mercurial.utils import (
42 interfaceutil,
42 interfaceutil,
43 )
43 )
44
44
45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 # be specifying the version(s) of Mercurial they are tested with, or
47 # be specifying the version(s) of Mercurial they are tested with, or
48 # leave the attribute unspecified.
48 # leave the attribute unspecified.
49 testedwith = 'ships-with-hg-core'
49 testedwith = 'ships-with-hg-core'
50
50
51 REQUIREMENT = 'testonly-simplestore'
51 REQUIREMENT = 'testonly-simplestore'
52
52
53 def validatenode(node):
53 def validatenode(node):
54 if isinstance(node, int):
54 if isinstance(node, int):
55 raise ValueError('expected node; got int')
55 raise ValueError('expected node; got int')
56
56
57 if len(node) != 20:
57 if len(node) != 20:
58 raise ValueError('expected 20 byte node')
58 raise ValueError('expected 20 byte node')
59
59
60 def validaterev(rev):
60 def validaterev(rev):
61 if not isinstance(rev, int):
61 if not isinstance(rev, int):
62 raise ValueError('expected int')
62 raise ValueError('expected int')
63
63
64 class simplestoreerror(error.StorageError):
64 class simplestoreerror(error.StorageError):
65 pass
65 pass
66
66
67 @interfaceutil.implementer(repository.irevisiondelta)
67 @interfaceutil.implementer(repository.irevisiondelta)
68 @attr.s(slots=True, frozen=True)
68 @attr.s(slots=True, frozen=True)
69 class simplestorerevisiondelta(object):
69 class simplestorerevisiondelta(object):
70 node = attr.ib()
70 node = attr.ib()
71 p1node = attr.ib()
71 p1node = attr.ib()
72 p2node = attr.ib()
72 p2node = attr.ib()
73 basenode = attr.ib()
73 basenode = attr.ib()
74 linknode = attr.ib()
74 linknode = attr.ib()
75 flags = attr.ib()
75 flags = attr.ib()
76 baserevisionsize = attr.ib()
76 baserevisionsize = attr.ib()
77 revision = attr.ib()
77 revision = attr.ib()
78 delta = attr.ib()
78 delta = attr.ib()
79
79
80 @interfaceutil.implementer(repository.ifilestorage)
80 @interfaceutil.implementer(repository.ifilestorage)
81 class filestorage(object):
81 class filestorage(object):
82 """Implements storage for a tracked path.
82 """Implements storage for a tracked path.
83
83
84 Data is stored in the VFS in a directory corresponding to the tracked
84 Data is stored in the VFS in a directory corresponding to the tracked
85 path.
85 path.
86
86
87 Index data is stored in an ``index`` file using CBOR.
87 Index data is stored in an ``index`` file using CBOR.
88
88
89 Fulltext data is stored in files having names of the node.
89 Fulltext data is stored in files having names of the node.
90 """
90 """
91
91
92 def __init__(self, svfs, path):
92 def __init__(self, svfs, path):
93 self._svfs = svfs
93 self._svfs = svfs
94 self._path = path
94 self._path = path
95
95
96 self._storepath = b'/'.join([b'data', path])
96 self._storepath = b'/'.join([b'data', path])
97 self._indexpath = b'/'.join([self._storepath, b'index'])
97 self._indexpath = b'/'.join([self._storepath, b'index'])
98
98
99 indexdata = self._svfs.tryread(self._indexpath)
99 indexdata = self._svfs.tryread(self._indexpath)
100 if indexdata:
100 if indexdata:
101 indexdata = cbor.loads(indexdata)
101 indexdata = cbor.loads(indexdata)
102
102
103 self._indexdata = indexdata or []
103 self._indexdata = indexdata or []
104 self._indexbynode = {}
104 self._indexbynode = {}
105 self._indexbyrev = {}
105 self._indexbyrev = {}
106 self._index = []
106 self._index = []
107 self._refreshindex()
107 self._refreshindex()
108
108
109 # This is used by changegroup code :/
109 # This is used by changegroup code :/
110 self._generaldelta = True
110 self._generaldelta = True
111
111
112 def _refreshindex(self):
112 def _refreshindex(self):
113 self._indexbynode.clear()
113 self._indexbynode.clear()
114 self._indexbyrev.clear()
114 self._indexbyrev.clear()
115 self._index = []
115 self._index = []
116
116
117 for i, entry in enumerate(self._indexdata):
117 for i, entry in enumerate(self._indexdata):
118 self._indexbynode[entry[b'node']] = entry
118 self._indexbynode[entry[b'node']] = entry
119 self._indexbyrev[i] = entry
119 self._indexbyrev[i] = entry
120
120
121 self._indexbynode[nullid] = {
121 self._indexbynode[nullid] = {
122 b'node': nullid,
122 b'node': nullid,
123 b'p1': nullid,
123 b'p1': nullid,
124 b'p2': nullid,
124 b'p2': nullid,
125 b'linkrev': nullrev,
125 b'linkrev': nullrev,
126 b'flags': 0,
126 b'flags': 0,
127 }
127 }
128
128
129 self._indexbyrev[nullrev] = {
129 self._indexbyrev[nullrev] = {
130 b'node': nullid,
130 b'node': nullid,
131 b'p1': nullid,
131 b'p1': nullid,
132 b'p2': nullid,
132 b'p2': nullid,
133 b'linkrev': nullrev,
133 b'linkrev': nullrev,
134 b'flags': 0,
134 b'flags': 0,
135 }
135 }
136
136
137 for i, entry in enumerate(self._indexdata):
137 for i, entry in enumerate(self._indexdata):
138 p1rev, p2rev = self.parentrevs(self.rev(entry[b'node']))
138 p1rev, p2rev = self.parentrevs(self.rev(entry[b'node']))
139
139
140 # start, length, rawsize, chainbase, linkrev, p1, p2, node
140 # start, length, rawsize, chainbase, linkrev, p1, p2, node
141 self._index.append((0, 0, 0, -1, entry[b'linkrev'], p1rev, p2rev,
141 self._index.append((0, 0, 0, -1, entry[b'linkrev'], p1rev, p2rev,
142 entry[b'node']))
142 entry[b'node']))
143
143
144 self._index.append((0, 0, 0, -1, -1, -1, -1, nullid))
144 self._index.append((0, 0, 0, -1, -1, -1, -1, nullid))
145
145
146 def __len__(self):
146 def __len__(self):
147 return len(self._indexdata)
147 return len(self._indexdata)
148
148
149 def __iter__(self):
149 def __iter__(self):
150 return iter(range(len(self)))
150 return iter(range(len(self)))
151
151
152 def revs(self, start=0, stop=None):
152 def revs(self, start=0, stop=None):
153 step = 1
153 step = 1
154 if stop is not None:
154 if stop is not None:
155 if start > stop:
155 if start > stop:
156 step = -1
156 step = -1
157
157
158 stop += step
158 stop += step
159 else:
159 else:
160 stop = len(self)
160 stop = len(self)
161
161
162 return range(start, stop, step)
162 return range(start, stop, step)
163
163
164 def parents(self, node):
164 def parents(self, node):
165 validatenode(node)
165 validatenode(node)
166
166
167 if node not in self._indexbynode:
167 if node not in self._indexbynode:
168 raise KeyError('unknown node')
168 raise KeyError('unknown node')
169
169
170 entry = self._indexbynode[node]
170 entry = self._indexbynode[node]
171
171
172 return entry[b'p1'], entry[b'p2']
172 return entry[b'p1'], entry[b'p2']
173
173
174 def parentrevs(self, rev):
174 def parentrevs(self, rev):
175 p1, p2 = self.parents(self._indexbyrev[rev][b'node'])
175 p1, p2 = self.parents(self._indexbyrev[rev][b'node'])
176 return self.rev(p1), self.rev(p2)
176 return self.rev(p1), self.rev(p2)
177
177
178 def rev(self, node):
178 def rev(self, node):
179 validatenode(node)
179 validatenode(node)
180
180
181 try:
181 try:
182 self._indexbynode[node]
182 self._indexbynode[node]
183 except KeyError:
183 except KeyError:
184 raise error.LookupError(node, self._indexpath, _('no node'))
184 raise error.LookupError(node, self._indexpath, _('no node'))
185
185
186 for rev, entry in self._indexbyrev.items():
186 for rev, entry in self._indexbyrev.items():
187 if entry[b'node'] == node:
187 if entry[b'node'] == node:
188 return rev
188 return rev
189
189
190 raise error.ProgrammingError('this should not occur')
190 raise error.ProgrammingError('this should not occur')
191
191
192 def node(self, rev):
192 def node(self, rev):
193 validaterev(rev)
193 validaterev(rev)
194
194
195 return self._indexbyrev[rev][b'node']
195 return self._indexbyrev[rev][b'node']
196
196
197 def lookup(self, node):
197 def lookup(self, node):
198 if isinstance(node, int):
198 if isinstance(node, int):
199 return self.node(node)
199 return self.node(node)
200
200
201 if len(node) == 20:
201 if len(node) == 20:
202 self.rev(node)
202 self.rev(node)
203 return node
203 return node
204
204
205 try:
205 try:
206 rev = int(node)
206 rev = int(node)
207 if '%d' % rev != node:
207 if '%d' % rev != node:
208 raise ValueError
208 raise ValueError
209
209
210 if rev < 0:
210 if rev < 0:
211 rev = len(self) + rev
211 rev = len(self) + rev
212 if rev < 0 or rev >= len(self):
212 if rev < 0 or rev >= len(self):
213 raise ValueError
213 raise ValueError
214
214
215 return self.node(rev)
215 return self.node(rev)
216 except (ValueError, OverflowError):
216 except (ValueError, OverflowError):
217 pass
217 pass
218
218
219 if len(node) == 40:
219 if len(node) == 40:
220 try:
220 try:
221 rawnode = bin(node)
221 rawnode = bin(node)
222 self.rev(rawnode)
222 self.rev(rawnode)
223 return rawnode
223 return rawnode
224 except TypeError:
224 except TypeError:
225 pass
225 pass
226
226
227 raise error.LookupError(node, self._path, _('invalid lookup input'))
227 raise error.LookupError(node, self._path, _('invalid lookup input'))
228
228
229 def linkrev(self, rev):
229 def linkrev(self, rev):
230 validaterev(rev)
230 validaterev(rev)
231
231
232 return self._indexbyrev[rev][b'linkrev']
232 return self._indexbyrev[rev][b'linkrev']
233
233
234 def flags(self, rev):
234 def flags(self, rev):
235 validaterev(rev)
235 validaterev(rev)
236
236
237 return self._indexbyrev[rev][b'flags']
237 return self._indexbyrev[rev][b'flags']
238
238
239 def deltaparent(self, rev):
239 def deltaparent(self, rev):
240 validaterev(rev)
240 validaterev(rev)
241
241
242 p1node = self.parents(self.node(rev))[0]
242 p1node = self.parents(self.node(rev))[0]
243 return self.rev(p1node)
243 return self.rev(p1node)
244
244
245 def _candelta(self, baserev, rev):
245 def _candelta(self, baserev, rev):
246 validaterev(baserev)
246 validaterev(baserev)
247 validaterev(rev)
247 validaterev(rev)
248
248
249 if ((self.flags(baserev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)
249 if ((self.flags(baserev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)
250 or (self.flags(rev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)):
250 or (self.flags(rev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)):
251 return False
251 return False
252
252
253 return True
253 return True
254
254
255 def rawsize(self, rev):
255 def rawsize(self, rev):
256 validaterev(rev)
256 validaterev(rev)
257 node = self.node(rev)
257 node = self.node(rev)
258 return len(self.revision(node, raw=True))
258 return len(self.revision(node, raw=True))
259
259
260 def _processflags(self, text, flags, operation, raw=False):
260 def _processflags(self, text, flags, operation, raw=False):
261 if flags == 0:
261 if flags == 0:
262 return text, True
262 return text, True
263
263
264 if flags & ~revlog.REVIDX_KNOWN_FLAGS:
264 if flags & ~revlog.REVIDX_KNOWN_FLAGS:
265 raise simplestoreerror(_("incompatible revision flag '%#x'") %
265 raise simplestoreerror(_("incompatible revision flag '%#x'") %
266 (flags & ~revlog.REVIDX_KNOWN_FLAGS))
266 (flags & ~revlog.REVIDX_KNOWN_FLAGS))
267
267
268 validatehash = True
268 validatehash = True
269 # Depending on the operation (read or write), the order might be
269 # Depending on the operation (read or write), the order might be
270 # reversed due to non-commutative transforms.
270 # reversed due to non-commutative transforms.
271 orderedflags = revlog.REVIDX_FLAGS_ORDER
271 orderedflags = revlog.REVIDX_FLAGS_ORDER
272 if operation == 'write':
272 if operation == 'write':
273 orderedflags = reversed(orderedflags)
273 orderedflags = reversed(orderedflags)
274
274
275 for flag in orderedflags:
275 for flag in orderedflags:
276 # If a flagprocessor has been registered for a known flag, apply the
276 # If a flagprocessor has been registered for a known flag, apply the
277 # related operation transform and update result tuple.
277 # related operation transform and update result tuple.
278 if flag & flags:
278 if flag & flags:
279 vhash = True
279 vhash = True
280
280
281 if flag not in revlog._flagprocessors:
281 if flag not in revlog._flagprocessors:
282 message = _("missing processor for flag '%#x'") % (flag)
282 message = _("missing processor for flag '%#x'") % (flag)
283 raise simplestoreerror(message)
283 raise simplestoreerror(message)
284
284
285 processor = revlog._flagprocessors[flag]
285 processor = revlog._flagprocessors[flag]
286 if processor is not None:
286 if processor is not None:
287 readtransform, writetransform, rawtransform = processor
287 readtransform, writetransform, rawtransform = processor
288
288
289 if raw:
289 if raw:
290 vhash = rawtransform(self, text)
290 vhash = rawtransform(self, text)
291 elif operation == 'read':
291 elif operation == 'read':
292 text, vhash = readtransform(self, text)
292 text, vhash = readtransform(self, text)
293 else: # write operation
293 else: # write operation
294 text, vhash = writetransform(self, text)
294 text, vhash = writetransform(self, text)
295 validatehash = validatehash and vhash
295 validatehash = validatehash and vhash
296
296
297 return text, validatehash
297 return text, validatehash
298
298
299 def checkhash(self, text, node, p1=None, p2=None, rev=None):
299 def checkhash(self, text, node, p1=None, p2=None, rev=None):
300 if p1 is None and p2 is None:
300 if p1 is None and p2 is None:
301 p1, p2 = self.parents(node)
301 p1, p2 = self.parents(node)
302 if node != revlog.hash(text, p1, p2):
302 if node != revlog.hash(text, p1, p2):
303 raise simplestoreerror(_("integrity check failed on %s") %
303 raise simplestoreerror(_("integrity check failed on %s") %
304 self._path)
304 self._path)
305
305
306 def revision(self, node, raw=False):
306 def revision(self, node, raw=False):
307 validatenode(node)
307 validatenode(node)
308
308
309 if node == nullid:
309 if node == nullid:
310 return b''
310 return b''
311
311
312 rev = self.rev(node)
312 rev = self.rev(node)
313 flags = self.flags(rev)
313 flags = self.flags(rev)
314
314
315 path = b'/'.join([self._storepath, hex(node)])
315 path = b'/'.join([self._storepath, hex(node)])
316 rawtext = self._svfs.read(path)
316 rawtext = self._svfs.read(path)
317
317
318 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
318 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
319 if validatehash:
319 if validatehash:
320 self.checkhash(text, node, rev=rev)
320 self.checkhash(text, node, rev=rev)
321
321
322 return text
322 return text
323
323
324 def read(self, node):
324 def read(self, node):
325 validatenode(node)
325 validatenode(node)
326
326
327 revision = self.revision(node)
327 revision = self.revision(node)
328
328
329 if not revision.startswith(b'\1\n'):
329 if not revision.startswith(b'\1\n'):
330 return revision
330 return revision
331
331
332 start = revision.index(b'\1\n', 2)
332 start = revision.index(b'\1\n', 2)
333 return revision[start + 2:]
333 return revision[start + 2:]
334
334
335 def renamed(self, node):
335 def renamed(self, node):
336 validatenode(node)
336 validatenode(node)
337
337
338 if self.parents(node)[0] != nullid:
338 if self.parents(node)[0] != nullid:
339 return False
339 return False
340
340
341 fulltext = self.revision(node)
341 fulltext = self.revision(node)
342 m = revlog.parsemeta(fulltext)[0]
342 m = revlog.parsemeta(fulltext)[0]
343
343
344 if m and 'copy' in m:
344 if m and 'copy' in m:
345 return m['copy'], bin(m['copyrev'])
345 return m['copy'], bin(m['copyrev'])
346
346
347 return False
347 return False
348
348
349 def cmp(self, node, text):
349 def cmp(self, node, text):
350 validatenode(node)
350 validatenode(node)
351
351
352 t = text
352 t = text
353
353
354 if text.startswith(b'\1\n'):
354 if text.startswith(b'\1\n'):
355 t = b'\1\n\1\n' + text
355 t = b'\1\n\1\n' + text
356
356
357 p1, p2 = self.parents(node)
357 p1, p2 = self.parents(node)
358
358
359 if revlog.hash(t, p1, p2) == node:
359 if revlog.hash(t, p1, p2) == node:
360 return False
360 return False
361
361
362 if self.iscensored(self.rev(node)):
362 if self.iscensored(self.rev(node)):
363 return text != b''
363 return text != b''
364
364
365 if self.renamed(node):
365 if self.renamed(node):
366 t2 = self.read(node)
366 t2 = self.read(node)
367 return t2 != text
367 return t2 != text
368
368
369 return True
369 return True
370
370
371 def size(self, rev):
371 def size(self, rev):
372 validaterev(rev)
372 validaterev(rev)
373
373
374 node = self._indexbyrev[rev][b'node']
374 node = self._indexbyrev[rev][b'node']
375
375
376 if self.renamed(node):
376 if self.renamed(node):
377 return len(self.read(node))
377 return len(self.read(node))
378
378
379 if self.iscensored(rev):
379 if self.iscensored(rev):
380 return 0
380 return 0
381
381
382 return len(self.revision(node))
382 return len(self.revision(node))
383
383
384 def iscensored(self, rev):
384 def iscensored(self, rev):
385 validaterev(rev)
385 validaterev(rev)
386
386
387 return self.flags(rev) & revlog.REVIDX_ISCENSORED
387 return self.flags(rev) & revlog.REVIDX_ISCENSORED
388
388
389 def commonancestorsheads(self, a, b):
389 def commonancestorsheads(self, a, b):
390 validatenode(a)
390 validatenode(a)
391 validatenode(b)
391 validatenode(b)
392
392
393 a = self.rev(a)
393 a = self.rev(a)
394 b = self.rev(b)
394 b = self.rev(b)
395
395
396 ancestors = ancestor.commonancestorsheads(self.parentrevs, a, b)
396 ancestors = ancestor.commonancestorsheads(self.parentrevs, a, b)
397 return pycompat.maplist(self.node, ancestors)
397 return pycompat.maplist(self.node, ancestors)
398
398
399 def descendants(self, revs):
399 def descendants(self, revs):
400 # This is a copy of revlog.descendants()
400 # This is a copy of revlog.descendants()
401 first = min(revs)
401 first = min(revs)
402 if first == nullrev:
402 if first == nullrev:
403 for i in self:
403 for i in self:
404 yield i
404 yield i
405 return
405 return
406
406
407 seen = set(revs)
407 seen = set(revs)
408 for i in self.revs(start=first + 1):
408 for i in self.revs(start=first + 1):
409 for x in self.parentrevs(i):
409 for x in self.parentrevs(i):
410 if x != nullrev and x in seen:
410 if x != nullrev and x in seen:
411 seen.add(i)
411 seen.add(i)
412 yield i
412 yield i
413 break
413 break
414
414
415 # Required by verify.
415 # Required by verify.
416 def files(self):
416 def files(self):
417 entries = self._svfs.listdir(self._storepath)
417 entries = self._svfs.listdir(self._storepath)
418
418
419 # Strip out undo.backup.* files created as part of transaction
419 # Strip out undo.backup.* files created as part of transaction
420 # recording.
420 # recording.
421 entries = [f for f in entries if not f.startswith('undo.backup.')]
421 entries = [f for f in entries if not f.startswith('undo.backup.')]
422
422
423 return [b'/'.join((self._storepath, f)) for f in entries]
423 return [b'/'.join((self._storepath, f)) for f in entries]
424
424
425 def add(self, text, meta, transaction, linkrev, p1, p2):
425 def add(self, text, meta, transaction, linkrev, p1, p2):
426 if meta or text.startswith(b'\1\n'):
426 if meta or text.startswith(b'\1\n'):
427 text = revlog.packmeta(meta, text)
427 text = revlog.packmeta(meta, text)
428
428
429 return self.addrevision(text, transaction, linkrev, p1, p2)
429 return self.addrevision(text, transaction, linkrev, p1, p2)
430
430
431 def addrevision(self, text, transaction, linkrev, p1, p2, node=None,
431 def addrevision(self, text, transaction, linkrev, p1, p2, node=None,
432 flags=revlog.REVIDX_DEFAULT_FLAGS, cachedelta=None):
432 flags=revlog.REVIDX_DEFAULT_FLAGS, cachedelta=None):
433 validatenode(p1)
433 validatenode(p1)
434 validatenode(p2)
434 validatenode(p2)
435
435
436 if flags:
436 if flags:
437 node = node or revlog.hash(text, p1, p2)
437 node = node or revlog.hash(text, p1, p2)
438
438
439 rawtext, validatehash = self._processflags(text, flags, 'write')
439 rawtext, validatehash = self._processflags(text, flags, 'write')
440
440
441 node = node or revlog.hash(text, p1, p2)
441 node = node or revlog.hash(text, p1, p2)
442
442
443 if node in self._indexbynode:
443 if node in self._indexbynode:
444 return node
444 return node
445
445
446 if validatehash:
446 if validatehash:
447 self.checkhash(rawtext, node, p1=p1, p2=p2)
447 self.checkhash(rawtext, node, p1=p1, p2=p2)
448
448
449 return self._addrawrevision(node, rawtext, transaction, linkrev, p1, p2,
449 return self._addrawrevision(node, rawtext, transaction, linkrev, p1, p2,
450 flags)
450 flags)
451
451
452 def _addrawrevision(self, node, rawtext, transaction, link, p1, p2, flags):
452 def _addrawrevision(self, node, rawtext, transaction, link, p1, p2, flags):
453 transaction.addbackup(self._indexpath)
453 transaction.addbackup(self._indexpath)
454
454
455 path = b'/'.join([self._storepath, hex(node)])
455 path = b'/'.join([self._storepath, hex(node)])
456
456
457 self._svfs.write(path, rawtext)
457 self._svfs.write(path, rawtext)
458
458
459 self._indexdata.append({
459 self._indexdata.append({
460 b'node': node,
460 b'node': node,
461 b'p1': p1,
461 b'p1': p1,
462 b'p2': p2,
462 b'p2': p2,
463 b'linkrev': link,
463 b'linkrev': link,
464 b'flags': flags,
464 b'flags': flags,
465 })
465 })
466
466
467 self._reflectindexupdate()
467 self._reflectindexupdate()
468
468
469 return node
469 return node
470
470
471 def _reflectindexupdate(self):
471 def _reflectindexupdate(self):
472 self._refreshindex()
472 self._refreshindex()
473 self._svfs.write(self._indexpath, cbor.dumps(self._indexdata))
473 self._svfs.write(self._indexpath, cbor.dumps(self._indexdata))
474
474
475 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
475 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
476 nodes = []
476 nodes = []
477
477
478 transaction.addbackup(self._indexpath)
478 transaction.addbackup(self._indexpath)
479
479
480 for node, p1, p2, linknode, deltabase, delta, flags in deltas:
480 for node, p1, p2, linknode, deltabase, delta, flags in deltas:
481 linkrev = linkmapper(linknode)
481 linkrev = linkmapper(linknode)
482 flags = flags or revlog.REVIDX_DEFAULT_FLAGS
482 flags = flags or revlog.REVIDX_DEFAULT_FLAGS
483
483
484 nodes.append(node)
484 nodes.append(node)
485
485
486 if node in self._indexbynode:
486 if node in self._indexbynode:
487 continue
487 continue
488
488
489 # Need to resolve the fulltext from the delta base.
489 # Need to resolve the fulltext from the delta base.
490 if deltabase == nullid:
490 if deltabase == nullid:
491 text = mdiff.patch(b'', delta)
491 text = mdiff.patch(b'', delta)
492 else:
492 else:
493 text = mdiff.patch(self.revision(deltabase), delta)
493 text = mdiff.patch(self.revision(deltabase), delta)
494
494
495 self._addrawrevision(node, text, transaction, linkrev, p1, p2,
495 self._addrawrevision(node, text, transaction, linkrev, p1, p2,
496 flags)
496 flags)
497
497
498 if addrevisioncb:
498 if addrevisioncb:
499 addrevisioncb(self, node)
499 addrevisioncb(self, node)
500
500
501 return nodes
501 return nodes
502
502
503 def revdiff(self, rev1, rev2):
503 def revdiff(self, rev1, rev2):
504 validaterev(rev1)
504 validaterev(rev1)
505 validaterev(rev2)
505 validaterev(rev2)
506
506
507 node1 = self.node(rev1)
507 node1 = self.node(rev1)
508 node2 = self.node(rev2)
508 node2 = self.node(rev2)
509
509
510 return mdiff.textdiff(self.revision(node1, raw=True),
510 return mdiff.textdiff(self.revision(node1, raw=True),
511 self.revision(node2, raw=True))
511 self.revision(node2, raw=True))
512
512
513 def emitrevisiondeltas(self, requests):
514 for request in requests:
515 node = request.node
516 rev = self.rev(node)
517
518 if request.basenode == nullid:
519 baserev = nullrev
520 elif request.basenode is not None:
521 baserev = self.rev(request.basenode)
522 else:
523 # This is a test extension and we can do simple things
524 # for choosing a delta parent.
525 baserev = self.deltaparent(rev)
526
527 if baserev != nullrev and not self._candelta(baserev, rev):
528 baserev = nullrev
529
530 revision = None
531 delta = None
532 baserevisionsize = None
533
534 if self.iscensored(baserev) or self.iscensored(rev):
535 try:
536 revision = self.revision(node, raw=True)
537 except error.CensoredNodeError as e:
538 revision = e.tombstone
539
540 if baserev != nullrev:
541 baserevisionsize = self.rawsize(baserev)
542
543 elif baserev == nullrev:
544 revision = self.revision(node, raw=True)
545 else:
546 delta = self.revdiff(baserev, rev)
547
548 extraflags = revlog.REVIDX_ELLIPSIS if request.ellipsis else 0
549
550 yield simplestorerevisiondelta(
551 node=node,
552 p1node=request.p1node,
553 p2node=request.p2node,
554 linknode=request.linknode,
555 basenode=self.node(baserev),
556 flags=self.flags(rev) | extraflags,
557 baserevisionsize=baserevisionsize,
558 revision=revision,
559 delta=delta)
560
561 def heads(self, start=None, stop=None):
513 def heads(self, start=None, stop=None):
562 # This is copied from revlog.py.
514 # This is copied from revlog.py.
563 if start is None and stop is None:
515 if start is None and stop is None:
564 if not len(self):
516 if not len(self):
565 return [nullid]
517 return [nullid]
566 return [self.node(r) for r in self.headrevs()]
518 return [self.node(r) for r in self.headrevs()]
567
519
568 if start is None:
520 if start is None:
569 start = nullid
521 start = nullid
570 if stop is None:
522 if stop is None:
571 stop = []
523 stop = []
572 stoprevs = set([self.rev(n) for n in stop])
524 stoprevs = set([self.rev(n) for n in stop])
573 startrev = self.rev(start)
525 startrev = self.rev(start)
574 reachable = {startrev}
526 reachable = {startrev}
575 heads = {startrev}
527 heads = {startrev}
576
528
577 parentrevs = self.parentrevs
529 parentrevs = self.parentrevs
578 for r in self.revs(start=startrev + 1):
530 for r in self.revs(start=startrev + 1):
579 for p in parentrevs(r):
531 for p in parentrevs(r):
580 if p in reachable:
532 if p in reachable:
581 if r not in stoprevs:
533 if r not in stoprevs:
582 reachable.add(r)
534 reachable.add(r)
583 heads.add(r)
535 heads.add(r)
584 if p in heads and p not in stoprevs:
536 if p in heads and p not in stoprevs:
585 heads.remove(p)
537 heads.remove(p)
586
538
587 return [self.node(r) for r in heads]
539 return [self.node(r) for r in heads]
588
540
589 def children(self, node):
541 def children(self, node):
590 validatenode(node)
542 validatenode(node)
591
543
592 # This is a copy of revlog.children().
544 # This is a copy of revlog.children().
593 c = []
545 c = []
594 p = self.rev(node)
546 p = self.rev(node)
595 for r in self.revs(start=p + 1):
547 for r in self.revs(start=p + 1):
596 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
548 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
597 if prevs:
549 if prevs:
598 for pr in prevs:
550 for pr in prevs:
599 if pr == p:
551 if pr == p:
600 c.append(self.node(r))
552 c.append(self.node(r))
601 elif p == nullrev:
553 elif p == nullrev:
602 c.append(self.node(r))
554 c.append(self.node(r))
603 return c
555 return c
604
556
605 def getstrippoint(self, minlink):
557 def getstrippoint(self, minlink):
606
558
607 # This is largely a copy of revlog.getstrippoint().
559 # This is largely a copy of revlog.getstrippoint().
608 brokenrevs = set()
560 brokenrevs = set()
609 strippoint = len(self)
561 strippoint = len(self)
610
562
611 heads = {}
563 heads = {}
612 futurelargelinkrevs = set()
564 futurelargelinkrevs = set()
613 for head in self.heads():
565 for head in self.heads():
614 headlinkrev = self.linkrev(self.rev(head))
566 headlinkrev = self.linkrev(self.rev(head))
615 heads[head] = headlinkrev
567 heads[head] = headlinkrev
616 if headlinkrev >= minlink:
568 if headlinkrev >= minlink:
617 futurelargelinkrevs.add(headlinkrev)
569 futurelargelinkrevs.add(headlinkrev)
618
570
619 # This algorithm involves walking down the rev graph, starting at the
571 # This algorithm involves walking down the rev graph, starting at the
620 # heads. Since the revs are topologically sorted according to linkrev,
572 # heads. Since the revs are topologically sorted according to linkrev,
621 # once all head linkrevs are below the minlink, we know there are
573 # once all head linkrevs are below the minlink, we know there are
622 # no more revs that could have a linkrev greater than minlink.
574 # no more revs that could have a linkrev greater than minlink.
623 # So we can stop walking.
575 # So we can stop walking.
624 while futurelargelinkrevs:
576 while futurelargelinkrevs:
625 strippoint -= 1
577 strippoint -= 1
626 linkrev = heads.pop(strippoint)
578 linkrev = heads.pop(strippoint)
627
579
628 if linkrev < minlink:
580 if linkrev < minlink:
629 brokenrevs.add(strippoint)
581 brokenrevs.add(strippoint)
630 else:
582 else:
631 futurelargelinkrevs.remove(linkrev)
583 futurelargelinkrevs.remove(linkrev)
632
584
633 for p in self.parentrevs(strippoint):
585 for p in self.parentrevs(strippoint):
634 if p != nullrev:
586 if p != nullrev:
635 plinkrev = self.linkrev(p)
587 plinkrev = self.linkrev(p)
636 heads[p] = plinkrev
588 heads[p] = plinkrev
637 if plinkrev >= minlink:
589 if plinkrev >= minlink:
638 futurelargelinkrevs.add(plinkrev)
590 futurelargelinkrevs.add(plinkrev)
639
591
640 return strippoint, brokenrevs
592 return strippoint, brokenrevs
641
593
642 def strip(self, minlink, transaction):
594 def strip(self, minlink, transaction):
643 if not len(self):
595 if not len(self):
644 return
596 return
645
597
646 rev, _ignored = self.getstrippoint(minlink)
598 rev, _ignored = self.getstrippoint(minlink)
647 if rev == len(self):
599 if rev == len(self):
648 return
600 return
649
601
650 # Purge index data starting at the requested revision.
602 # Purge index data starting at the requested revision.
651 self._indexdata[rev:] = []
603 self._indexdata[rev:] = []
652 self._reflectindexupdate()
604 self._reflectindexupdate()
653
605
654 def issimplestorefile(f, kind, st):
606 def issimplestorefile(f, kind, st):
655 if kind != stat.S_IFREG:
607 if kind != stat.S_IFREG:
656 return False
608 return False
657
609
658 if store.isrevlog(f, kind, st):
610 if store.isrevlog(f, kind, st):
659 return False
611 return False
660
612
661 # Ignore transaction undo files.
613 # Ignore transaction undo files.
662 if f.startswith('undo.'):
614 if f.startswith('undo.'):
663 return False
615 return False
664
616
665 # Otherwise assume it belongs to the simple store.
617 # Otherwise assume it belongs to the simple store.
666 return True
618 return True
667
619
668 class simplestore(store.encodedstore):
620 class simplestore(store.encodedstore):
669 def datafiles(self):
621 def datafiles(self):
670 for x in super(simplestore, self).datafiles():
622 for x in super(simplestore, self).datafiles():
671 yield x
623 yield x
672
624
673 # Supplement with non-revlog files.
625 # Supplement with non-revlog files.
674 extrafiles = self._walk('data', True, filefilter=issimplestorefile)
626 extrafiles = self._walk('data', True, filefilter=issimplestorefile)
675
627
676 for unencoded, encoded, size in extrafiles:
628 for unencoded, encoded, size in extrafiles:
677 try:
629 try:
678 unencoded = store.decodefilename(unencoded)
630 unencoded = store.decodefilename(unencoded)
679 except KeyError:
631 except KeyError:
680 unencoded = None
632 unencoded = None
681
633
682 yield unencoded, encoded, size
634 yield unencoded, encoded, size
683
635
684 def reposetup(ui, repo):
636 def reposetup(ui, repo):
685 if not repo.local():
637 if not repo.local():
686 return
638 return
687
639
688 if isinstance(repo, bundlerepo.bundlerepository):
640 if isinstance(repo, bundlerepo.bundlerepository):
689 raise error.Abort(_('cannot use simple store with bundlerepo'))
641 raise error.Abort(_('cannot use simple store with bundlerepo'))
690
642
691 class simplestorerepo(repo.__class__):
643 class simplestorerepo(repo.__class__):
692 def file(self, f):
644 def file(self, f):
693 return filestorage(self.svfs, f)
645 return filestorage(self.svfs, f)
694
646
695 repo.__class__ = simplestorerepo
647 repo.__class__ = simplestorerepo
696
648
697 def featuresetup(ui, supported):
649 def featuresetup(ui, supported):
698 supported.add(REQUIREMENT)
650 supported.add(REQUIREMENT)
699
651
700 def newreporequirements(orig, ui):
652 def newreporequirements(orig, ui):
701 """Modifies default requirements for new repos to use the simple store."""
653 """Modifies default requirements for new repos to use the simple store."""
702 requirements = orig(ui)
654 requirements = orig(ui)
703
655
704 # These requirements are only used to affect creation of the store
656 # These requirements are only used to affect creation of the store
705 # object. We have our own store. So we can remove them.
657 # object. We have our own store. So we can remove them.
706 # TODO do this once we feel like taking the test hit.
658 # TODO do this once we feel like taking the test hit.
707 #if 'fncache' in requirements:
659 #if 'fncache' in requirements:
708 # requirements.remove('fncache')
660 # requirements.remove('fncache')
709 #if 'dotencode' in requirements:
661 #if 'dotencode' in requirements:
710 # requirements.remove('dotencode')
662 # requirements.remove('dotencode')
711
663
712 requirements.add(REQUIREMENT)
664 requirements.add(REQUIREMENT)
713
665
714 return requirements
666 return requirements
715
667
716 def makestore(orig, requirements, path, vfstype):
668 def makestore(orig, requirements, path, vfstype):
717 if REQUIREMENT not in requirements:
669 if REQUIREMENT not in requirements:
718 return orig(requirements, path, vfstype)
670 return orig(requirements, path, vfstype)
719
671
720 return simplestore(path, vfstype)
672 return simplestore(path, vfstype)
721
673
722 def verifierinit(orig, self, *args, **kwargs):
674 def verifierinit(orig, self, *args, **kwargs):
723 orig(self, *args, **kwargs)
675 orig(self, *args, **kwargs)
724
676
725 # We don't care that files in the store don't align with what is
677 # We don't care that files in the store don't align with what is
726 # advertised. So suppress these warnings.
678 # advertised. So suppress these warnings.
727 self.warnorphanstorefiles = False
679 self.warnorphanstorefiles = False
728
680
729 def extsetup(ui):
681 def extsetup(ui):
730 localrepo.featuresetupfuncs.add(featuresetup)
682 localrepo.featuresetupfuncs.add(featuresetup)
731
683
732 extensions.wrapfunction(localrepo, 'newreporequirements',
684 extensions.wrapfunction(localrepo, 'newreporequirements',
733 newreporequirements)
685 newreporequirements)
734 extensions.wrapfunction(store, 'store', makestore)
686 extensions.wrapfunction(store, 'store', makestore)
735 extensions.wrapfunction(verify.verifier, '__init__', verifierinit)
687 extensions.wrapfunction(verify.verifier, '__init__', verifierinit)
@@ -1,236 +1,225 b''
1 # Test that certain objects conform to well-defined interfaces.
1 # Test that certain objects conform to well-defined interfaces.
2
2
3 from __future__ import absolute_import, print_function
3 from __future__ import absolute_import, print_function
4
4
5 from mercurial import encoding
5 from mercurial import encoding
6 encoding.environ[b'HGREALINTERFACES'] = b'1'
6 encoding.environ[b'HGREALINTERFACES'] = b'1'
7
7
8 import os
8 import os
9 import subprocess
9 import subprocess
10 import sys
10 import sys
11
11
12 # Only run if tests are run in a repo
12 # Only run if tests are run in a repo
13 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
13 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
14 'test-repo']):
14 'test-repo']):
15 sys.exit(80)
15 sys.exit(80)
16
16
17 from mercurial.thirdparty.zope import (
17 from mercurial.thirdparty.zope import (
18 interface as zi,
18 interface as zi,
19 )
19 )
20 from mercurial.thirdparty.zope.interface import (
20 from mercurial.thirdparty.zope.interface import (
21 verify as ziverify,
21 verify as ziverify,
22 )
22 )
23 from mercurial import (
23 from mercurial import (
24 changegroup,
25 bundlerepo,
24 bundlerepo,
26 filelog,
25 filelog,
27 httppeer,
26 httppeer,
28 localrepo,
27 localrepo,
29 manifest,
28 manifest,
30 pycompat,
29 pycompat,
31 repository,
30 repository,
32 revlog,
31 revlog,
33 sshpeer,
32 sshpeer,
34 statichttprepo,
33 statichttprepo,
35 ui as uimod,
34 ui as uimod,
36 unionrepo,
35 unionrepo,
37 vfs as vfsmod,
36 vfs as vfsmod,
38 wireprotoserver,
37 wireprotoserver,
39 wireprototypes,
38 wireprototypes,
40 wireprotov1peer,
39 wireprotov1peer,
41 wireprotov2server,
40 wireprotov2server,
42 )
41 )
43
42
44 rootdir = pycompat.fsencode(
43 rootdir = pycompat.fsencode(
45 os.path.normpath(os.path.join(os.path.dirname(__file__), '..')))
44 os.path.normpath(os.path.join(os.path.dirname(__file__), '..')))
46
45
47 def checkzobject(o, allowextra=False):
46 def checkzobject(o, allowextra=False):
48 """Verify an object with a zope interface."""
47 """Verify an object with a zope interface."""
49 ifaces = zi.providedBy(o)
48 ifaces = zi.providedBy(o)
50 if not ifaces:
49 if not ifaces:
51 print('%r does not provide any zope interfaces' % o)
50 print('%r does not provide any zope interfaces' % o)
52 return
51 return
53
52
54 # Run zope.interface's built-in verification routine. This verifies that
53 # Run zope.interface's built-in verification routine. This verifies that
55 # everything that is supposed to be present is present.
54 # everything that is supposed to be present is present.
56 for iface in ifaces:
55 for iface in ifaces:
57 ziverify.verifyObject(iface, o)
56 ziverify.verifyObject(iface, o)
58
57
59 if allowextra:
58 if allowextra:
60 return
59 return
61
60
62 # Now verify that the object provides no extra public attributes that
61 # Now verify that the object provides no extra public attributes that
63 # aren't declared as part of interfaces.
62 # aren't declared as part of interfaces.
64 allowed = set()
63 allowed = set()
65 for iface in ifaces:
64 for iface in ifaces:
66 allowed |= set(iface.names(all=True))
65 allowed |= set(iface.names(all=True))
67
66
68 public = {a for a in dir(o) if not a.startswith('_')}
67 public = {a for a in dir(o) if not a.startswith('_')}
69
68
70 for attr in sorted(public - allowed):
69 for attr in sorted(public - allowed):
71 print('public attribute not declared in interfaces: %s.%s' % (
70 print('public attribute not declared in interfaces: %s.%s' % (
72 o.__class__.__name__, attr))
71 o.__class__.__name__, attr))
73
72
74 # Facilitates testing localpeer.
73 # Facilitates testing localpeer.
75 class dummyrepo(object):
74 class dummyrepo(object):
76 def __init__(self):
75 def __init__(self):
77 self.ui = uimod.ui()
76 self.ui = uimod.ui()
78 def filtered(self, name):
77 def filtered(self, name):
79 pass
78 pass
80 def _restrictcapabilities(self, caps):
79 def _restrictcapabilities(self, caps):
81 pass
80 pass
82
81
83 class dummyopener(object):
82 class dummyopener(object):
84 handlers = []
83 handlers = []
85
84
86 # Facilitates testing sshpeer without requiring a server.
85 # Facilitates testing sshpeer without requiring a server.
87 class badpeer(httppeer.httppeer):
86 class badpeer(httppeer.httppeer):
88 def __init__(self):
87 def __init__(self):
89 super(badpeer, self).__init__(None, None, None, dummyopener(), None,
88 super(badpeer, self).__init__(None, None, None, dummyopener(), None,
90 None)
89 None)
91 self.badattribute = True
90 self.badattribute = True
92
91
93 def badmethod(self):
92 def badmethod(self):
94 pass
93 pass
95
94
96 class dummypipe(object):
95 class dummypipe(object):
97 def close(self):
96 def close(self):
98 pass
97 pass
99
98
100 def main():
99 def main():
101 ui = uimod.ui()
100 ui = uimod.ui()
102 # Needed so we can open a local repo with obsstore without a warning.
101 # Needed so we can open a local repo with obsstore without a warning.
103 ui.setconfig('experimental', 'evolution.createmarkers', True)
102 ui.setconfig('experimental', 'evolution.createmarkers', True)
104
103
105 checkzobject(badpeer())
104 checkzobject(badpeer())
106
105
107 ziverify.verifyClass(repository.ipeerbase, httppeer.httppeer)
106 ziverify.verifyClass(repository.ipeerbase, httppeer.httppeer)
108 checkzobject(httppeer.httppeer(None, None, None, dummyopener(), None, None))
107 checkzobject(httppeer.httppeer(None, None, None, dummyopener(), None, None))
109
108
110 ziverify.verifyClass(repository.ipeerconnection,
109 ziverify.verifyClass(repository.ipeerconnection,
111 httppeer.httpv2peer)
110 httppeer.httpv2peer)
112 ziverify.verifyClass(repository.ipeercapabilities,
111 ziverify.verifyClass(repository.ipeercapabilities,
113 httppeer.httpv2peer)
112 httppeer.httpv2peer)
114 checkzobject(httppeer.httpv2peer(None, b'', b'', None, None, None))
113 checkzobject(httppeer.httpv2peer(None, b'', b'', None, None, None))
115
114
116 ziverify.verifyClass(repository.ipeerbase,
115 ziverify.verifyClass(repository.ipeerbase,
117 localrepo.localpeer)
116 localrepo.localpeer)
118 checkzobject(localrepo.localpeer(dummyrepo()))
117 checkzobject(localrepo.localpeer(dummyrepo()))
119
118
120 ziverify.verifyClass(repository.ipeercommandexecutor,
119 ziverify.verifyClass(repository.ipeercommandexecutor,
121 localrepo.localcommandexecutor)
120 localrepo.localcommandexecutor)
122 checkzobject(localrepo.localcommandexecutor(None))
121 checkzobject(localrepo.localcommandexecutor(None))
123
122
124 ziverify.verifyClass(repository.ipeercommandexecutor,
123 ziverify.verifyClass(repository.ipeercommandexecutor,
125 wireprotov1peer.peerexecutor)
124 wireprotov1peer.peerexecutor)
126 checkzobject(wireprotov1peer.peerexecutor(None))
125 checkzobject(wireprotov1peer.peerexecutor(None))
127
126
128 ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv1peer)
127 ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv1peer)
129 checkzobject(sshpeer.sshv1peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
128 checkzobject(sshpeer.sshv1peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
130 dummypipe(), None, None))
129 dummypipe(), None, None))
131
130
132 ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv2peer)
131 ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv2peer)
133 checkzobject(sshpeer.sshv2peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
132 checkzobject(sshpeer.sshv2peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
134 dummypipe(), None, None))
133 dummypipe(), None, None))
135
134
136 ziverify.verifyClass(repository.ipeerbase, bundlerepo.bundlepeer)
135 ziverify.verifyClass(repository.ipeerbase, bundlerepo.bundlepeer)
137 checkzobject(bundlerepo.bundlepeer(dummyrepo()))
136 checkzobject(bundlerepo.bundlepeer(dummyrepo()))
138
137
139 ziverify.verifyClass(repository.ipeerbase, statichttprepo.statichttppeer)
138 ziverify.verifyClass(repository.ipeerbase, statichttprepo.statichttppeer)
140 checkzobject(statichttprepo.statichttppeer(dummyrepo()))
139 checkzobject(statichttprepo.statichttppeer(dummyrepo()))
141
140
142 ziverify.verifyClass(repository.ipeerbase, unionrepo.unionpeer)
141 ziverify.verifyClass(repository.ipeerbase, unionrepo.unionpeer)
143 checkzobject(unionrepo.unionpeer(dummyrepo()))
142 checkzobject(unionrepo.unionpeer(dummyrepo()))
144
143
145 ziverify.verifyClass(repository.ilocalrepositorymain,
144 ziverify.verifyClass(repository.ilocalrepositorymain,
146 localrepo.localrepository)
145 localrepo.localrepository)
147 ziverify.verifyClass(repository.ilocalrepositoryfilestorage,
146 ziverify.verifyClass(repository.ilocalrepositoryfilestorage,
148 localrepo.revlogfilestorage)
147 localrepo.revlogfilestorage)
149 repo = localrepo.makelocalrepository(ui, rootdir)
148 repo = localrepo.makelocalrepository(ui, rootdir)
150 checkzobject(repo)
149 checkzobject(repo)
151
150
152 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
151 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
153 wireprotoserver.sshv1protocolhandler)
152 wireprotoserver.sshv1protocolhandler)
154 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
153 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
155 wireprotoserver.sshv2protocolhandler)
154 wireprotoserver.sshv2protocolhandler)
156 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
155 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
157 wireprotoserver.httpv1protocolhandler)
156 wireprotoserver.httpv1protocolhandler)
158 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
157 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
159 wireprotov2server.httpv2protocolhandler)
158 wireprotov2server.httpv2protocolhandler)
160
159
161 sshv1 = wireprotoserver.sshv1protocolhandler(None, None, None)
160 sshv1 = wireprotoserver.sshv1protocolhandler(None, None, None)
162 checkzobject(sshv1)
161 checkzobject(sshv1)
163 sshv2 = wireprotoserver.sshv2protocolhandler(None, None, None)
162 sshv2 = wireprotoserver.sshv2protocolhandler(None, None, None)
164 checkzobject(sshv2)
163 checkzobject(sshv2)
165
164
166 httpv1 = wireprotoserver.httpv1protocolhandler(None, None, None)
165 httpv1 = wireprotoserver.httpv1protocolhandler(None, None, None)
167 checkzobject(httpv1)
166 checkzobject(httpv1)
168 httpv2 = wireprotov2server.httpv2protocolhandler(None, None)
167 httpv2 = wireprotov2server.httpv2protocolhandler(None, None)
169 checkzobject(httpv2)
168 checkzobject(httpv2)
170
169
171 ziverify.verifyClass(repository.ifilestorage, filelog.filelog)
170 ziverify.verifyClass(repository.ifilestorage, filelog.filelog)
172 ziverify.verifyClass(repository.imanifestdict, manifest.manifestdict)
171 ziverify.verifyClass(repository.imanifestdict, manifest.manifestdict)
173 ziverify.verifyClass(repository.imanifestrevisionstored,
172 ziverify.verifyClass(repository.imanifestrevisionstored,
174 manifest.manifestctx)
173 manifest.manifestctx)
175 ziverify.verifyClass(repository.imanifestrevisionwritable,
174 ziverify.verifyClass(repository.imanifestrevisionwritable,
176 manifest.memmanifestctx)
175 manifest.memmanifestctx)
177 ziverify.verifyClass(repository.imanifestrevisionstored,
176 ziverify.verifyClass(repository.imanifestrevisionstored,
178 manifest.treemanifestctx)
177 manifest.treemanifestctx)
179 ziverify.verifyClass(repository.imanifestrevisionwritable,
178 ziverify.verifyClass(repository.imanifestrevisionwritable,
180 manifest.memtreemanifestctx)
179 manifest.memtreemanifestctx)
181 ziverify.verifyClass(repository.imanifestlog, manifest.manifestlog)
180 ziverify.verifyClass(repository.imanifestlog, manifest.manifestlog)
182 ziverify.verifyClass(repository.imanifeststorage, manifest.manifestrevlog)
181 ziverify.verifyClass(repository.imanifeststorage, manifest.manifestrevlog)
183
182
184 vfs = vfsmod.vfs(b'.')
183 vfs = vfsmod.vfs(b'.')
185 fl = filelog.filelog(vfs, b'dummy.i')
184 fl = filelog.filelog(vfs, b'dummy.i')
186 checkzobject(fl, allowextra=True)
185 checkzobject(fl, allowextra=True)
187
186
188 # Conforms to imanifestlog.
187 # Conforms to imanifestlog.
189 ml = manifest.manifestlog(vfs, repo, manifest.manifestrevlog(repo.svfs))
188 ml = manifest.manifestlog(vfs, repo, manifest.manifestrevlog(repo.svfs))
190 checkzobject(ml)
189 checkzobject(ml)
191 checkzobject(repo.manifestlog)
190 checkzobject(repo.manifestlog)
192
191
193 # Conforms to imanifestrevision.
192 # Conforms to imanifestrevision.
194 mctx = ml[repo[0].manifestnode()]
193 mctx = ml[repo[0].manifestnode()]
195 checkzobject(mctx)
194 checkzobject(mctx)
196
195
197 # Conforms to imanifestrevisionwritable.
196 # Conforms to imanifestrevisionwritable.
198 checkzobject(mctx.new())
197 checkzobject(mctx.new())
199 checkzobject(mctx.copy())
198 checkzobject(mctx.copy())
200
199
201 # Conforms to imanifestdict.
200 # Conforms to imanifestdict.
202 checkzobject(mctx.read())
201 checkzobject(mctx.read())
203
202
204 mrl = manifest.manifestrevlog(vfs)
203 mrl = manifest.manifestrevlog(vfs)
205 checkzobject(mrl)
204 checkzobject(mrl)
206
205
207 ziverify.verifyClass(repository.irevisiondelta,
206 ziverify.verifyClass(repository.irevisiondelta,
208 revlog.revlogrevisiondelta)
207 revlog.revlogrevisiondelta)
209 ziverify.verifyClass(repository.irevisiondeltarequest,
210 changegroup.revisiondeltarequest)
211
208
212 rd = revlog.revlogrevisiondelta(
209 rd = revlog.revlogrevisiondelta(
213 node=b'',
210 node=b'',
214 p1node=b'',
211 p1node=b'',
215 p2node=b'',
212 p2node=b'',
216 basenode=b'',
213 basenode=b'',
217 linknode=b'',
214 linknode=b'',
218 flags=b'',
215 flags=b'',
219 baserevisionsize=None,
216 baserevisionsize=None,
220 revision=b'',
217 revision=b'',
221 delta=None)
218 delta=None)
222 checkzobject(rd)
219 checkzobject(rd)
223
220
224 rdr = changegroup.revisiondeltarequest(
225 node=b'',
226 linknode=b'',
227 p1node=b'',
228 p2node=b'',
229 basenode=b'')
230 checkzobject(rdr)
231
232 ziverify.verifyClass(repository.iverifyproblem,
221 ziverify.verifyClass(repository.iverifyproblem,
233 revlog.revlogproblem)
222 revlog.revlogproblem)
234 checkzobject(revlog.revlogproblem())
223 checkzobject(revlog.revlogproblem())
235
224
236 main()
225 main()
General Comments 0
You need to be logged in to leave comments. Login now