##// END OF EJS Templates
revlog: decouple caching from addrevision callback for addgroup...
Joerg Sonnenberger -
r47085:711ba0f1 default
parent child Browse files
Show More
@@ -1,1706 +1,1707 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 from .pycompat import open
21 from .pycompat import open
22
22
23 from . import (
23 from . import (
24 error,
24 error,
25 match as matchmod,
25 match as matchmod,
26 mdiff,
26 mdiff,
27 phases,
27 phases,
28 pycompat,
28 pycompat,
29 requirements,
29 requirements,
30 scmutil,
30 scmutil,
31 util,
31 util,
32 )
32 )
33
33
34 from .interfaces import repository
34 from .interfaces import repository
35
35
36 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
36 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
37 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
37 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
38 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
38 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
39
39
40 LFS_REQUIREMENT = b'lfs'
40 LFS_REQUIREMENT = b'lfs'
41
41
42 readexactly = util.readexactly
42 readexactly = util.readexactly
43
43
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(b">l", d)[0]
48 l = struct.unpack(b">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise error.Abort(_(b"invalid chunk length %d") % l)
51 raise error.Abort(_(b"invalid chunk length %d") % l)
52 return b""
52 return b""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55
55
56 def chunkheader(length):
56 def chunkheader(length):
57 """return a changegroup chunk header (string)"""
57 """return a changegroup chunk header (string)"""
58 return struct.pack(b">l", length + 4)
58 return struct.pack(b">l", length + 4)
59
59
60
60
61 def closechunk():
61 def closechunk():
62 """return a changegroup chunk header (string) for a zero-length chunk"""
62 """return a changegroup chunk header (string) for a zero-length chunk"""
63 return struct.pack(b">l", 0)
63 return struct.pack(b">l", 0)
64
64
65
65
66 def _fileheader(path):
66 def _fileheader(path):
67 """Obtain a changegroup chunk header for a named path."""
67 """Obtain a changegroup chunk header for a named path."""
68 return chunkheader(len(path)) + path
68 return chunkheader(len(path)) + path
69
69
70
70
71 def writechunks(ui, chunks, filename, vfs=None):
71 def writechunks(ui, chunks, filename, vfs=None):
72 """Write chunks to a file and return its filename.
72 """Write chunks to a file and return its filename.
73
73
74 The stream is assumed to be a bundle file.
74 The stream is assumed to be a bundle file.
75 Existing files will not be overwritten.
75 Existing files will not be overwritten.
76 If no filename is specified, a temporary file is created.
76 If no filename is specified, a temporary file is created.
77 """
77 """
78 fh = None
78 fh = None
79 cleanup = None
79 cleanup = None
80 try:
80 try:
81 if filename:
81 if filename:
82 if vfs:
82 if vfs:
83 fh = vfs.open(filename, b"wb")
83 fh = vfs.open(filename, b"wb")
84 else:
84 else:
85 # Increase default buffer size because default is usually
85 # Increase default buffer size because default is usually
86 # small (4k is common on Linux).
86 # small (4k is common on Linux).
87 fh = open(filename, b"wb", 131072)
87 fh = open(filename, b"wb", 131072)
88 else:
88 else:
89 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
89 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
90 fh = os.fdopen(fd, "wb")
90 fh = os.fdopen(fd, "wb")
91 cleanup = filename
91 cleanup = filename
92 for c in chunks:
92 for c in chunks:
93 fh.write(c)
93 fh.write(c)
94 cleanup = None
94 cleanup = None
95 return filename
95 return filename
96 finally:
96 finally:
97 if fh is not None:
97 if fh is not None:
98 fh.close()
98 fh.close()
99 if cleanup is not None:
99 if cleanup is not None:
100 if filename and vfs:
100 if filename and vfs:
101 vfs.unlink(cleanup)
101 vfs.unlink(cleanup)
102 else:
102 else:
103 os.unlink(cleanup)
103 os.unlink(cleanup)
104
104
105
105
106 class cg1unpacker(object):
106 class cg1unpacker(object):
107 """Unpacker for cg1 changegroup streams.
107 """Unpacker for cg1 changegroup streams.
108
108
109 A changegroup unpacker handles the framing of the revision data in
109 A changegroup unpacker handles the framing of the revision data in
110 the wire format. Most consumers will want to use the apply()
110 the wire format. Most consumers will want to use the apply()
111 method to add the changes from the changegroup to a repository.
111 method to add the changes from the changegroup to a repository.
112
112
113 If you're forwarding a changegroup unmodified to another consumer,
113 If you're forwarding a changegroup unmodified to another consumer,
114 use getchunks(), which returns an iterator of changegroup
114 use getchunks(), which returns an iterator of changegroup
115 chunks. This is mostly useful for cases where you need to know the
115 chunks. This is mostly useful for cases where you need to know the
116 data stream has ended by observing the end of the changegroup.
116 data stream has ended by observing the end of the changegroup.
117
117
118 deltachunk() is useful only if you're applying delta data. Most
118 deltachunk() is useful only if you're applying delta data. Most
119 consumers should prefer apply() instead.
119 consumers should prefer apply() instead.
120
120
121 A few other public methods exist. Those are used only for
121 A few other public methods exist. Those are used only for
122 bundlerepo and some debug commands - their use is discouraged.
122 bundlerepo and some debug commands - their use is discouraged.
123 """
123 """
124
124
125 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
125 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
126 deltaheadersize = deltaheader.size
126 deltaheadersize = deltaheader.size
127 version = b'01'
127 version = b'01'
128 _grouplistcount = 1 # One list of files after the manifests
128 _grouplistcount = 1 # One list of files after the manifests
129
129
130 def __init__(self, fh, alg, extras=None):
130 def __init__(self, fh, alg, extras=None):
131 if alg is None:
131 if alg is None:
132 alg = b'UN'
132 alg = b'UN'
133 if alg not in util.compengines.supportedbundletypes:
133 if alg not in util.compengines.supportedbundletypes:
134 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
134 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
135 if alg == b'BZ':
135 if alg == b'BZ':
136 alg = b'_truncatedBZ'
136 alg = b'_truncatedBZ'
137
137
138 compengine = util.compengines.forbundletype(alg)
138 compengine = util.compengines.forbundletype(alg)
139 self._stream = compengine.decompressorreader(fh)
139 self._stream = compengine.decompressorreader(fh)
140 self._type = alg
140 self._type = alg
141 self.extras = extras or {}
141 self.extras = extras or {}
142 self.callback = None
142 self.callback = None
143
143
144 # These methods (compressed, read, seek, tell) all appear to only
144 # These methods (compressed, read, seek, tell) all appear to only
145 # be used by bundlerepo, but it's a little hard to tell.
145 # be used by bundlerepo, but it's a little hard to tell.
146 def compressed(self):
146 def compressed(self):
147 return self._type is not None and self._type != b'UN'
147 return self._type is not None and self._type != b'UN'
148
148
149 def read(self, l):
149 def read(self, l):
150 return self._stream.read(l)
150 return self._stream.read(l)
151
151
152 def seek(self, pos):
152 def seek(self, pos):
153 return self._stream.seek(pos)
153 return self._stream.seek(pos)
154
154
155 def tell(self):
155 def tell(self):
156 return self._stream.tell()
156 return self._stream.tell()
157
157
158 def close(self):
158 def close(self):
159 return self._stream.close()
159 return self._stream.close()
160
160
161 def _chunklength(self):
161 def _chunklength(self):
162 d = readexactly(self._stream, 4)
162 d = readexactly(self._stream, 4)
163 l = struct.unpack(b">l", d)[0]
163 l = struct.unpack(b">l", d)[0]
164 if l <= 4:
164 if l <= 4:
165 if l:
165 if l:
166 raise error.Abort(_(b"invalid chunk length %d") % l)
166 raise error.Abort(_(b"invalid chunk length %d") % l)
167 return 0
167 return 0
168 if self.callback:
168 if self.callback:
169 self.callback()
169 self.callback()
170 return l - 4
170 return l - 4
171
171
172 def changelogheader(self):
172 def changelogheader(self):
173 """v10 does not have a changelog header chunk"""
173 """v10 does not have a changelog header chunk"""
174 return {}
174 return {}
175
175
176 def manifestheader(self):
176 def manifestheader(self):
177 """v10 does not have a manifest header chunk"""
177 """v10 does not have a manifest header chunk"""
178 return {}
178 return {}
179
179
180 def filelogheader(self):
180 def filelogheader(self):
181 """return the header of the filelogs chunk, v10 only has the filename"""
181 """return the header of the filelogs chunk, v10 only has the filename"""
182 l = self._chunklength()
182 l = self._chunklength()
183 if not l:
183 if not l:
184 return {}
184 return {}
185 fname = readexactly(self._stream, l)
185 fname = readexactly(self._stream, l)
186 return {b'filename': fname}
186 return {b'filename': fname}
187
187
188 def _deltaheader(self, headertuple, prevnode):
188 def _deltaheader(self, headertuple, prevnode):
189 node, p1, p2, cs = headertuple
189 node, p1, p2, cs = headertuple
190 if prevnode is None:
190 if prevnode is None:
191 deltabase = p1
191 deltabase = p1
192 else:
192 else:
193 deltabase = prevnode
193 deltabase = prevnode
194 flags = 0
194 flags = 0
195 return node, p1, p2, deltabase, cs, flags
195 return node, p1, p2, deltabase, cs, flags
196
196
197 def deltachunk(self, prevnode):
197 def deltachunk(self, prevnode):
198 l = self._chunklength()
198 l = self._chunklength()
199 if not l:
199 if not l:
200 return {}
200 return {}
201 headerdata = readexactly(self._stream, self.deltaheadersize)
201 headerdata = readexactly(self._stream, self.deltaheadersize)
202 header = self.deltaheader.unpack(headerdata)
202 header = self.deltaheader.unpack(headerdata)
203 delta = readexactly(self._stream, l - self.deltaheadersize)
203 delta = readexactly(self._stream, l - self.deltaheadersize)
204 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
204 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
205 return (node, p1, p2, cs, deltabase, delta, flags)
205 return (node, p1, p2, cs, deltabase, delta, flags)
206
206
207 def getchunks(self):
207 def getchunks(self):
208 """returns all the chunks contains in the bundle
208 """returns all the chunks contains in the bundle
209
209
210 Used when you need to forward the binary stream to a file or another
210 Used when you need to forward the binary stream to a file or another
211 network API. To do so, it parse the changegroup data, otherwise it will
211 network API. To do so, it parse the changegroup data, otherwise it will
212 block in case of sshrepo because it don't know the end of the stream.
212 block in case of sshrepo because it don't know the end of the stream.
213 """
213 """
214 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
214 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
215 # and a list of filelogs. For changegroup 3, we expect 4 parts:
215 # and a list of filelogs. For changegroup 3, we expect 4 parts:
216 # changelog, manifestlog, a list of tree manifestlogs, and a list of
216 # changelog, manifestlog, a list of tree manifestlogs, and a list of
217 # filelogs.
217 # filelogs.
218 #
218 #
219 # Changelog and manifestlog parts are terminated with empty chunks. The
219 # Changelog and manifestlog parts are terminated with empty chunks. The
220 # tree and file parts are a list of entry sections. Each entry section
220 # tree and file parts are a list of entry sections. Each entry section
221 # is a series of chunks terminating in an empty chunk. The list of these
221 # is a series of chunks terminating in an empty chunk. The list of these
222 # entry sections is terminated in yet another empty chunk, so we know
222 # entry sections is terminated in yet another empty chunk, so we know
223 # we've reached the end of the tree/file list when we reach an empty
223 # we've reached the end of the tree/file list when we reach an empty
224 # chunk that was proceeded by no non-empty chunks.
224 # chunk that was proceeded by no non-empty chunks.
225
225
226 parts = 0
226 parts = 0
227 while parts < 2 + self._grouplistcount:
227 while parts < 2 + self._grouplistcount:
228 noentries = True
228 noentries = True
229 while True:
229 while True:
230 chunk = getchunk(self)
230 chunk = getchunk(self)
231 if not chunk:
231 if not chunk:
232 # The first two empty chunks represent the end of the
232 # The first two empty chunks represent the end of the
233 # changelog and the manifestlog portions. The remaining
233 # changelog and the manifestlog portions. The remaining
234 # empty chunks represent either A) the end of individual
234 # empty chunks represent either A) the end of individual
235 # tree or file entries in the file list, or B) the end of
235 # tree or file entries in the file list, or B) the end of
236 # the entire list. It's the end of the entire list if there
236 # the entire list. It's the end of the entire list if there
237 # were no entries (i.e. noentries is True).
237 # were no entries (i.e. noentries is True).
238 if parts < 2:
238 if parts < 2:
239 parts += 1
239 parts += 1
240 elif noentries:
240 elif noentries:
241 parts += 1
241 parts += 1
242 break
242 break
243 noentries = False
243 noentries = False
244 yield chunkheader(len(chunk))
244 yield chunkheader(len(chunk))
245 pos = 0
245 pos = 0
246 while pos < len(chunk):
246 while pos < len(chunk):
247 next = pos + 2 ** 20
247 next = pos + 2 ** 20
248 yield chunk[pos:next]
248 yield chunk[pos:next]
249 pos = next
249 pos = next
250 yield closechunk()
250 yield closechunk()
251
251
252 def _unpackmanifests(self, repo, revmap, trp, prog):
252 def _unpackmanifests(self, repo, revmap, trp, prog):
253 self.callback = prog.increment
253 self.callback = prog.increment
254 # no need to check for empty manifest group here:
254 # no need to check for empty manifest group here:
255 # if the result of the merge of 1 and 2 is the same in 3 and 4,
255 # if the result of the merge of 1 and 2 is the same in 3 and 4,
256 # no new manifest will be created and the manifest group will
256 # no new manifest will be created and the manifest group will
257 # be empty during the pull
257 # be empty during the pull
258 self.manifestheader()
258 self.manifestheader()
259 deltas = self.deltaiter()
259 deltas = self.deltaiter()
260 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
260 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
261 prog.complete()
261 prog.complete()
262 self.callback = None
262 self.callback = None
263
263
264 def apply(
264 def apply(
265 self,
265 self,
266 repo,
266 repo,
267 tr,
267 tr,
268 srctype,
268 srctype,
269 url,
269 url,
270 targetphase=phases.draft,
270 targetphase=phases.draft,
271 expectedtotal=None,
271 expectedtotal=None,
272 ):
272 ):
273 """Add the changegroup returned by source.read() to this repo.
273 """Add the changegroup returned by source.read() to this repo.
274 srctype is a string like 'push', 'pull', or 'unbundle'. url is
274 srctype is a string like 'push', 'pull', or 'unbundle'. url is
275 the URL of the repo where this changegroup is coming from.
275 the URL of the repo where this changegroup is coming from.
276
276
277 Return an integer summarizing the change to this repo:
277 Return an integer summarizing the change to this repo:
278 - nothing changed or no source: 0
278 - nothing changed or no source: 0
279 - more heads than before: 1+added heads (2..n)
279 - more heads than before: 1+added heads (2..n)
280 - fewer heads than before: -1-removed heads (-2..-n)
280 - fewer heads than before: -1-removed heads (-2..-n)
281 - number of heads stays the same: 1
281 - number of heads stays the same: 1
282 """
282 """
283 repo = repo.unfiltered()
283 repo = repo.unfiltered()
284
284
285 def csmap(x):
285 def csmap(x):
286 repo.ui.debug(b"add changeset %s\n" % short(x))
286 repo.ui.debug(b"add changeset %s\n" % short(x))
287 return len(cl)
287 return len(cl)
288
288
289 def revmap(x):
289 def revmap(x):
290 return cl.rev(x)
290 return cl.rev(x)
291
291
292 try:
292 try:
293 # The transaction may already carry source information. In this
293 # The transaction may already carry source information. In this
294 # case we use the top level data. We overwrite the argument
294 # case we use the top level data. We overwrite the argument
295 # because we need to use the top level value (if they exist)
295 # because we need to use the top level value (if they exist)
296 # in this function.
296 # in this function.
297 srctype = tr.hookargs.setdefault(b'source', srctype)
297 srctype = tr.hookargs.setdefault(b'source', srctype)
298 tr.hookargs.setdefault(b'url', url)
298 tr.hookargs.setdefault(b'url', url)
299 repo.hook(
299 repo.hook(
300 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
300 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
301 )
301 )
302
302
303 # write changelog data to temp files so concurrent readers
303 # write changelog data to temp files so concurrent readers
304 # will not see an inconsistent view
304 # will not see an inconsistent view
305 cl = repo.changelog
305 cl = repo.changelog
306 cl.delayupdate(tr)
306 cl.delayupdate(tr)
307 oldheads = set(cl.heads())
307 oldheads = set(cl.heads())
308
308
309 trp = weakref.proxy(tr)
309 trp = weakref.proxy(tr)
310 # pull off the changeset group
310 # pull off the changeset group
311 repo.ui.status(_(b"adding changesets\n"))
311 repo.ui.status(_(b"adding changesets\n"))
312 clstart = len(cl)
312 clstart = len(cl)
313 progress = repo.ui.makeprogress(
313 progress = repo.ui.makeprogress(
314 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
314 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
315 )
315 )
316 self.callback = progress.increment
316 self.callback = progress.increment
317
317
318 efilesset = set()
318 efilesset = set()
319 cgnodes = []
319 cgnodes = []
320
320
321 def ondupchangelog(cl, node):
321 def ondupchangelog(cl, node):
322 if cl.rev(node) < clstart:
322 if cl.rev(node) < clstart:
323 cgnodes.append(node)
323 cgnodes.append(node)
324
324
325 def onchangelog(cl, node):
325 def onchangelog(cl, node):
326 rev = cl.rev(node)
326 rev = cl.rev(node)
327 ctx = cl.changelogrevision(rev)
327 ctx = cl.changelogrevision(rev)
328 efilesset.update(ctx.files)
328 efilesset.update(ctx.files)
329 repo.register_changeset(rev, ctx)
329 repo.register_changeset(rev, ctx)
330
330
331 self.changelogheader()
331 self.changelogheader()
332 deltas = self.deltaiter()
332 deltas = self.deltaiter()
333 if not cl.addgroup(
333 if not cl.addgroup(
334 deltas,
334 deltas,
335 csmap,
335 csmap,
336 trp,
336 trp,
337 alwayscache=True,
337 addrevisioncb=onchangelog,
338 addrevisioncb=onchangelog,
338 duplicaterevisioncb=ondupchangelog,
339 duplicaterevisioncb=ondupchangelog,
339 ):
340 ):
340 repo.ui.develwarn(
341 repo.ui.develwarn(
341 b'applied empty changelog from changegroup',
342 b'applied empty changelog from changegroup',
342 config=b'warn-empty-changegroup',
343 config=b'warn-empty-changegroup',
343 )
344 )
344 efiles = len(efilesset)
345 efiles = len(efilesset)
345 clend = len(cl)
346 clend = len(cl)
346 changesets = clend - clstart
347 changesets = clend - clstart
347 progress.complete()
348 progress.complete()
348 del deltas
349 del deltas
349 # TODO Python 2.7 removal
350 # TODO Python 2.7 removal
350 # del efilesset
351 # del efilesset
351 efilesset = None
352 efilesset = None
352 self.callback = None
353 self.callback = None
353
354
354 # pull off the manifest group
355 # pull off the manifest group
355 repo.ui.status(_(b"adding manifests\n"))
356 repo.ui.status(_(b"adding manifests\n"))
356 # We know that we'll never have more manifests than we had
357 # We know that we'll never have more manifests than we had
357 # changesets.
358 # changesets.
358 progress = repo.ui.makeprogress(
359 progress = repo.ui.makeprogress(
359 _(b'manifests'), unit=_(b'chunks'), total=changesets
360 _(b'manifests'), unit=_(b'chunks'), total=changesets
360 )
361 )
361 self._unpackmanifests(repo, revmap, trp, progress)
362 self._unpackmanifests(repo, revmap, trp, progress)
362
363
363 needfiles = {}
364 needfiles = {}
364 if repo.ui.configbool(b'server', b'validate'):
365 if repo.ui.configbool(b'server', b'validate'):
365 cl = repo.changelog
366 cl = repo.changelog
366 ml = repo.manifestlog
367 ml = repo.manifestlog
367 # validate incoming csets have their manifests
368 # validate incoming csets have their manifests
368 for cset in pycompat.xrange(clstart, clend):
369 for cset in pycompat.xrange(clstart, clend):
369 mfnode = cl.changelogrevision(cset).manifest
370 mfnode = cl.changelogrevision(cset).manifest
370 mfest = ml[mfnode].readdelta()
371 mfest = ml[mfnode].readdelta()
371 # store file nodes we must see
372 # store file nodes we must see
372 for f, n in pycompat.iteritems(mfest):
373 for f, n in pycompat.iteritems(mfest):
373 needfiles.setdefault(f, set()).add(n)
374 needfiles.setdefault(f, set()).add(n)
374
375
375 # process the files
376 # process the files
376 repo.ui.status(_(b"adding file changes\n"))
377 repo.ui.status(_(b"adding file changes\n"))
377 newrevs, newfiles = _addchangegroupfiles(
378 newrevs, newfiles = _addchangegroupfiles(
378 repo, self, revmap, trp, efiles, needfiles
379 repo, self, revmap, trp, efiles, needfiles
379 )
380 )
380
381
381 # making sure the value exists
382 # making sure the value exists
382 tr.changes.setdefault(b'changegroup-count-changesets', 0)
383 tr.changes.setdefault(b'changegroup-count-changesets', 0)
383 tr.changes.setdefault(b'changegroup-count-revisions', 0)
384 tr.changes.setdefault(b'changegroup-count-revisions', 0)
384 tr.changes.setdefault(b'changegroup-count-files', 0)
385 tr.changes.setdefault(b'changegroup-count-files', 0)
385 tr.changes.setdefault(b'changegroup-count-heads', 0)
386 tr.changes.setdefault(b'changegroup-count-heads', 0)
386
387
387 # some code use bundle operation for internal purpose. They usually
388 # some code use bundle operation for internal purpose. They usually
388 # set `ui.quiet` to do this outside of user sight. Size the report
389 # set `ui.quiet` to do this outside of user sight. Size the report
389 # of such operation now happens at the end of the transaction, that
390 # of such operation now happens at the end of the transaction, that
390 # ui.quiet has not direct effect on the output.
391 # ui.quiet has not direct effect on the output.
391 #
392 #
392 # To preserve this intend use an inelegant hack, we fail to report
393 # To preserve this intend use an inelegant hack, we fail to report
393 # the change if `quiet` is set. We should probably move to
394 # the change if `quiet` is set. We should probably move to
394 # something better, but this is a good first step to allow the "end
395 # something better, but this is a good first step to allow the "end
395 # of transaction report" to pass tests.
396 # of transaction report" to pass tests.
396 if not repo.ui.quiet:
397 if not repo.ui.quiet:
397 tr.changes[b'changegroup-count-changesets'] += changesets
398 tr.changes[b'changegroup-count-changesets'] += changesets
398 tr.changes[b'changegroup-count-revisions'] += newrevs
399 tr.changes[b'changegroup-count-revisions'] += newrevs
399 tr.changes[b'changegroup-count-files'] += newfiles
400 tr.changes[b'changegroup-count-files'] += newfiles
400
401
401 deltaheads = 0
402 deltaheads = 0
402 if oldheads:
403 if oldheads:
403 heads = cl.heads()
404 heads = cl.heads()
404 deltaheads += len(heads) - len(oldheads)
405 deltaheads += len(heads) - len(oldheads)
405 for h in heads:
406 for h in heads:
406 if h not in oldheads and repo[h].closesbranch():
407 if h not in oldheads and repo[h].closesbranch():
407 deltaheads -= 1
408 deltaheads -= 1
408
409
409 # see previous comment about checking ui.quiet
410 # see previous comment about checking ui.quiet
410 if not repo.ui.quiet:
411 if not repo.ui.quiet:
411 tr.changes[b'changegroup-count-heads'] += deltaheads
412 tr.changes[b'changegroup-count-heads'] += deltaheads
412 repo.invalidatevolatilesets()
413 repo.invalidatevolatilesets()
413
414
414 if changesets > 0:
415 if changesets > 0:
415 if b'node' not in tr.hookargs:
416 if b'node' not in tr.hookargs:
416 tr.hookargs[b'node'] = hex(cl.node(clstart))
417 tr.hookargs[b'node'] = hex(cl.node(clstart))
417 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
418 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
418 hookargs = dict(tr.hookargs)
419 hookargs = dict(tr.hookargs)
419 else:
420 else:
420 hookargs = dict(tr.hookargs)
421 hookargs = dict(tr.hookargs)
421 hookargs[b'node'] = hex(cl.node(clstart))
422 hookargs[b'node'] = hex(cl.node(clstart))
422 hookargs[b'node_last'] = hex(cl.node(clend - 1))
423 hookargs[b'node_last'] = hex(cl.node(clend - 1))
423 repo.hook(
424 repo.hook(
424 b'pretxnchangegroup',
425 b'pretxnchangegroup',
425 throw=True,
426 throw=True,
426 **pycompat.strkwargs(hookargs)
427 **pycompat.strkwargs(hookargs)
427 )
428 )
428
429
429 added = pycompat.xrange(clstart, clend)
430 added = pycompat.xrange(clstart, clend)
430 phaseall = None
431 phaseall = None
431 if srctype in (b'push', b'serve'):
432 if srctype in (b'push', b'serve'):
432 # Old servers can not push the boundary themselves.
433 # Old servers can not push the boundary themselves.
433 # New servers won't push the boundary if changeset already
434 # New servers won't push the boundary if changeset already
434 # exists locally as secret
435 # exists locally as secret
435 #
436 #
436 # We should not use added here but the list of all change in
437 # We should not use added here but the list of all change in
437 # the bundle
438 # the bundle
438 if repo.publishing():
439 if repo.publishing():
439 targetphase = phaseall = phases.public
440 targetphase = phaseall = phases.public
440 else:
441 else:
441 # closer target phase computation
442 # closer target phase computation
442
443
443 # Those changesets have been pushed from the
444 # Those changesets have been pushed from the
444 # outside, their phases are going to be pushed
445 # outside, their phases are going to be pushed
445 # alongside. Therefor `targetphase` is
446 # alongside. Therefor `targetphase` is
446 # ignored.
447 # ignored.
447 targetphase = phaseall = phases.draft
448 targetphase = phaseall = phases.draft
448 if added:
449 if added:
449 phases.registernew(repo, tr, targetphase, added)
450 phases.registernew(repo, tr, targetphase, added)
450 if phaseall is not None:
451 if phaseall is not None:
451 phases.advanceboundary(repo, tr, phaseall, cgnodes, revs=added)
452 phases.advanceboundary(repo, tr, phaseall, cgnodes, revs=added)
452 cgnodes = []
453 cgnodes = []
453
454
454 if changesets > 0:
455 if changesets > 0:
455
456
456 def runhooks(unused_success):
457 def runhooks(unused_success):
457 # These hooks run when the lock releases, not when the
458 # These hooks run when the lock releases, not when the
458 # transaction closes. So it's possible for the changelog
459 # transaction closes. So it's possible for the changelog
459 # to have changed since we last saw it.
460 # to have changed since we last saw it.
460 if clstart >= len(repo):
461 if clstart >= len(repo):
461 return
462 return
462
463
463 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
464 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
464
465
465 for rev in added:
466 for rev in added:
466 args = hookargs.copy()
467 args = hookargs.copy()
467 args[b'node'] = hex(cl.node(rev))
468 args[b'node'] = hex(cl.node(rev))
468 del args[b'node_last']
469 del args[b'node_last']
469 repo.hook(b"incoming", **pycompat.strkwargs(args))
470 repo.hook(b"incoming", **pycompat.strkwargs(args))
470
471
471 newheads = [h for h in repo.heads() if h not in oldheads]
472 newheads = [h for h in repo.heads() if h not in oldheads]
472 repo.ui.log(
473 repo.ui.log(
473 b"incoming",
474 b"incoming",
474 b"%d incoming changes - new heads: %s\n",
475 b"%d incoming changes - new heads: %s\n",
475 len(added),
476 len(added),
476 b', '.join([hex(c[:6]) for c in newheads]),
477 b', '.join([hex(c[:6]) for c in newheads]),
477 )
478 )
478
479
479 tr.addpostclose(
480 tr.addpostclose(
480 b'changegroup-runhooks-%020i' % clstart,
481 b'changegroup-runhooks-%020i' % clstart,
481 lambda tr: repo._afterlock(runhooks),
482 lambda tr: repo._afterlock(runhooks),
482 )
483 )
483 finally:
484 finally:
484 repo.ui.flush()
485 repo.ui.flush()
485 # never return 0 here:
486 # never return 0 here:
486 if deltaheads < 0:
487 if deltaheads < 0:
487 ret = deltaheads - 1
488 ret = deltaheads - 1
488 else:
489 else:
489 ret = deltaheads + 1
490 ret = deltaheads + 1
490 return ret
491 return ret
491
492
492 def deltaiter(self):
493 def deltaiter(self):
493 """
494 """
494 returns an iterator of the deltas in this changegroup
495 returns an iterator of the deltas in this changegroup
495
496
496 Useful for passing to the underlying storage system to be stored.
497 Useful for passing to the underlying storage system to be stored.
497 """
498 """
498 chain = None
499 chain = None
499 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
500 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
500 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
501 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
501 yield chunkdata
502 yield chunkdata
502 chain = chunkdata[0]
503 chain = chunkdata[0]
503
504
504
505
505 class cg2unpacker(cg1unpacker):
506 class cg2unpacker(cg1unpacker):
506 """Unpacker for cg2 streams.
507 """Unpacker for cg2 streams.
507
508
508 cg2 streams add support for generaldelta, so the delta header
509 cg2 streams add support for generaldelta, so the delta header
509 format is slightly different. All other features about the data
510 format is slightly different. All other features about the data
510 remain the same.
511 remain the same.
511 """
512 """
512
513
513 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
514 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
514 deltaheadersize = deltaheader.size
515 deltaheadersize = deltaheader.size
515 version = b'02'
516 version = b'02'
516
517
517 def _deltaheader(self, headertuple, prevnode):
518 def _deltaheader(self, headertuple, prevnode):
518 node, p1, p2, deltabase, cs = headertuple
519 node, p1, p2, deltabase, cs = headertuple
519 flags = 0
520 flags = 0
520 return node, p1, p2, deltabase, cs, flags
521 return node, p1, p2, deltabase, cs, flags
521
522
522
523
523 class cg3unpacker(cg2unpacker):
524 class cg3unpacker(cg2unpacker):
524 """Unpacker for cg3 streams.
525 """Unpacker for cg3 streams.
525
526
526 cg3 streams add support for exchanging treemanifests and revlog
527 cg3 streams add support for exchanging treemanifests and revlog
527 flags. It adds the revlog flags to the delta header and an empty chunk
528 flags. It adds the revlog flags to the delta header and an empty chunk
528 separating manifests and files.
529 separating manifests and files.
529 """
530 """
530
531
531 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
532 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
532 deltaheadersize = deltaheader.size
533 deltaheadersize = deltaheader.size
533 version = b'03'
534 version = b'03'
534 _grouplistcount = 2 # One list of manifests and one list of files
535 _grouplistcount = 2 # One list of manifests and one list of files
535
536
536 def _deltaheader(self, headertuple, prevnode):
537 def _deltaheader(self, headertuple, prevnode):
537 node, p1, p2, deltabase, cs, flags = headertuple
538 node, p1, p2, deltabase, cs, flags = headertuple
538 return node, p1, p2, deltabase, cs, flags
539 return node, p1, p2, deltabase, cs, flags
539
540
540 def _unpackmanifests(self, repo, revmap, trp, prog):
541 def _unpackmanifests(self, repo, revmap, trp, prog):
541 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
542 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
542 for chunkdata in iter(self.filelogheader, {}):
543 for chunkdata in iter(self.filelogheader, {}):
543 # If we get here, there are directory manifests in the changegroup
544 # If we get here, there are directory manifests in the changegroup
544 d = chunkdata[b"filename"]
545 d = chunkdata[b"filename"]
545 repo.ui.debug(b"adding %s revisions\n" % d)
546 repo.ui.debug(b"adding %s revisions\n" % d)
546 deltas = self.deltaiter()
547 deltas = self.deltaiter()
547 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
548 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
548 raise error.Abort(_(b"received dir revlog group is empty"))
549 raise error.Abort(_(b"received dir revlog group is empty"))
549
550
550
551
551 class headerlessfixup(object):
552 class headerlessfixup(object):
552 def __init__(self, fh, h):
553 def __init__(self, fh, h):
553 self._h = h
554 self._h = h
554 self._fh = fh
555 self._fh = fh
555
556
556 def read(self, n):
557 def read(self, n):
557 if self._h:
558 if self._h:
558 d, self._h = self._h[:n], self._h[n:]
559 d, self._h = self._h[:n], self._h[n:]
559 if len(d) < n:
560 if len(d) < n:
560 d += readexactly(self._fh, n - len(d))
561 d += readexactly(self._fh, n - len(d))
561 return d
562 return d
562 return readexactly(self._fh, n)
563 return readexactly(self._fh, n)
563
564
564
565
565 def _revisiondeltatochunks(delta, headerfn):
566 def _revisiondeltatochunks(delta, headerfn):
566 """Serialize a revisiondelta to changegroup chunks."""
567 """Serialize a revisiondelta to changegroup chunks."""
567
568
568 # The captured revision delta may be encoded as a delta against
569 # The captured revision delta may be encoded as a delta against
569 # a base revision or as a full revision. The changegroup format
570 # a base revision or as a full revision. The changegroup format
570 # requires that everything on the wire be deltas. So for full
571 # requires that everything on the wire be deltas. So for full
571 # revisions, we need to invent a header that says to rewrite
572 # revisions, we need to invent a header that says to rewrite
572 # data.
573 # data.
573
574
574 if delta.delta is not None:
575 if delta.delta is not None:
575 prefix, data = b'', delta.delta
576 prefix, data = b'', delta.delta
576 elif delta.basenode == nullid:
577 elif delta.basenode == nullid:
577 data = delta.revision
578 data = delta.revision
578 prefix = mdiff.trivialdiffheader(len(data))
579 prefix = mdiff.trivialdiffheader(len(data))
579 else:
580 else:
580 data = delta.revision
581 data = delta.revision
581 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
582 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
582
583
583 meta = headerfn(delta)
584 meta = headerfn(delta)
584
585
585 yield chunkheader(len(meta) + len(prefix) + len(data))
586 yield chunkheader(len(meta) + len(prefix) + len(data))
586 yield meta
587 yield meta
587 if prefix:
588 if prefix:
588 yield prefix
589 yield prefix
589 yield data
590 yield data
590
591
591
592
592 def _sortnodesellipsis(store, nodes, cl, lookup):
593 def _sortnodesellipsis(store, nodes, cl, lookup):
593 """Sort nodes for changegroup generation."""
594 """Sort nodes for changegroup generation."""
594 # Ellipses serving mode.
595 # Ellipses serving mode.
595 #
596 #
596 # In a perfect world, we'd generate better ellipsis-ified graphs
597 # In a perfect world, we'd generate better ellipsis-ified graphs
597 # for non-changelog revlogs. In practice, we haven't started doing
598 # for non-changelog revlogs. In practice, we haven't started doing
598 # that yet, so the resulting DAGs for the manifestlog and filelogs
599 # that yet, so the resulting DAGs for the manifestlog and filelogs
599 # are actually full of bogus parentage on all the ellipsis
600 # are actually full of bogus parentage on all the ellipsis
600 # nodes. This has the side effect that, while the contents are
601 # nodes. This has the side effect that, while the contents are
601 # correct, the individual DAGs might be completely out of whack in
602 # correct, the individual DAGs might be completely out of whack in
602 # a case like 882681bc3166 and its ancestors (back about 10
603 # a case like 882681bc3166 and its ancestors (back about 10
603 # revisions or so) in the main hg repo.
604 # revisions or so) in the main hg repo.
604 #
605 #
605 # The one invariant we *know* holds is that the new (potentially
606 # The one invariant we *know* holds is that the new (potentially
606 # bogus) DAG shape will be valid if we order the nodes in the
607 # bogus) DAG shape will be valid if we order the nodes in the
607 # order that they're introduced in dramatis personae by the
608 # order that they're introduced in dramatis personae by the
608 # changelog, so what we do is we sort the non-changelog histories
609 # changelog, so what we do is we sort the non-changelog histories
609 # by the order in which they are used by the changelog.
610 # by the order in which they are used by the changelog.
610 key = lambda n: cl.rev(lookup(n))
611 key = lambda n: cl.rev(lookup(n))
611 return sorted(nodes, key=key)
612 return sorted(nodes, key=key)
612
613
613
614
614 def _resolvenarrowrevisioninfo(
615 def _resolvenarrowrevisioninfo(
615 cl,
616 cl,
616 store,
617 store,
617 ischangelog,
618 ischangelog,
618 rev,
619 rev,
619 linkrev,
620 linkrev,
620 linknode,
621 linknode,
621 clrevtolocalrev,
622 clrevtolocalrev,
622 fullclnodes,
623 fullclnodes,
623 precomputedellipsis,
624 precomputedellipsis,
624 ):
625 ):
625 linkparents = precomputedellipsis[linkrev]
626 linkparents = precomputedellipsis[linkrev]
626
627
627 def local(clrev):
628 def local(clrev):
628 """Turn a changelog revnum into a local revnum.
629 """Turn a changelog revnum into a local revnum.
629
630
630 The ellipsis dag is stored as revnums on the changelog,
631 The ellipsis dag is stored as revnums on the changelog,
631 but when we're producing ellipsis entries for
632 but when we're producing ellipsis entries for
632 non-changelog revlogs, we need to turn those numbers into
633 non-changelog revlogs, we need to turn those numbers into
633 something local. This does that for us, and during the
634 something local. This does that for us, and during the
634 changelog sending phase will also expand the stored
635 changelog sending phase will also expand the stored
635 mappings as needed.
636 mappings as needed.
636 """
637 """
637 if clrev == nullrev:
638 if clrev == nullrev:
638 return nullrev
639 return nullrev
639
640
640 if ischangelog:
641 if ischangelog:
641 return clrev
642 return clrev
642
643
643 # Walk the ellipsis-ized changelog breadth-first looking for a
644 # Walk the ellipsis-ized changelog breadth-first looking for a
644 # change that has been linked from the current revlog.
645 # change that has been linked from the current revlog.
645 #
646 #
646 # For a flat manifest revlog only a single step should be necessary
647 # For a flat manifest revlog only a single step should be necessary
647 # as all relevant changelog entries are relevant to the flat
648 # as all relevant changelog entries are relevant to the flat
648 # manifest.
649 # manifest.
649 #
650 #
650 # For a filelog or tree manifest dirlog however not every changelog
651 # For a filelog or tree manifest dirlog however not every changelog
651 # entry will have been relevant, so we need to skip some changelog
652 # entry will have been relevant, so we need to skip some changelog
652 # nodes even after ellipsis-izing.
653 # nodes even after ellipsis-izing.
653 walk = [clrev]
654 walk = [clrev]
654 while walk:
655 while walk:
655 p = walk[0]
656 p = walk[0]
656 walk = walk[1:]
657 walk = walk[1:]
657 if p in clrevtolocalrev:
658 if p in clrevtolocalrev:
658 return clrevtolocalrev[p]
659 return clrevtolocalrev[p]
659 elif p in fullclnodes:
660 elif p in fullclnodes:
660 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
661 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
661 elif p in precomputedellipsis:
662 elif p in precomputedellipsis:
662 walk.extend(
663 walk.extend(
663 [pp for pp in precomputedellipsis[p] if pp != nullrev]
664 [pp for pp in precomputedellipsis[p] if pp != nullrev]
664 )
665 )
665 else:
666 else:
666 # In this case, we've got an ellipsis with parents
667 # In this case, we've got an ellipsis with parents
667 # outside the current bundle (likely an
668 # outside the current bundle (likely an
668 # incremental pull). We "know" that we can use the
669 # incremental pull). We "know" that we can use the
669 # value of this same revlog at whatever revision
670 # value of this same revlog at whatever revision
670 # is pointed to by linknode. "Know" is in scare
671 # is pointed to by linknode. "Know" is in scare
671 # quotes because I haven't done enough examination
672 # quotes because I haven't done enough examination
672 # of edge cases to convince myself this is really
673 # of edge cases to convince myself this is really
673 # a fact - it works for all the (admittedly
674 # a fact - it works for all the (admittedly
674 # thorough) cases in our testsuite, but I would be
675 # thorough) cases in our testsuite, but I would be
675 # somewhat unsurprised to find a case in the wild
676 # somewhat unsurprised to find a case in the wild
676 # where this breaks down a bit. That said, I don't
677 # where this breaks down a bit. That said, I don't
677 # know if it would hurt anything.
678 # know if it would hurt anything.
678 for i in pycompat.xrange(rev, 0, -1):
679 for i in pycompat.xrange(rev, 0, -1):
679 if store.linkrev(i) == clrev:
680 if store.linkrev(i) == clrev:
680 return i
681 return i
681 # We failed to resolve a parent for this node, so
682 # We failed to resolve a parent for this node, so
682 # we crash the changegroup construction.
683 # we crash the changegroup construction.
683 raise error.Abort(
684 raise error.Abort(
684 b'unable to resolve parent while packing %r %r'
685 b'unable to resolve parent while packing %r %r'
685 b' for changeset %r' % (store.indexfile, rev, clrev)
686 b' for changeset %r' % (store.indexfile, rev, clrev)
686 )
687 )
687
688
688 return nullrev
689 return nullrev
689
690
690 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
691 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
691 p1, p2 = nullrev, nullrev
692 p1, p2 = nullrev, nullrev
692 elif len(linkparents) == 1:
693 elif len(linkparents) == 1:
693 (p1,) = sorted(local(p) for p in linkparents)
694 (p1,) = sorted(local(p) for p in linkparents)
694 p2 = nullrev
695 p2 = nullrev
695 else:
696 else:
696 p1, p2 = sorted(local(p) for p in linkparents)
697 p1, p2 = sorted(local(p) for p in linkparents)
697
698
698 p1node, p2node = store.node(p1), store.node(p2)
699 p1node, p2node = store.node(p1), store.node(p2)
699
700
700 return p1node, p2node, linknode
701 return p1node, p2node, linknode
701
702
702
703
703 def deltagroup(
704 def deltagroup(
704 repo,
705 repo,
705 store,
706 store,
706 nodes,
707 nodes,
707 ischangelog,
708 ischangelog,
708 lookup,
709 lookup,
709 forcedeltaparentprev,
710 forcedeltaparentprev,
710 topic=None,
711 topic=None,
711 ellipses=False,
712 ellipses=False,
712 clrevtolocalrev=None,
713 clrevtolocalrev=None,
713 fullclnodes=None,
714 fullclnodes=None,
714 precomputedellipsis=None,
715 precomputedellipsis=None,
715 ):
716 ):
716 """Calculate deltas for a set of revisions.
717 """Calculate deltas for a set of revisions.
717
718
718 Is a generator of ``revisiondelta`` instances.
719 Is a generator of ``revisiondelta`` instances.
719
720
720 If topic is not None, progress detail will be generated using this
721 If topic is not None, progress detail will be generated using this
721 topic name (e.g. changesets, manifests, etc).
722 topic name (e.g. changesets, manifests, etc).
722 """
723 """
723 if not nodes:
724 if not nodes:
724 return
725 return
725
726
726 cl = repo.changelog
727 cl = repo.changelog
727
728
728 if ischangelog:
729 if ischangelog:
729 # `hg log` shows changesets in storage order. To preserve order
730 # `hg log` shows changesets in storage order. To preserve order
730 # across clones, send out changesets in storage order.
731 # across clones, send out changesets in storage order.
731 nodesorder = b'storage'
732 nodesorder = b'storage'
732 elif ellipses:
733 elif ellipses:
733 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
734 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
734 nodesorder = b'nodes'
735 nodesorder = b'nodes'
735 else:
736 else:
736 nodesorder = None
737 nodesorder = None
737
738
738 # Perform ellipses filtering and revision massaging. We do this before
739 # Perform ellipses filtering and revision massaging. We do this before
739 # emitrevisions() because a) filtering out revisions creates less work
740 # emitrevisions() because a) filtering out revisions creates less work
740 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
741 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
741 # assumptions about delta choices and we would possibly send a delta
742 # assumptions about delta choices and we would possibly send a delta
742 # referencing a missing base revision.
743 # referencing a missing base revision.
743 #
744 #
744 # Also, calling lookup() has side-effects with regards to populating
745 # Also, calling lookup() has side-effects with regards to populating
745 # data structures. If we don't call lookup() for each node or if we call
746 # data structures. If we don't call lookup() for each node or if we call
746 # lookup() after the first pass through each node, things can break -
747 # lookup() after the first pass through each node, things can break -
747 # possibly intermittently depending on the python hash seed! For that
748 # possibly intermittently depending on the python hash seed! For that
748 # reason, we store a mapping of all linknodes during the initial node
749 # reason, we store a mapping of all linknodes during the initial node
749 # pass rather than use lookup() on the output side.
750 # pass rather than use lookup() on the output side.
750 if ellipses:
751 if ellipses:
751 filtered = []
752 filtered = []
752 adjustedparents = {}
753 adjustedparents = {}
753 linknodes = {}
754 linknodes = {}
754
755
755 for node in nodes:
756 for node in nodes:
756 rev = store.rev(node)
757 rev = store.rev(node)
757 linknode = lookup(node)
758 linknode = lookup(node)
758 linkrev = cl.rev(linknode)
759 linkrev = cl.rev(linknode)
759 clrevtolocalrev[linkrev] = rev
760 clrevtolocalrev[linkrev] = rev
760
761
761 # If linknode is in fullclnodes, it means the corresponding
762 # If linknode is in fullclnodes, it means the corresponding
762 # changeset was a full changeset and is being sent unaltered.
763 # changeset was a full changeset and is being sent unaltered.
763 if linknode in fullclnodes:
764 if linknode in fullclnodes:
764 linknodes[node] = linknode
765 linknodes[node] = linknode
765
766
766 # If the corresponding changeset wasn't in the set computed
767 # If the corresponding changeset wasn't in the set computed
767 # as relevant to us, it should be dropped outright.
768 # as relevant to us, it should be dropped outright.
768 elif linkrev not in precomputedellipsis:
769 elif linkrev not in precomputedellipsis:
769 continue
770 continue
770
771
771 else:
772 else:
772 # We could probably do this later and avoid the dict
773 # We could probably do this later and avoid the dict
773 # holding state. But it likely doesn't matter.
774 # holding state. But it likely doesn't matter.
774 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
775 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
775 cl,
776 cl,
776 store,
777 store,
777 ischangelog,
778 ischangelog,
778 rev,
779 rev,
779 linkrev,
780 linkrev,
780 linknode,
781 linknode,
781 clrevtolocalrev,
782 clrevtolocalrev,
782 fullclnodes,
783 fullclnodes,
783 precomputedellipsis,
784 precomputedellipsis,
784 )
785 )
785
786
786 adjustedparents[node] = (p1node, p2node)
787 adjustedparents[node] = (p1node, p2node)
787 linknodes[node] = linknode
788 linknodes[node] = linknode
788
789
789 filtered.append(node)
790 filtered.append(node)
790
791
791 nodes = filtered
792 nodes = filtered
792
793
793 # We expect the first pass to be fast, so we only engage the progress
794 # We expect the first pass to be fast, so we only engage the progress
794 # meter for constructing the revision deltas.
795 # meter for constructing the revision deltas.
795 progress = None
796 progress = None
796 if topic is not None:
797 if topic is not None:
797 progress = repo.ui.makeprogress(
798 progress = repo.ui.makeprogress(
798 topic, unit=_(b'chunks'), total=len(nodes)
799 topic, unit=_(b'chunks'), total=len(nodes)
799 )
800 )
800
801
801 configtarget = repo.ui.config(b'devel', b'bundle.delta')
802 configtarget = repo.ui.config(b'devel', b'bundle.delta')
802 if configtarget not in (b'', b'p1', b'full'):
803 if configtarget not in (b'', b'p1', b'full'):
803 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
804 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
804 repo.ui.warn(msg % configtarget)
805 repo.ui.warn(msg % configtarget)
805
806
806 deltamode = repository.CG_DELTAMODE_STD
807 deltamode = repository.CG_DELTAMODE_STD
807 if forcedeltaparentprev:
808 if forcedeltaparentprev:
808 deltamode = repository.CG_DELTAMODE_PREV
809 deltamode = repository.CG_DELTAMODE_PREV
809 elif configtarget == b'p1':
810 elif configtarget == b'p1':
810 deltamode = repository.CG_DELTAMODE_P1
811 deltamode = repository.CG_DELTAMODE_P1
811 elif configtarget == b'full':
812 elif configtarget == b'full':
812 deltamode = repository.CG_DELTAMODE_FULL
813 deltamode = repository.CG_DELTAMODE_FULL
813
814
814 revisions = store.emitrevisions(
815 revisions = store.emitrevisions(
815 nodes,
816 nodes,
816 nodesorder=nodesorder,
817 nodesorder=nodesorder,
817 revisiondata=True,
818 revisiondata=True,
818 assumehaveparentrevisions=not ellipses,
819 assumehaveparentrevisions=not ellipses,
819 deltamode=deltamode,
820 deltamode=deltamode,
820 )
821 )
821
822
822 for i, revision in enumerate(revisions):
823 for i, revision in enumerate(revisions):
823 if progress:
824 if progress:
824 progress.update(i + 1)
825 progress.update(i + 1)
825
826
826 if ellipses:
827 if ellipses:
827 linknode = linknodes[revision.node]
828 linknode = linknodes[revision.node]
828
829
829 if revision.node in adjustedparents:
830 if revision.node in adjustedparents:
830 p1node, p2node = adjustedparents[revision.node]
831 p1node, p2node = adjustedparents[revision.node]
831 revision.p1node = p1node
832 revision.p1node = p1node
832 revision.p2node = p2node
833 revision.p2node = p2node
833 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
834 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
834
835
835 else:
836 else:
836 linknode = lookup(revision.node)
837 linknode = lookup(revision.node)
837
838
838 revision.linknode = linknode
839 revision.linknode = linknode
839 yield revision
840 yield revision
840
841
841 if progress:
842 if progress:
842 progress.complete()
843 progress.complete()
843
844
844
845
845 class cgpacker(object):
846 class cgpacker(object):
846 def __init__(
847 def __init__(
847 self,
848 self,
848 repo,
849 repo,
849 oldmatcher,
850 oldmatcher,
850 matcher,
851 matcher,
851 version,
852 version,
852 builddeltaheader,
853 builddeltaheader,
853 manifestsend,
854 manifestsend,
854 forcedeltaparentprev=False,
855 forcedeltaparentprev=False,
855 bundlecaps=None,
856 bundlecaps=None,
856 ellipses=False,
857 ellipses=False,
857 shallow=False,
858 shallow=False,
858 ellipsisroots=None,
859 ellipsisroots=None,
859 fullnodes=None,
860 fullnodes=None,
860 ):
861 ):
861 """Given a source repo, construct a bundler.
862 """Given a source repo, construct a bundler.
862
863
863 oldmatcher is a matcher that matches on files the client already has.
864 oldmatcher is a matcher that matches on files the client already has.
864 These will not be included in the changegroup.
865 These will not be included in the changegroup.
865
866
866 matcher is a matcher that matches on files to include in the
867 matcher is a matcher that matches on files to include in the
867 changegroup. Used to facilitate sparse changegroups.
868 changegroup. Used to facilitate sparse changegroups.
868
869
869 forcedeltaparentprev indicates whether delta parents must be against
870 forcedeltaparentprev indicates whether delta parents must be against
870 the previous revision in a delta group. This should only be used for
871 the previous revision in a delta group. This should only be used for
871 compatibility with changegroup version 1.
872 compatibility with changegroup version 1.
872
873
873 builddeltaheader is a callable that constructs the header for a group
874 builddeltaheader is a callable that constructs the header for a group
874 delta.
875 delta.
875
876
876 manifestsend is a chunk to send after manifests have been fully emitted.
877 manifestsend is a chunk to send after manifests have been fully emitted.
877
878
878 ellipses indicates whether ellipsis serving mode is enabled.
879 ellipses indicates whether ellipsis serving mode is enabled.
879
880
880 bundlecaps is optional and can be used to specify the set of
881 bundlecaps is optional and can be used to specify the set of
881 capabilities which can be used to build the bundle. While bundlecaps is
882 capabilities which can be used to build the bundle. While bundlecaps is
882 unused in core Mercurial, extensions rely on this feature to communicate
883 unused in core Mercurial, extensions rely on this feature to communicate
883 capabilities to customize the changegroup packer.
884 capabilities to customize the changegroup packer.
884
885
885 shallow indicates whether shallow data might be sent. The packer may
886 shallow indicates whether shallow data might be sent. The packer may
886 need to pack file contents not introduced by the changes being packed.
887 need to pack file contents not introduced by the changes being packed.
887
888
888 fullnodes is the set of changelog nodes which should not be ellipsis
889 fullnodes is the set of changelog nodes which should not be ellipsis
889 nodes. We store this rather than the set of nodes that should be
890 nodes. We store this rather than the set of nodes that should be
890 ellipsis because for very large histories we expect this to be
891 ellipsis because for very large histories we expect this to be
891 significantly smaller.
892 significantly smaller.
892 """
893 """
893 assert oldmatcher
894 assert oldmatcher
894 assert matcher
895 assert matcher
895 self._oldmatcher = oldmatcher
896 self._oldmatcher = oldmatcher
896 self._matcher = matcher
897 self._matcher = matcher
897
898
898 self.version = version
899 self.version = version
899 self._forcedeltaparentprev = forcedeltaparentprev
900 self._forcedeltaparentprev = forcedeltaparentprev
900 self._builddeltaheader = builddeltaheader
901 self._builddeltaheader = builddeltaheader
901 self._manifestsend = manifestsend
902 self._manifestsend = manifestsend
902 self._ellipses = ellipses
903 self._ellipses = ellipses
903
904
904 # Set of capabilities we can use to build the bundle.
905 # Set of capabilities we can use to build the bundle.
905 if bundlecaps is None:
906 if bundlecaps is None:
906 bundlecaps = set()
907 bundlecaps = set()
907 self._bundlecaps = bundlecaps
908 self._bundlecaps = bundlecaps
908 self._isshallow = shallow
909 self._isshallow = shallow
909 self._fullclnodes = fullnodes
910 self._fullclnodes = fullnodes
910
911
911 # Maps ellipsis revs to their roots at the changelog level.
912 # Maps ellipsis revs to their roots at the changelog level.
912 self._precomputedellipsis = ellipsisroots
913 self._precomputedellipsis = ellipsisroots
913
914
914 self._repo = repo
915 self._repo = repo
915
916
916 if self._repo.ui.verbose and not self._repo.ui.debugflag:
917 if self._repo.ui.verbose and not self._repo.ui.debugflag:
917 self._verbosenote = self._repo.ui.note
918 self._verbosenote = self._repo.ui.note
918 else:
919 else:
919 self._verbosenote = lambda s: None
920 self._verbosenote = lambda s: None
920
921
921 def generate(
922 def generate(
922 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
923 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
923 ):
924 ):
924 """Yield a sequence of changegroup byte chunks.
925 """Yield a sequence of changegroup byte chunks.
925 If changelog is False, changelog data won't be added to changegroup
926 If changelog is False, changelog data won't be added to changegroup
926 """
927 """
927
928
928 repo = self._repo
929 repo = self._repo
929 cl = repo.changelog
930 cl = repo.changelog
930
931
931 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
932 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
932 size = 0
933 size = 0
933
934
934 clstate, deltas = self._generatechangelog(
935 clstate, deltas = self._generatechangelog(
935 cl, clnodes, generate=changelog
936 cl, clnodes, generate=changelog
936 )
937 )
937 for delta in deltas:
938 for delta in deltas:
938 for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
939 for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
939 size += len(chunk)
940 size += len(chunk)
940 yield chunk
941 yield chunk
941
942
942 close = closechunk()
943 close = closechunk()
943 size += len(close)
944 size += len(close)
944 yield closechunk()
945 yield closechunk()
945
946
946 self._verbosenote(_(b'%8.i (changelog)\n') % size)
947 self._verbosenote(_(b'%8.i (changelog)\n') % size)
947
948
948 clrevorder = clstate[b'clrevorder']
949 clrevorder = clstate[b'clrevorder']
949 manifests = clstate[b'manifests']
950 manifests = clstate[b'manifests']
950 changedfiles = clstate[b'changedfiles']
951 changedfiles = clstate[b'changedfiles']
951
952
952 # We need to make sure that the linkrev in the changegroup refers to
953 # We need to make sure that the linkrev in the changegroup refers to
953 # the first changeset that introduced the manifest or file revision.
954 # the first changeset that introduced the manifest or file revision.
954 # The fastpath is usually safer than the slowpath, because the filelogs
955 # The fastpath is usually safer than the slowpath, because the filelogs
955 # are walked in revlog order.
956 # are walked in revlog order.
956 #
957 #
957 # When taking the slowpath when the manifest revlog uses generaldelta,
958 # When taking the slowpath when the manifest revlog uses generaldelta,
958 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
959 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
959 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
960 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
960 #
961 #
961 # When taking the fastpath, we are only vulnerable to reordering
962 # When taking the fastpath, we are only vulnerable to reordering
962 # of the changelog itself. The changelog never uses generaldelta and is
963 # of the changelog itself. The changelog never uses generaldelta and is
963 # never reordered. To handle this case, we simply take the slowpath,
964 # never reordered. To handle this case, we simply take the slowpath,
964 # which already has the 'clrevorder' logic. This was also fixed in
965 # which already has the 'clrevorder' logic. This was also fixed in
965 # cc0ff93d0c0c.
966 # cc0ff93d0c0c.
966
967
967 # Treemanifests don't work correctly with fastpathlinkrev
968 # Treemanifests don't work correctly with fastpathlinkrev
968 # either, because we don't discover which directory nodes to
969 # either, because we don't discover which directory nodes to
969 # send along with files. This could probably be fixed.
970 # send along with files. This could probably be fixed.
970 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
971 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
971
972
972 fnodes = {} # needed file nodes
973 fnodes = {} # needed file nodes
973
974
974 size = 0
975 size = 0
975 it = self.generatemanifests(
976 it = self.generatemanifests(
976 commonrevs,
977 commonrevs,
977 clrevorder,
978 clrevorder,
978 fastpathlinkrev,
979 fastpathlinkrev,
979 manifests,
980 manifests,
980 fnodes,
981 fnodes,
981 source,
982 source,
982 clstate[b'clrevtomanifestrev'],
983 clstate[b'clrevtomanifestrev'],
983 )
984 )
984
985
985 for tree, deltas in it:
986 for tree, deltas in it:
986 if tree:
987 if tree:
987 assert self.version == b'03'
988 assert self.version == b'03'
988 chunk = _fileheader(tree)
989 chunk = _fileheader(tree)
989 size += len(chunk)
990 size += len(chunk)
990 yield chunk
991 yield chunk
991
992
992 for delta in deltas:
993 for delta in deltas:
993 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
994 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
994 for chunk in chunks:
995 for chunk in chunks:
995 size += len(chunk)
996 size += len(chunk)
996 yield chunk
997 yield chunk
997
998
998 close = closechunk()
999 close = closechunk()
999 size += len(close)
1000 size += len(close)
1000 yield close
1001 yield close
1001
1002
1002 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1003 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1003 yield self._manifestsend
1004 yield self._manifestsend
1004
1005
1005 mfdicts = None
1006 mfdicts = None
1006 if self._ellipses and self._isshallow:
1007 if self._ellipses and self._isshallow:
1007 mfdicts = [
1008 mfdicts = [
1008 (self._repo.manifestlog[n].read(), lr)
1009 (self._repo.manifestlog[n].read(), lr)
1009 for (n, lr) in pycompat.iteritems(manifests)
1010 for (n, lr) in pycompat.iteritems(manifests)
1010 ]
1011 ]
1011
1012
1012 manifests.clear()
1013 manifests.clear()
1013 clrevs = {cl.rev(x) for x in clnodes}
1014 clrevs = {cl.rev(x) for x in clnodes}
1014
1015
1015 it = self.generatefiles(
1016 it = self.generatefiles(
1016 changedfiles,
1017 changedfiles,
1017 commonrevs,
1018 commonrevs,
1018 source,
1019 source,
1019 mfdicts,
1020 mfdicts,
1020 fastpathlinkrev,
1021 fastpathlinkrev,
1021 fnodes,
1022 fnodes,
1022 clrevs,
1023 clrevs,
1023 )
1024 )
1024
1025
1025 for path, deltas in it:
1026 for path, deltas in it:
1026 h = _fileheader(path)
1027 h = _fileheader(path)
1027 size = len(h)
1028 size = len(h)
1028 yield h
1029 yield h
1029
1030
1030 for delta in deltas:
1031 for delta in deltas:
1031 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
1032 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
1032 for chunk in chunks:
1033 for chunk in chunks:
1033 size += len(chunk)
1034 size += len(chunk)
1034 yield chunk
1035 yield chunk
1035
1036
1036 close = closechunk()
1037 close = closechunk()
1037 size += len(close)
1038 size += len(close)
1038 yield close
1039 yield close
1039
1040
1040 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1041 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1041
1042
1042 yield closechunk()
1043 yield closechunk()
1043
1044
1044 if clnodes:
1045 if clnodes:
1045 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1046 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1046
1047
1047 def _generatechangelog(self, cl, nodes, generate=True):
1048 def _generatechangelog(self, cl, nodes, generate=True):
1048 """Generate data for changelog chunks.
1049 """Generate data for changelog chunks.
1049
1050
1050 Returns a 2-tuple of a dict containing state and an iterable of
1051 Returns a 2-tuple of a dict containing state and an iterable of
1051 byte chunks. The state will not be fully populated until the
1052 byte chunks. The state will not be fully populated until the
1052 chunk stream has been fully consumed.
1053 chunk stream has been fully consumed.
1053
1054
1054 if generate is False, the state will be fully populated and no chunk
1055 if generate is False, the state will be fully populated and no chunk
1055 stream will be yielded
1056 stream will be yielded
1056 """
1057 """
1057 clrevorder = {}
1058 clrevorder = {}
1058 manifests = {}
1059 manifests = {}
1059 mfl = self._repo.manifestlog
1060 mfl = self._repo.manifestlog
1060 changedfiles = set()
1061 changedfiles = set()
1061 clrevtomanifestrev = {}
1062 clrevtomanifestrev = {}
1062
1063
1063 state = {
1064 state = {
1064 b'clrevorder': clrevorder,
1065 b'clrevorder': clrevorder,
1065 b'manifests': manifests,
1066 b'manifests': manifests,
1066 b'changedfiles': changedfiles,
1067 b'changedfiles': changedfiles,
1067 b'clrevtomanifestrev': clrevtomanifestrev,
1068 b'clrevtomanifestrev': clrevtomanifestrev,
1068 }
1069 }
1069
1070
1070 if not (generate or self._ellipses):
1071 if not (generate or self._ellipses):
1071 # sort the nodes in storage order
1072 # sort the nodes in storage order
1072 nodes = sorted(nodes, key=cl.rev)
1073 nodes = sorted(nodes, key=cl.rev)
1073 for node in nodes:
1074 for node in nodes:
1074 c = cl.changelogrevision(node)
1075 c = cl.changelogrevision(node)
1075 clrevorder[node] = len(clrevorder)
1076 clrevorder[node] = len(clrevorder)
1076 # record the first changeset introducing this manifest version
1077 # record the first changeset introducing this manifest version
1077 manifests.setdefault(c.manifest, node)
1078 manifests.setdefault(c.manifest, node)
1078 # Record a complete list of potentially-changed files in
1079 # Record a complete list of potentially-changed files in
1079 # this manifest.
1080 # this manifest.
1080 changedfiles.update(c.files)
1081 changedfiles.update(c.files)
1081
1082
1082 return state, ()
1083 return state, ()
1083
1084
1084 # Callback for the changelog, used to collect changed files and
1085 # Callback for the changelog, used to collect changed files and
1085 # manifest nodes.
1086 # manifest nodes.
1086 # Returns the linkrev node (identity in the changelog case).
1087 # Returns the linkrev node (identity in the changelog case).
1087 def lookupcl(x):
1088 def lookupcl(x):
1088 c = cl.changelogrevision(x)
1089 c = cl.changelogrevision(x)
1089 clrevorder[x] = len(clrevorder)
1090 clrevorder[x] = len(clrevorder)
1090
1091
1091 if self._ellipses:
1092 if self._ellipses:
1092 # Only update manifests if x is going to be sent. Otherwise we
1093 # Only update manifests if x is going to be sent. Otherwise we
1093 # end up with bogus linkrevs specified for manifests and
1094 # end up with bogus linkrevs specified for manifests and
1094 # we skip some manifest nodes that we should otherwise
1095 # we skip some manifest nodes that we should otherwise
1095 # have sent.
1096 # have sent.
1096 if (
1097 if (
1097 x in self._fullclnodes
1098 x in self._fullclnodes
1098 or cl.rev(x) in self._precomputedellipsis
1099 or cl.rev(x) in self._precomputedellipsis
1099 ):
1100 ):
1100
1101
1101 manifestnode = c.manifest
1102 manifestnode = c.manifest
1102 # Record the first changeset introducing this manifest
1103 # Record the first changeset introducing this manifest
1103 # version.
1104 # version.
1104 manifests.setdefault(manifestnode, x)
1105 manifests.setdefault(manifestnode, x)
1105 # Set this narrow-specific dict so we have the lowest
1106 # Set this narrow-specific dict so we have the lowest
1106 # manifest revnum to look up for this cl revnum. (Part of
1107 # manifest revnum to look up for this cl revnum. (Part of
1107 # mapping changelog ellipsis parents to manifest ellipsis
1108 # mapping changelog ellipsis parents to manifest ellipsis
1108 # parents)
1109 # parents)
1109 clrevtomanifestrev.setdefault(
1110 clrevtomanifestrev.setdefault(
1110 cl.rev(x), mfl.rev(manifestnode)
1111 cl.rev(x), mfl.rev(manifestnode)
1111 )
1112 )
1112 # We can't trust the changed files list in the changeset if the
1113 # We can't trust the changed files list in the changeset if the
1113 # client requested a shallow clone.
1114 # client requested a shallow clone.
1114 if self._isshallow:
1115 if self._isshallow:
1115 changedfiles.update(mfl[c.manifest].read().keys())
1116 changedfiles.update(mfl[c.manifest].read().keys())
1116 else:
1117 else:
1117 changedfiles.update(c.files)
1118 changedfiles.update(c.files)
1118 else:
1119 else:
1119 # record the first changeset introducing this manifest version
1120 # record the first changeset introducing this manifest version
1120 manifests.setdefault(c.manifest, x)
1121 manifests.setdefault(c.manifest, x)
1121 # Record a complete list of potentially-changed files in
1122 # Record a complete list of potentially-changed files in
1122 # this manifest.
1123 # this manifest.
1123 changedfiles.update(c.files)
1124 changedfiles.update(c.files)
1124
1125
1125 return x
1126 return x
1126
1127
1127 gen = deltagroup(
1128 gen = deltagroup(
1128 self._repo,
1129 self._repo,
1129 cl,
1130 cl,
1130 nodes,
1131 nodes,
1131 True,
1132 True,
1132 lookupcl,
1133 lookupcl,
1133 self._forcedeltaparentprev,
1134 self._forcedeltaparentprev,
1134 ellipses=self._ellipses,
1135 ellipses=self._ellipses,
1135 topic=_(b'changesets'),
1136 topic=_(b'changesets'),
1136 clrevtolocalrev={},
1137 clrevtolocalrev={},
1137 fullclnodes=self._fullclnodes,
1138 fullclnodes=self._fullclnodes,
1138 precomputedellipsis=self._precomputedellipsis,
1139 precomputedellipsis=self._precomputedellipsis,
1139 )
1140 )
1140
1141
1141 return state, gen
1142 return state, gen
1142
1143
1143 def generatemanifests(
1144 def generatemanifests(
1144 self,
1145 self,
1145 commonrevs,
1146 commonrevs,
1146 clrevorder,
1147 clrevorder,
1147 fastpathlinkrev,
1148 fastpathlinkrev,
1148 manifests,
1149 manifests,
1149 fnodes,
1150 fnodes,
1150 source,
1151 source,
1151 clrevtolocalrev,
1152 clrevtolocalrev,
1152 ):
1153 ):
1153 """Returns an iterator of changegroup chunks containing manifests.
1154 """Returns an iterator of changegroup chunks containing manifests.
1154
1155
1155 `source` is unused here, but is used by extensions like remotefilelog to
1156 `source` is unused here, but is used by extensions like remotefilelog to
1156 change what is sent based in pulls vs pushes, etc.
1157 change what is sent based in pulls vs pushes, etc.
1157 """
1158 """
1158 repo = self._repo
1159 repo = self._repo
1159 mfl = repo.manifestlog
1160 mfl = repo.manifestlog
1160 tmfnodes = {b'': manifests}
1161 tmfnodes = {b'': manifests}
1161
1162
1162 # Callback for the manifest, used to collect linkrevs for filelog
1163 # Callback for the manifest, used to collect linkrevs for filelog
1163 # revisions.
1164 # revisions.
1164 # Returns the linkrev node (collected in lookupcl).
1165 # Returns the linkrev node (collected in lookupcl).
1165 def makelookupmflinknode(tree, nodes):
1166 def makelookupmflinknode(tree, nodes):
1166 if fastpathlinkrev:
1167 if fastpathlinkrev:
1167 assert not tree
1168 assert not tree
1168 return (
1169 return (
1169 manifests.__getitem__
1170 manifests.__getitem__
1170 ) # pytype: disable=unsupported-operands
1171 ) # pytype: disable=unsupported-operands
1171
1172
1172 def lookupmflinknode(x):
1173 def lookupmflinknode(x):
1173 """Callback for looking up the linknode for manifests.
1174 """Callback for looking up the linknode for manifests.
1174
1175
1175 Returns the linkrev node for the specified manifest.
1176 Returns the linkrev node for the specified manifest.
1176
1177
1177 SIDE EFFECT:
1178 SIDE EFFECT:
1178
1179
1179 1) fclnodes gets populated with the list of relevant
1180 1) fclnodes gets populated with the list of relevant
1180 file nodes if we're not using fastpathlinkrev
1181 file nodes if we're not using fastpathlinkrev
1181 2) When treemanifests are in use, collects treemanifest nodes
1182 2) When treemanifests are in use, collects treemanifest nodes
1182 to send
1183 to send
1183
1184
1184 Note that this means manifests must be completely sent to
1185 Note that this means manifests must be completely sent to
1185 the client before you can trust the list of files and
1186 the client before you can trust the list of files and
1186 treemanifests to send.
1187 treemanifests to send.
1187 """
1188 """
1188 clnode = nodes[x]
1189 clnode = nodes[x]
1189 mdata = mfl.get(tree, x).readfast(shallow=True)
1190 mdata = mfl.get(tree, x).readfast(shallow=True)
1190 for p, n, fl in mdata.iterentries():
1191 for p, n, fl in mdata.iterentries():
1191 if fl == b't': # subdirectory manifest
1192 if fl == b't': # subdirectory manifest
1192 subtree = tree + p + b'/'
1193 subtree = tree + p + b'/'
1193 tmfclnodes = tmfnodes.setdefault(subtree, {})
1194 tmfclnodes = tmfnodes.setdefault(subtree, {})
1194 tmfclnode = tmfclnodes.setdefault(n, clnode)
1195 tmfclnode = tmfclnodes.setdefault(n, clnode)
1195 if clrevorder[clnode] < clrevorder[tmfclnode]:
1196 if clrevorder[clnode] < clrevorder[tmfclnode]:
1196 tmfclnodes[n] = clnode
1197 tmfclnodes[n] = clnode
1197 else:
1198 else:
1198 f = tree + p
1199 f = tree + p
1199 fclnodes = fnodes.setdefault(f, {})
1200 fclnodes = fnodes.setdefault(f, {})
1200 fclnode = fclnodes.setdefault(n, clnode)
1201 fclnode = fclnodes.setdefault(n, clnode)
1201 if clrevorder[clnode] < clrevorder[fclnode]:
1202 if clrevorder[clnode] < clrevorder[fclnode]:
1202 fclnodes[n] = clnode
1203 fclnodes[n] = clnode
1203 return clnode
1204 return clnode
1204
1205
1205 return lookupmflinknode
1206 return lookupmflinknode
1206
1207
1207 while tmfnodes:
1208 while tmfnodes:
1208 tree, nodes = tmfnodes.popitem()
1209 tree, nodes = tmfnodes.popitem()
1209
1210
1210 should_visit = self._matcher.visitdir(tree[:-1])
1211 should_visit = self._matcher.visitdir(tree[:-1])
1211 if tree and not should_visit:
1212 if tree and not should_visit:
1212 continue
1213 continue
1213
1214
1214 store = mfl.getstorage(tree)
1215 store = mfl.getstorage(tree)
1215
1216
1216 if not should_visit:
1217 if not should_visit:
1217 # No nodes to send because this directory is out of
1218 # No nodes to send because this directory is out of
1218 # the client's view of the repository (probably
1219 # the client's view of the repository (probably
1219 # because of narrow clones). Do this even for the root
1220 # because of narrow clones). Do this even for the root
1220 # directory (tree=='')
1221 # directory (tree=='')
1221 prunednodes = []
1222 prunednodes = []
1222 else:
1223 else:
1223 # Avoid sending any manifest nodes we can prove the
1224 # Avoid sending any manifest nodes we can prove the
1224 # client already has by checking linkrevs. See the
1225 # client already has by checking linkrevs. See the
1225 # related comment in generatefiles().
1226 # related comment in generatefiles().
1226 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1227 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1227
1228
1228 if tree and not prunednodes:
1229 if tree and not prunednodes:
1229 continue
1230 continue
1230
1231
1231 lookupfn = makelookupmflinknode(tree, nodes)
1232 lookupfn = makelookupmflinknode(tree, nodes)
1232
1233
1233 deltas = deltagroup(
1234 deltas = deltagroup(
1234 self._repo,
1235 self._repo,
1235 store,
1236 store,
1236 prunednodes,
1237 prunednodes,
1237 False,
1238 False,
1238 lookupfn,
1239 lookupfn,
1239 self._forcedeltaparentprev,
1240 self._forcedeltaparentprev,
1240 ellipses=self._ellipses,
1241 ellipses=self._ellipses,
1241 topic=_(b'manifests'),
1242 topic=_(b'manifests'),
1242 clrevtolocalrev=clrevtolocalrev,
1243 clrevtolocalrev=clrevtolocalrev,
1243 fullclnodes=self._fullclnodes,
1244 fullclnodes=self._fullclnodes,
1244 precomputedellipsis=self._precomputedellipsis,
1245 precomputedellipsis=self._precomputedellipsis,
1245 )
1246 )
1246
1247
1247 if not self._oldmatcher.visitdir(store.tree[:-1]):
1248 if not self._oldmatcher.visitdir(store.tree[:-1]):
1248 yield tree, deltas
1249 yield tree, deltas
1249 else:
1250 else:
1250 # 'deltas' is a generator and we need to consume it even if
1251 # 'deltas' is a generator and we need to consume it even if
1251 # we are not going to send it because a side-effect is that
1252 # we are not going to send it because a side-effect is that
1252 # it updates tmdnodes (via lookupfn)
1253 # it updates tmdnodes (via lookupfn)
1253 for d in deltas:
1254 for d in deltas:
1254 pass
1255 pass
1255 if not tree:
1256 if not tree:
1256 yield tree, []
1257 yield tree, []
1257
1258
1258 def _prunemanifests(self, store, nodes, commonrevs):
1259 def _prunemanifests(self, store, nodes, commonrevs):
1259 if not self._ellipses:
1260 if not self._ellipses:
1260 # In non-ellipses case and large repositories, it is better to
1261 # In non-ellipses case and large repositories, it is better to
1261 # prevent calling of store.rev and store.linkrev on a lot of
1262 # prevent calling of store.rev and store.linkrev on a lot of
1262 # nodes as compared to sending some extra data
1263 # nodes as compared to sending some extra data
1263 return nodes.copy()
1264 return nodes.copy()
1264 # This is split out as a separate method to allow filtering
1265 # This is split out as a separate method to allow filtering
1265 # commonrevs in extension code.
1266 # commonrevs in extension code.
1266 #
1267 #
1267 # TODO(augie): this shouldn't be required, instead we should
1268 # TODO(augie): this shouldn't be required, instead we should
1268 # make filtering of revisions to send delegated to the store
1269 # make filtering of revisions to send delegated to the store
1269 # layer.
1270 # layer.
1270 frev, flr = store.rev, store.linkrev
1271 frev, flr = store.rev, store.linkrev
1271 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1272 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1272
1273
1273 # The 'source' parameter is useful for extensions
1274 # The 'source' parameter is useful for extensions
1274 def generatefiles(
1275 def generatefiles(
1275 self,
1276 self,
1276 changedfiles,
1277 changedfiles,
1277 commonrevs,
1278 commonrevs,
1278 source,
1279 source,
1279 mfdicts,
1280 mfdicts,
1280 fastpathlinkrev,
1281 fastpathlinkrev,
1281 fnodes,
1282 fnodes,
1282 clrevs,
1283 clrevs,
1283 ):
1284 ):
1284 changedfiles = [
1285 changedfiles = [
1285 f
1286 f
1286 for f in changedfiles
1287 for f in changedfiles
1287 if self._matcher(f) and not self._oldmatcher(f)
1288 if self._matcher(f) and not self._oldmatcher(f)
1288 ]
1289 ]
1289
1290
1290 if not fastpathlinkrev:
1291 if not fastpathlinkrev:
1291
1292
1292 def normallinknodes(unused, fname):
1293 def normallinknodes(unused, fname):
1293 return fnodes.get(fname, {})
1294 return fnodes.get(fname, {})
1294
1295
1295 else:
1296 else:
1296 cln = self._repo.changelog.node
1297 cln = self._repo.changelog.node
1297
1298
1298 def normallinknodes(store, fname):
1299 def normallinknodes(store, fname):
1299 flinkrev = store.linkrev
1300 flinkrev = store.linkrev
1300 fnode = store.node
1301 fnode = store.node
1301 revs = ((r, flinkrev(r)) for r in store)
1302 revs = ((r, flinkrev(r)) for r in store)
1302 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1303 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1303
1304
1304 clrevtolocalrev = {}
1305 clrevtolocalrev = {}
1305
1306
1306 if self._isshallow:
1307 if self._isshallow:
1307 # In a shallow clone, the linknodes callback needs to also include
1308 # In a shallow clone, the linknodes callback needs to also include
1308 # those file nodes that are in the manifests we sent but weren't
1309 # those file nodes that are in the manifests we sent but weren't
1309 # introduced by those manifests.
1310 # introduced by those manifests.
1310 commonctxs = [self._repo[c] for c in commonrevs]
1311 commonctxs = [self._repo[c] for c in commonrevs]
1311 clrev = self._repo.changelog.rev
1312 clrev = self._repo.changelog.rev
1312
1313
1313 def linknodes(flog, fname):
1314 def linknodes(flog, fname):
1314 for c in commonctxs:
1315 for c in commonctxs:
1315 try:
1316 try:
1316 fnode = c.filenode(fname)
1317 fnode = c.filenode(fname)
1317 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1318 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1318 except error.ManifestLookupError:
1319 except error.ManifestLookupError:
1319 pass
1320 pass
1320 links = normallinknodes(flog, fname)
1321 links = normallinknodes(flog, fname)
1321 if len(links) != len(mfdicts):
1322 if len(links) != len(mfdicts):
1322 for mf, lr in mfdicts:
1323 for mf, lr in mfdicts:
1323 fnode = mf.get(fname, None)
1324 fnode = mf.get(fname, None)
1324 if fnode in links:
1325 if fnode in links:
1325 links[fnode] = min(links[fnode], lr, key=clrev)
1326 links[fnode] = min(links[fnode], lr, key=clrev)
1326 elif fnode:
1327 elif fnode:
1327 links[fnode] = lr
1328 links[fnode] = lr
1328 return links
1329 return links
1329
1330
1330 else:
1331 else:
1331 linknodes = normallinknodes
1332 linknodes = normallinknodes
1332
1333
1333 repo = self._repo
1334 repo = self._repo
1334 progress = repo.ui.makeprogress(
1335 progress = repo.ui.makeprogress(
1335 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1336 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1336 )
1337 )
1337 for i, fname in enumerate(sorted(changedfiles)):
1338 for i, fname in enumerate(sorted(changedfiles)):
1338 filerevlog = repo.file(fname)
1339 filerevlog = repo.file(fname)
1339 if not filerevlog:
1340 if not filerevlog:
1340 raise error.Abort(
1341 raise error.Abort(
1341 _(b"empty or missing file data for %s") % fname
1342 _(b"empty or missing file data for %s") % fname
1342 )
1343 )
1343
1344
1344 clrevtolocalrev.clear()
1345 clrevtolocalrev.clear()
1345
1346
1346 linkrevnodes = linknodes(filerevlog, fname)
1347 linkrevnodes = linknodes(filerevlog, fname)
1347 # Lookup for filenodes, we collected the linkrev nodes above in the
1348 # Lookup for filenodes, we collected the linkrev nodes above in the
1348 # fastpath case and with lookupmf in the slowpath case.
1349 # fastpath case and with lookupmf in the slowpath case.
1349 def lookupfilelog(x):
1350 def lookupfilelog(x):
1350 return linkrevnodes[x]
1351 return linkrevnodes[x]
1351
1352
1352 frev, flr = filerevlog.rev, filerevlog.linkrev
1353 frev, flr = filerevlog.rev, filerevlog.linkrev
1353 # Skip sending any filenode we know the client already
1354 # Skip sending any filenode we know the client already
1354 # has. This avoids over-sending files relatively
1355 # has. This avoids over-sending files relatively
1355 # inexpensively, so it's not a problem if we under-filter
1356 # inexpensively, so it's not a problem if we under-filter
1356 # here.
1357 # here.
1357 filenodes = [
1358 filenodes = [
1358 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1359 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1359 ]
1360 ]
1360
1361
1361 if not filenodes:
1362 if not filenodes:
1362 continue
1363 continue
1363
1364
1364 progress.update(i + 1, item=fname)
1365 progress.update(i + 1, item=fname)
1365
1366
1366 deltas = deltagroup(
1367 deltas = deltagroup(
1367 self._repo,
1368 self._repo,
1368 filerevlog,
1369 filerevlog,
1369 filenodes,
1370 filenodes,
1370 False,
1371 False,
1371 lookupfilelog,
1372 lookupfilelog,
1372 self._forcedeltaparentprev,
1373 self._forcedeltaparentprev,
1373 ellipses=self._ellipses,
1374 ellipses=self._ellipses,
1374 clrevtolocalrev=clrevtolocalrev,
1375 clrevtolocalrev=clrevtolocalrev,
1375 fullclnodes=self._fullclnodes,
1376 fullclnodes=self._fullclnodes,
1376 precomputedellipsis=self._precomputedellipsis,
1377 precomputedellipsis=self._precomputedellipsis,
1377 )
1378 )
1378
1379
1379 yield fname, deltas
1380 yield fname, deltas
1380
1381
1381 progress.complete()
1382 progress.complete()
1382
1383
1383
1384
1384 def _makecg1packer(
1385 def _makecg1packer(
1385 repo,
1386 repo,
1386 oldmatcher,
1387 oldmatcher,
1387 matcher,
1388 matcher,
1388 bundlecaps,
1389 bundlecaps,
1389 ellipses=False,
1390 ellipses=False,
1390 shallow=False,
1391 shallow=False,
1391 ellipsisroots=None,
1392 ellipsisroots=None,
1392 fullnodes=None,
1393 fullnodes=None,
1393 ):
1394 ):
1394 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1395 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1395 d.node, d.p1node, d.p2node, d.linknode
1396 d.node, d.p1node, d.p2node, d.linknode
1396 )
1397 )
1397
1398
1398 return cgpacker(
1399 return cgpacker(
1399 repo,
1400 repo,
1400 oldmatcher,
1401 oldmatcher,
1401 matcher,
1402 matcher,
1402 b'01',
1403 b'01',
1403 builddeltaheader=builddeltaheader,
1404 builddeltaheader=builddeltaheader,
1404 manifestsend=b'',
1405 manifestsend=b'',
1405 forcedeltaparentprev=True,
1406 forcedeltaparentprev=True,
1406 bundlecaps=bundlecaps,
1407 bundlecaps=bundlecaps,
1407 ellipses=ellipses,
1408 ellipses=ellipses,
1408 shallow=shallow,
1409 shallow=shallow,
1409 ellipsisroots=ellipsisroots,
1410 ellipsisroots=ellipsisroots,
1410 fullnodes=fullnodes,
1411 fullnodes=fullnodes,
1411 )
1412 )
1412
1413
1413
1414
1414 def _makecg2packer(
1415 def _makecg2packer(
1415 repo,
1416 repo,
1416 oldmatcher,
1417 oldmatcher,
1417 matcher,
1418 matcher,
1418 bundlecaps,
1419 bundlecaps,
1419 ellipses=False,
1420 ellipses=False,
1420 shallow=False,
1421 shallow=False,
1421 ellipsisroots=None,
1422 ellipsisroots=None,
1422 fullnodes=None,
1423 fullnodes=None,
1423 ):
1424 ):
1424 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1425 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1425 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1426 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1426 )
1427 )
1427
1428
1428 return cgpacker(
1429 return cgpacker(
1429 repo,
1430 repo,
1430 oldmatcher,
1431 oldmatcher,
1431 matcher,
1432 matcher,
1432 b'02',
1433 b'02',
1433 builddeltaheader=builddeltaheader,
1434 builddeltaheader=builddeltaheader,
1434 manifestsend=b'',
1435 manifestsend=b'',
1435 bundlecaps=bundlecaps,
1436 bundlecaps=bundlecaps,
1436 ellipses=ellipses,
1437 ellipses=ellipses,
1437 shallow=shallow,
1438 shallow=shallow,
1438 ellipsisroots=ellipsisroots,
1439 ellipsisroots=ellipsisroots,
1439 fullnodes=fullnodes,
1440 fullnodes=fullnodes,
1440 )
1441 )
1441
1442
1442
1443
1443 def _makecg3packer(
1444 def _makecg3packer(
1444 repo,
1445 repo,
1445 oldmatcher,
1446 oldmatcher,
1446 matcher,
1447 matcher,
1447 bundlecaps,
1448 bundlecaps,
1448 ellipses=False,
1449 ellipses=False,
1449 shallow=False,
1450 shallow=False,
1450 ellipsisroots=None,
1451 ellipsisroots=None,
1451 fullnodes=None,
1452 fullnodes=None,
1452 ):
1453 ):
1453 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1454 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1454 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1455 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1455 )
1456 )
1456
1457
1457 return cgpacker(
1458 return cgpacker(
1458 repo,
1459 repo,
1459 oldmatcher,
1460 oldmatcher,
1460 matcher,
1461 matcher,
1461 b'03',
1462 b'03',
1462 builddeltaheader=builddeltaheader,
1463 builddeltaheader=builddeltaheader,
1463 manifestsend=closechunk(),
1464 manifestsend=closechunk(),
1464 bundlecaps=bundlecaps,
1465 bundlecaps=bundlecaps,
1465 ellipses=ellipses,
1466 ellipses=ellipses,
1466 shallow=shallow,
1467 shallow=shallow,
1467 ellipsisroots=ellipsisroots,
1468 ellipsisroots=ellipsisroots,
1468 fullnodes=fullnodes,
1469 fullnodes=fullnodes,
1469 )
1470 )
1470
1471
1471
1472
1472 _packermap = {
1473 _packermap = {
1473 b'01': (_makecg1packer, cg1unpacker),
1474 b'01': (_makecg1packer, cg1unpacker),
1474 # cg2 adds support for exchanging generaldelta
1475 # cg2 adds support for exchanging generaldelta
1475 b'02': (_makecg2packer, cg2unpacker),
1476 b'02': (_makecg2packer, cg2unpacker),
1476 # cg3 adds support for exchanging revlog flags and treemanifests
1477 # cg3 adds support for exchanging revlog flags and treemanifests
1477 b'03': (_makecg3packer, cg3unpacker),
1478 b'03': (_makecg3packer, cg3unpacker),
1478 }
1479 }
1479
1480
1480
1481
1481 def allsupportedversions(repo):
1482 def allsupportedversions(repo):
1482 versions = set(_packermap.keys())
1483 versions = set(_packermap.keys())
1483 needv03 = False
1484 needv03 = False
1484 if (
1485 if (
1485 repo.ui.configbool(b'experimental', b'changegroup3')
1486 repo.ui.configbool(b'experimental', b'changegroup3')
1486 or repo.ui.configbool(b'experimental', b'treemanifest')
1487 or repo.ui.configbool(b'experimental', b'treemanifest')
1487 or scmutil.istreemanifest(repo)
1488 or scmutil.istreemanifest(repo)
1488 ):
1489 ):
1489 # we keep version 03 because we need to to exchange treemanifest data
1490 # we keep version 03 because we need to to exchange treemanifest data
1490 #
1491 #
1491 # we also keep vresion 01 and 02, because it is possible for repo to
1492 # we also keep vresion 01 and 02, because it is possible for repo to
1492 # contains both normal and tree manifest at the same time. so using
1493 # contains both normal and tree manifest at the same time. so using
1493 # older version to pull data is viable
1494 # older version to pull data is viable
1494 #
1495 #
1495 # (or even to push subset of history)
1496 # (or even to push subset of history)
1496 needv03 = True
1497 needv03 = True
1497 if b'exp-sidedata-flag' in repo.requirements:
1498 if b'exp-sidedata-flag' in repo.requirements:
1498 needv03 = True
1499 needv03 = True
1499 # don't attempt to use 01/02 until we do sidedata cleaning
1500 # don't attempt to use 01/02 until we do sidedata cleaning
1500 versions.discard(b'01')
1501 versions.discard(b'01')
1501 versions.discard(b'02')
1502 versions.discard(b'02')
1502 if not needv03:
1503 if not needv03:
1503 versions.discard(b'03')
1504 versions.discard(b'03')
1504 return versions
1505 return versions
1505
1506
1506
1507
1507 # Changegroup versions that can be applied to the repo
1508 # Changegroup versions that can be applied to the repo
1508 def supportedincomingversions(repo):
1509 def supportedincomingversions(repo):
1509 return allsupportedversions(repo)
1510 return allsupportedversions(repo)
1510
1511
1511
1512
1512 # Changegroup versions that can be created from the repo
1513 # Changegroup versions that can be created from the repo
1513 def supportedoutgoingversions(repo):
1514 def supportedoutgoingversions(repo):
1514 versions = allsupportedversions(repo)
1515 versions = allsupportedversions(repo)
1515 if scmutil.istreemanifest(repo):
1516 if scmutil.istreemanifest(repo):
1516 # Versions 01 and 02 support only flat manifests and it's just too
1517 # Versions 01 and 02 support only flat manifests and it's just too
1517 # expensive to convert between the flat manifest and tree manifest on
1518 # expensive to convert between the flat manifest and tree manifest on
1518 # the fly. Since tree manifests are hashed differently, all of history
1519 # the fly. Since tree manifests are hashed differently, all of history
1519 # would have to be converted. Instead, we simply don't even pretend to
1520 # would have to be converted. Instead, we simply don't even pretend to
1520 # support versions 01 and 02.
1521 # support versions 01 and 02.
1521 versions.discard(b'01')
1522 versions.discard(b'01')
1522 versions.discard(b'02')
1523 versions.discard(b'02')
1523 if requirements.NARROW_REQUIREMENT in repo.requirements:
1524 if requirements.NARROW_REQUIREMENT in repo.requirements:
1524 # Versions 01 and 02 don't support revlog flags, and we need to
1525 # Versions 01 and 02 don't support revlog flags, and we need to
1525 # support that for stripping and unbundling to work.
1526 # support that for stripping and unbundling to work.
1526 versions.discard(b'01')
1527 versions.discard(b'01')
1527 versions.discard(b'02')
1528 versions.discard(b'02')
1528 if LFS_REQUIREMENT in repo.requirements:
1529 if LFS_REQUIREMENT in repo.requirements:
1529 # Versions 01 and 02 don't support revlog flags, and we need to
1530 # Versions 01 and 02 don't support revlog flags, and we need to
1530 # mark LFS entries with REVIDX_EXTSTORED.
1531 # mark LFS entries with REVIDX_EXTSTORED.
1531 versions.discard(b'01')
1532 versions.discard(b'01')
1532 versions.discard(b'02')
1533 versions.discard(b'02')
1533
1534
1534 return versions
1535 return versions
1535
1536
1536
1537
1537 def localversion(repo):
1538 def localversion(repo):
1538 # Finds the best version to use for bundles that are meant to be used
1539 # Finds the best version to use for bundles that are meant to be used
1539 # locally, such as those from strip and shelve, and temporary bundles.
1540 # locally, such as those from strip and shelve, and temporary bundles.
1540 return max(supportedoutgoingversions(repo))
1541 return max(supportedoutgoingversions(repo))
1541
1542
1542
1543
1543 def safeversion(repo):
1544 def safeversion(repo):
1544 # Finds the smallest version that it's safe to assume clients of the repo
1545 # Finds the smallest version that it's safe to assume clients of the repo
1545 # will support. For example, all hg versions that support generaldelta also
1546 # will support. For example, all hg versions that support generaldelta also
1546 # support changegroup 02.
1547 # support changegroup 02.
1547 versions = supportedoutgoingversions(repo)
1548 versions = supportedoutgoingversions(repo)
1548 if b'generaldelta' in repo.requirements:
1549 if b'generaldelta' in repo.requirements:
1549 versions.discard(b'01')
1550 versions.discard(b'01')
1550 assert versions
1551 assert versions
1551 return min(versions)
1552 return min(versions)
1552
1553
1553
1554
1554 def getbundler(
1555 def getbundler(
1555 version,
1556 version,
1556 repo,
1557 repo,
1557 bundlecaps=None,
1558 bundlecaps=None,
1558 oldmatcher=None,
1559 oldmatcher=None,
1559 matcher=None,
1560 matcher=None,
1560 ellipses=False,
1561 ellipses=False,
1561 shallow=False,
1562 shallow=False,
1562 ellipsisroots=None,
1563 ellipsisroots=None,
1563 fullnodes=None,
1564 fullnodes=None,
1564 ):
1565 ):
1565 assert version in supportedoutgoingversions(repo)
1566 assert version in supportedoutgoingversions(repo)
1566
1567
1567 if matcher is None:
1568 if matcher is None:
1568 matcher = matchmod.always()
1569 matcher = matchmod.always()
1569 if oldmatcher is None:
1570 if oldmatcher is None:
1570 oldmatcher = matchmod.never()
1571 oldmatcher = matchmod.never()
1571
1572
1572 if version == b'01' and not matcher.always():
1573 if version == b'01' and not matcher.always():
1573 raise error.ProgrammingError(
1574 raise error.ProgrammingError(
1574 b'version 01 changegroups do not support sparse file matchers'
1575 b'version 01 changegroups do not support sparse file matchers'
1575 )
1576 )
1576
1577
1577 if ellipses and version in (b'01', b'02'):
1578 if ellipses and version in (b'01', b'02'):
1578 raise error.Abort(
1579 raise error.Abort(
1579 _(
1580 _(
1580 b'ellipsis nodes require at least cg3 on client and server, '
1581 b'ellipsis nodes require at least cg3 on client and server, '
1581 b'but negotiated version %s'
1582 b'but negotiated version %s'
1582 )
1583 )
1583 % version
1584 % version
1584 )
1585 )
1585
1586
1586 # Requested files could include files not in the local store. So
1587 # Requested files could include files not in the local store. So
1587 # filter those out.
1588 # filter those out.
1588 matcher = repo.narrowmatch(matcher)
1589 matcher = repo.narrowmatch(matcher)
1589
1590
1590 fn = _packermap[version][0]
1591 fn = _packermap[version][0]
1591 return fn(
1592 return fn(
1592 repo,
1593 repo,
1593 oldmatcher,
1594 oldmatcher,
1594 matcher,
1595 matcher,
1595 bundlecaps,
1596 bundlecaps,
1596 ellipses=ellipses,
1597 ellipses=ellipses,
1597 shallow=shallow,
1598 shallow=shallow,
1598 ellipsisroots=ellipsisroots,
1599 ellipsisroots=ellipsisroots,
1599 fullnodes=fullnodes,
1600 fullnodes=fullnodes,
1600 )
1601 )
1601
1602
1602
1603
1603 def getunbundler(version, fh, alg, extras=None):
1604 def getunbundler(version, fh, alg, extras=None):
1604 return _packermap[version][1](fh, alg, extras=extras)
1605 return _packermap[version][1](fh, alg, extras=extras)
1605
1606
1606
1607
1607 def _changegroupinfo(repo, nodes, source):
1608 def _changegroupinfo(repo, nodes, source):
1608 if repo.ui.verbose or source == b'bundle':
1609 if repo.ui.verbose or source == b'bundle':
1609 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1610 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1610 if repo.ui.debugflag:
1611 if repo.ui.debugflag:
1611 repo.ui.debug(b"list of changesets:\n")
1612 repo.ui.debug(b"list of changesets:\n")
1612 for node in nodes:
1613 for node in nodes:
1613 repo.ui.debug(b"%s\n" % hex(node))
1614 repo.ui.debug(b"%s\n" % hex(node))
1614
1615
1615
1616
1616 def makechangegroup(
1617 def makechangegroup(
1617 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1618 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1618 ):
1619 ):
1619 cgstream = makestream(
1620 cgstream = makestream(
1620 repo,
1621 repo,
1621 outgoing,
1622 outgoing,
1622 version,
1623 version,
1623 source,
1624 source,
1624 fastpath=fastpath,
1625 fastpath=fastpath,
1625 bundlecaps=bundlecaps,
1626 bundlecaps=bundlecaps,
1626 )
1627 )
1627 return getunbundler(
1628 return getunbundler(
1628 version,
1629 version,
1629 util.chunkbuffer(cgstream),
1630 util.chunkbuffer(cgstream),
1630 None,
1631 None,
1631 {b'clcount': len(outgoing.missing)},
1632 {b'clcount': len(outgoing.missing)},
1632 )
1633 )
1633
1634
1634
1635
1635 def makestream(
1636 def makestream(
1636 repo,
1637 repo,
1637 outgoing,
1638 outgoing,
1638 version,
1639 version,
1639 source,
1640 source,
1640 fastpath=False,
1641 fastpath=False,
1641 bundlecaps=None,
1642 bundlecaps=None,
1642 matcher=None,
1643 matcher=None,
1643 ):
1644 ):
1644 bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher)
1645 bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher)
1645
1646
1646 repo = repo.unfiltered()
1647 repo = repo.unfiltered()
1647 commonrevs = outgoing.common
1648 commonrevs = outgoing.common
1648 csets = outgoing.missing
1649 csets = outgoing.missing
1649 heads = outgoing.ancestorsof
1650 heads = outgoing.ancestorsof
1650 # We go through the fast path if we get told to, or if all (unfiltered
1651 # We go through the fast path if we get told to, or if all (unfiltered
1651 # heads have been requested (since we then know there all linkrevs will
1652 # heads have been requested (since we then know there all linkrevs will
1652 # be pulled by the client).
1653 # be pulled by the client).
1653 heads.sort()
1654 heads.sort()
1654 fastpathlinkrev = fastpath or (
1655 fastpathlinkrev = fastpath or (
1655 repo.filtername is None and heads == sorted(repo.heads())
1656 repo.filtername is None and heads == sorted(repo.heads())
1656 )
1657 )
1657
1658
1658 repo.hook(b'preoutgoing', throw=True, source=source)
1659 repo.hook(b'preoutgoing', throw=True, source=source)
1659 _changegroupinfo(repo, csets, source)
1660 _changegroupinfo(repo, csets, source)
1660 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1661 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1661
1662
1662
1663
1663 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1664 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1664 revisions = 0
1665 revisions = 0
1665 files = 0
1666 files = 0
1666 progress = repo.ui.makeprogress(
1667 progress = repo.ui.makeprogress(
1667 _(b'files'), unit=_(b'files'), total=expectedfiles
1668 _(b'files'), unit=_(b'files'), total=expectedfiles
1668 )
1669 )
1669 for chunkdata in iter(source.filelogheader, {}):
1670 for chunkdata in iter(source.filelogheader, {}):
1670 files += 1
1671 files += 1
1671 f = chunkdata[b"filename"]
1672 f = chunkdata[b"filename"]
1672 repo.ui.debug(b"adding %s revisions\n" % f)
1673 repo.ui.debug(b"adding %s revisions\n" % f)
1673 progress.increment()
1674 progress.increment()
1674 fl = repo.file(f)
1675 fl = repo.file(f)
1675 o = len(fl)
1676 o = len(fl)
1676 try:
1677 try:
1677 deltas = source.deltaiter()
1678 deltas = source.deltaiter()
1678 if not fl.addgroup(deltas, revmap, trp):
1679 if not fl.addgroup(deltas, revmap, trp):
1679 raise error.Abort(_(b"received file revlog group is empty"))
1680 raise error.Abort(_(b"received file revlog group is empty"))
1680 except error.CensoredBaseError as e:
1681 except error.CensoredBaseError as e:
1681 raise error.Abort(_(b"received delta base is censored: %s") % e)
1682 raise error.Abort(_(b"received delta base is censored: %s") % e)
1682 revisions += len(fl) - o
1683 revisions += len(fl) - o
1683 if f in needfiles:
1684 if f in needfiles:
1684 needs = needfiles[f]
1685 needs = needfiles[f]
1685 for new in pycompat.xrange(o, len(fl)):
1686 for new in pycompat.xrange(o, len(fl)):
1686 n = fl.node(new)
1687 n = fl.node(new)
1687 if n in needs:
1688 if n in needs:
1688 needs.remove(n)
1689 needs.remove(n)
1689 else:
1690 else:
1690 raise error.Abort(_(b"received spurious file revlog entry"))
1691 raise error.Abort(_(b"received spurious file revlog entry"))
1691 if not needs:
1692 if not needs:
1692 del needfiles[f]
1693 del needfiles[f]
1693 progress.complete()
1694 progress.complete()
1694
1695
1695 for f, needs in pycompat.iteritems(needfiles):
1696 for f, needs in pycompat.iteritems(needfiles):
1696 fl = repo.file(f)
1697 fl = repo.file(f)
1697 for n in needs:
1698 for n in needs:
1698 try:
1699 try:
1699 fl.rev(n)
1700 fl.rev(n)
1700 except error.LookupError:
1701 except error.LookupError:
1701 raise error.Abort(
1702 raise error.Abort(
1702 _(b'missing file data for %s:%s - run hg verify')
1703 _(b'missing file data for %s:%s - run hg verify')
1703 % (f, hex(n))
1704 % (f, hex(n))
1704 )
1705 )
1705
1706
1706 return revisions, files
1707 return revisions, files
@@ -1,798 +1,799 b''
1 # exchangev2.py - repository exchange for wire protocol version 2
1 # exchangev2.py - repository exchange for wire protocol version 2
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 collections
10 import collections
11 import weakref
11 import weakref
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 nullid,
15 nullid,
16 short,
16 short,
17 )
17 )
18 from . import (
18 from . import (
19 bookmarks,
19 bookmarks,
20 error,
20 error,
21 mdiff,
21 mdiff,
22 narrowspec,
22 narrowspec,
23 phases,
23 phases,
24 pycompat,
24 pycompat,
25 setdiscovery,
25 setdiscovery,
26 )
26 )
27 from .interfaces import repository
27 from .interfaces import repository
28
28
29
29
30 def pull(pullop):
30 def pull(pullop):
31 """Pull using wire protocol version 2."""
31 """Pull using wire protocol version 2."""
32 repo = pullop.repo
32 repo = pullop.repo
33 remote = pullop.remote
33 remote = pullop.remote
34
34
35 usingrawchangelogandmanifest = _checkuserawstorefiledata(pullop)
35 usingrawchangelogandmanifest = _checkuserawstorefiledata(pullop)
36
36
37 # If this is a clone and it was requested to perform a "stream clone",
37 # If this is a clone and it was requested to perform a "stream clone",
38 # we obtain the raw files data from the remote then fall back to an
38 # we obtain the raw files data from the remote then fall back to an
39 # incremental pull. This is somewhat hacky and is not nearly robust enough
39 # incremental pull. This is somewhat hacky and is not nearly robust enough
40 # for long-term usage.
40 # for long-term usage.
41 if usingrawchangelogandmanifest:
41 if usingrawchangelogandmanifest:
42 with repo.transaction(b'clone'):
42 with repo.transaction(b'clone'):
43 _fetchrawstorefiles(repo, remote)
43 _fetchrawstorefiles(repo, remote)
44 repo.invalidate(clearfilecache=True)
44 repo.invalidate(clearfilecache=True)
45
45
46 tr = pullop.trmanager.transaction()
46 tr = pullop.trmanager.transaction()
47
47
48 # We don't use the repo's narrow matcher here because the patterns passed
48 # We don't use the repo's narrow matcher here because the patterns passed
49 # to exchange.pull() could be different.
49 # to exchange.pull() could be different.
50 narrowmatcher = narrowspec.match(
50 narrowmatcher = narrowspec.match(
51 repo.root,
51 repo.root,
52 # Empty maps to nevermatcher. So always
52 # Empty maps to nevermatcher. So always
53 # set includes if missing.
53 # set includes if missing.
54 pullop.includepats or {b'path:.'},
54 pullop.includepats or {b'path:.'},
55 pullop.excludepats,
55 pullop.excludepats,
56 )
56 )
57
57
58 if pullop.includepats or pullop.excludepats:
58 if pullop.includepats or pullop.excludepats:
59 pathfilter = {}
59 pathfilter = {}
60 if pullop.includepats:
60 if pullop.includepats:
61 pathfilter[b'include'] = sorted(pullop.includepats)
61 pathfilter[b'include'] = sorted(pullop.includepats)
62 if pullop.excludepats:
62 if pullop.excludepats:
63 pathfilter[b'exclude'] = sorted(pullop.excludepats)
63 pathfilter[b'exclude'] = sorted(pullop.excludepats)
64 else:
64 else:
65 pathfilter = None
65 pathfilter = None
66
66
67 # Figure out what needs to be fetched.
67 # Figure out what needs to be fetched.
68 common, fetch, remoteheads = _pullchangesetdiscovery(
68 common, fetch, remoteheads = _pullchangesetdiscovery(
69 repo, remote, pullop.heads, abortwhenunrelated=pullop.force
69 repo, remote, pullop.heads, abortwhenunrelated=pullop.force
70 )
70 )
71
71
72 # And fetch the data.
72 # And fetch the data.
73 pullheads = pullop.heads or remoteheads
73 pullheads = pullop.heads or remoteheads
74 csetres = _fetchchangesets(repo, tr, remote, common, fetch, pullheads)
74 csetres = _fetchchangesets(repo, tr, remote, common, fetch, pullheads)
75
75
76 # New revisions are written to the changelog. But all other updates
76 # New revisions are written to the changelog. But all other updates
77 # are deferred. Do those now.
77 # are deferred. Do those now.
78
78
79 # Ensure all new changesets are draft by default. If the repo is
79 # Ensure all new changesets are draft by default. If the repo is
80 # publishing, the phase will be adjusted by the loop below.
80 # publishing, the phase will be adjusted by the loop below.
81 if csetres[b'added']:
81 if csetres[b'added']:
82 phases.registernew(
82 phases.registernew(
83 repo, tr, phases.draft, [repo[n].rev() for n in csetres[b'added']]
83 repo, tr, phases.draft, [repo[n].rev() for n in csetres[b'added']]
84 )
84 )
85
85
86 # And adjust the phase of all changesets accordingly.
86 # And adjust the phase of all changesets accordingly.
87 for phasenumber, phase in phases.phasenames.items():
87 for phasenumber, phase in phases.phasenames.items():
88 if phase == b'secret' or not csetres[b'nodesbyphase'][phase]:
88 if phase == b'secret' or not csetres[b'nodesbyphase'][phase]:
89 continue
89 continue
90
90
91 phases.advanceboundary(
91 phases.advanceboundary(
92 repo,
92 repo,
93 tr,
93 tr,
94 phasenumber,
94 phasenumber,
95 csetres[b'nodesbyphase'][phase],
95 csetres[b'nodesbyphase'][phase],
96 )
96 )
97
97
98 # Write bookmark updates.
98 # Write bookmark updates.
99 bookmarks.updatefromremote(
99 bookmarks.updatefromremote(
100 repo.ui,
100 repo.ui,
101 repo,
101 repo,
102 csetres[b'bookmarks'],
102 csetres[b'bookmarks'],
103 remote.url(),
103 remote.url(),
104 pullop.gettransaction,
104 pullop.gettransaction,
105 explicit=pullop.explicitbookmarks,
105 explicit=pullop.explicitbookmarks,
106 )
106 )
107
107
108 manres = _fetchmanifests(repo, tr, remote, csetres[b'manifestnodes'])
108 manres = _fetchmanifests(repo, tr, remote, csetres[b'manifestnodes'])
109
109
110 # We don't properly support shallow changeset and manifest yet. So we apply
110 # We don't properly support shallow changeset and manifest yet. So we apply
111 # depth limiting locally.
111 # depth limiting locally.
112 if pullop.depth:
112 if pullop.depth:
113 relevantcsetnodes = set()
113 relevantcsetnodes = set()
114 clnode = repo.changelog.node
114 clnode = repo.changelog.node
115
115
116 for rev in repo.revs(
116 for rev in repo.revs(
117 b'ancestors(%ln, %s)', pullheads, pullop.depth - 1
117 b'ancestors(%ln, %s)', pullheads, pullop.depth - 1
118 ):
118 ):
119 relevantcsetnodes.add(clnode(rev))
119 relevantcsetnodes.add(clnode(rev))
120
120
121 csetrelevantfilter = lambda n: n in relevantcsetnodes
121 csetrelevantfilter = lambda n: n in relevantcsetnodes
122
122
123 else:
123 else:
124 csetrelevantfilter = lambda n: True
124 csetrelevantfilter = lambda n: True
125
125
126 # If obtaining the raw store files, we need to scan the full repo to
126 # If obtaining the raw store files, we need to scan the full repo to
127 # derive all the changesets, manifests, and linkrevs.
127 # derive all the changesets, manifests, and linkrevs.
128 if usingrawchangelogandmanifest:
128 if usingrawchangelogandmanifest:
129 csetsforfiles = []
129 csetsforfiles = []
130 mnodesforfiles = []
130 mnodesforfiles = []
131 manifestlinkrevs = {}
131 manifestlinkrevs = {}
132
132
133 for rev in repo:
133 for rev in repo:
134 ctx = repo[rev]
134 ctx = repo[rev]
135 node = ctx.node()
135 node = ctx.node()
136
136
137 if not csetrelevantfilter(node):
137 if not csetrelevantfilter(node):
138 continue
138 continue
139
139
140 mnode = ctx.manifestnode()
140 mnode = ctx.manifestnode()
141
141
142 csetsforfiles.append(node)
142 csetsforfiles.append(node)
143 mnodesforfiles.append(mnode)
143 mnodesforfiles.append(mnode)
144 manifestlinkrevs[mnode] = rev
144 manifestlinkrevs[mnode] = rev
145
145
146 else:
146 else:
147 csetsforfiles = [n for n in csetres[b'added'] if csetrelevantfilter(n)]
147 csetsforfiles = [n for n in csetres[b'added'] if csetrelevantfilter(n)]
148 mnodesforfiles = manres[b'added']
148 mnodesforfiles = manres[b'added']
149 manifestlinkrevs = manres[b'linkrevs']
149 manifestlinkrevs = manres[b'linkrevs']
150
150
151 # Find all file nodes referenced by added manifests and fetch those
151 # Find all file nodes referenced by added manifests and fetch those
152 # revisions.
152 # revisions.
153 fnodes = _derivefilesfrommanifests(repo, narrowmatcher, mnodesforfiles)
153 fnodes = _derivefilesfrommanifests(repo, narrowmatcher, mnodesforfiles)
154 _fetchfilesfromcsets(
154 _fetchfilesfromcsets(
155 repo,
155 repo,
156 tr,
156 tr,
157 remote,
157 remote,
158 pathfilter,
158 pathfilter,
159 fnodes,
159 fnodes,
160 csetsforfiles,
160 csetsforfiles,
161 manifestlinkrevs,
161 manifestlinkrevs,
162 shallow=bool(pullop.depth),
162 shallow=bool(pullop.depth),
163 )
163 )
164
164
165
165
166 def _checkuserawstorefiledata(pullop):
166 def _checkuserawstorefiledata(pullop):
167 """Check whether we should use rawstorefiledata command to retrieve data."""
167 """Check whether we should use rawstorefiledata command to retrieve data."""
168
168
169 repo = pullop.repo
169 repo = pullop.repo
170 remote = pullop.remote
170 remote = pullop.remote
171
171
172 # Command to obtain raw store data isn't available.
172 # Command to obtain raw store data isn't available.
173 if b'rawstorefiledata' not in remote.apidescriptor[b'commands']:
173 if b'rawstorefiledata' not in remote.apidescriptor[b'commands']:
174 return False
174 return False
175
175
176 # Only honor if user requested stream clone operation.
176 # Only honor if user requested stream clone operation.
177 if not pullop.streamclonerequested:
177 if not pullop.streamclonerequested:
178 return False
178 return False
179
179
180 # Only works on empty repos.
180 # Only works on empty repos.
181 if len(repo):
181 if len(repo):
182 return False
182 return False
183
183
184 # TODO This is super hacky. There needs to be a storage API for this. We
184 # TODO This is super hacky. There needs to be a storage API for this. We
185 # also need to check for compatibility with the remote.
185 # also need to check for compatibility with the remote.
186 if b'revlogv1' not in repo.requirements:
186 if b'revlogv1' not in repo.requirements:
187 return False
187 return False
188
188
189 return True
189 return True
190
190
191
191
192 def _fetchrawstorefiles(repo, remote):
192 def _fetchrawstorefiles(repo, remote):
193 with remote.commandexecutor() as e:
193 with remote.commandexecutor() as e:
194 objs = e.callcommand(
194 objs = e.callcommand(
195 b'rawstorefiledata',
195 b'rawstorefiledata',
196 {
196 {
197 b'files': [b'changelog', b'manifestlog'],
197 b'files': [b'changelog', b'manifestlog'],
198 },
198 },
199 ).result()
199 ).result()
200
200
201 # First object is a summary of files data that follows.
201 # First object is a summary of files data that follows.
202 overall = next(objs)
202 overall = next(objs)
203
203
204 progress = repo.ui.makeprogress(
204 progress = repo.ui.makeprogress(
205 _(b'clone'), total=overall[b'totalsize'], unit=_(b'bytes')
205 _(b'clone'), total=overall[b'totalsize'], unit=_(b'bytes')
206 )
206 )
207 with progress:
207 with progress:
208 progress.update(0)
208 progress.update(0)
209
209
210 # Next are pairs of file metadata, data.
210 # Next are pairs of file metadata, data.
211 while True:
211 while True:
212 try:
212 try:
213 filemeta = next(objs)
213 filemeta = next(objs)
214 except StopIteration:
214 except StopIteration:
215 break
215 break
216
216
217 for k in (b'location', b'path', b'size'):
217 for k in (b'location', b'path', b'size'):
218 if k not in filemeta:
218 if k not in filemeta:
219 raise error.Abort(
219 raise error.Abort(
220 _(b'remote file data missing key: %s') % k
220 _(b'remote file data missing key: %s') % k
221 )
221 )
222
222
223 if filemeta[b'location'] == b'store':
223 if filemeta[b'location'] == b'store':
224 vfs = repo.svfs
224 vfs = repo.svfs
225 else:
225 else:
226 raise error.Abort(
226 raise error.Abort(
227 _(b'invalid location for raw file data: %s')
227 _(b'invalid location for raw file data: %s')
228 % filemeta[b'location']
228 % filemeta[b'location']
229 )
229 )
230
230
231 bytesremaining = filemeta[b'size']
231 bytesremaining = filemeta[b'size']
232
232
233 with vfs.open(filemeta[b'path'], b'wb') as fh:
233 with vfs.open(filemeta[b'path'], b'wb') as fh:
234 while True:
234 while True:
235 try:
235 try:
236 chunk = next(objs)
236 chunk = next(objs)
237 except StopIteration:
237 except StopIteration:
238 break
238 break
239
239
240 bytesremaining -= len(chunk)
240 bytesremaining -= len(chunk)
241
241
242 if bytesremaining < 0:
242 if bytesremaining < 0:
243 raise error.Abort(
243 raise error.Abort(
244 _(
244 _(
245 b'received invalid number of bytes for file '
245 b'received invalid number of bytes for file '
246 b'data; expected %d, got extra'
246 b'data; expected %d, got extra'
247 )
247 )
248 % filemeta[b'size']
248 % filemeta[b'size']
249 )
249 )
250
250
251 progress.increment(step=len(chunk))
251 progress.increment(step=len(chunk))
252 fh.write(chunk)
252 fh.write(chunk)
253
253
254 try:
254 try:
255 if chunk.islast:
255 if chunk.islast:
256 break
256 break
257 except AttributeError:
257 except AttributeError:
258 raise error.Abort(
258 raise error.Abort(
259 _(
259 _(
260 b'did not receive indefinite length bytestring '
260 b'did not receive indefinite length bytestring '
261 b'for file data'
261 b'for file data'
262 )
262 )
263 )
263 )
264
264
265 if bytesremaining:
265 if bytesremaining:
266 raise error.Abort(
266 raise error.Abort(
267 _(
267 _(
268 b'received invalid number of bytes for'
268 b'received invalid number of bytes for'
269 b'file data; expected %d got %d'
269 b'file data; expected %d got %d'
270 )
270 )
271 % (
271 % (
272 filemeta[b'size'],
272 filemeta[b'size'],
273 filemeta[b'size'] - bytesremaining,
273 filemeta[b'size'] - bytesremaining,
274 )
274 )
275 )
275 )
276
276
277
277
278 def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):
278 def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):
279 """Determine which changesets need to be pulled."""
279 """Determine which changesets need to be pulled."""
280
280
281 if heads:
281 if heads:
282 knownnode = repo.changelog.hasnode
282 knownnode = repo.changelog.hasnode
283 if all(knownnode(head) for head in heads):
283 if all(knownnode(head) for head in heads):
284 return heads, False, heads
284 return heads, False, heads
285
285
286 # TODO wire protocol version 2 is capable of more efficient discovery
286 # TODO wire protocol version 2 is capable of more efficient discovery
287 # than setdiscovery. Consider implementing something better.
287 # than setdiscovery. Consider implementing something better.
288 common, fetch, remoteheads = setdiscovery.findcommonheads(
288 common, fetch, remoteheads = setdiscovery.findcommonheads(
289 repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated
289 repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated
290 )
290 )
291
291
292 common = set(common)
292 common = set(common)
293 remoteheads = set(remoteheads)
293 remoteheads = set(remoteheads)
294
294
295 # If a remote head is filtered locally, put it back in the common set.
295 # If a remote head is filtered locally, put it back in the common set.
296 # See the comment in exchange._pulldiscoverychangegroup() for more.
296 # See the comment in exchange._pulldiscoverychangegroup() for more.
297
297
298 if fetch and remoteheads:
298 if fetch and remoteheads:
299 has_node = repo.unfiltered().changelog.index.has_node
299 has_node = repo.unfiltered().changelog.index.has_node
300
300
301 common |= {head for head in remoteheads if has_node(head)}
301 common |= {head for head in remoteheads if has_node(head)}
302
302
303 if set(remoteheads).issubset(common):
303 if set(remoteheads).issubset(common):
304 fetch = []
304 fetch = []
305
305
306 common.discard(nullid)
306 common.discard(nullid)
307
307
308 return common, fetch, remoteheads
308 return common, fetch, remoteheads
309
309
310
310
311 def _fetchchangesets(repo, tr, remote, common, fetch, remoteheads):
311 def _fetchchangesets(repo, tr, remote, common, fetch, remoteheads):
312 # TODO consider adding a step here where we obtain the DAG shape first
312 # TODO consider adding a step here where we obtain the DAG shape first
313 # (or ask the server to slice changesets into chunks for us) so that
313 # (or ask the server to slice changesets into chunks for us) so that
314 # we can perform multiple fetches in batches. This will facilitate
314 # we can perform multiple fetches in batches. This will facilitate
315 # resuming interrupted clones, higher server-side cache hit rates due
315 # resuming interrupted clones, higher server-side cache hit rates due
316 # to smaller segments, etc.
316 # to smaller segments, etc.
317 with remote.commandexecutor() as e:
317 with remote.commandexecutor() as e:
318 objs = e.callcommand(
318 objs = e.callcommand(
319 b'changesetdata',
319 b'changesetdata',
320 {
320 {
321 b'revisions': [
321 b'revisions': [
322 {
322 {
323 b'type': b'changesetdagrange',
323 b'type': b'changesetdagrange',
324 b'roots': sorted(common),
324 b'roots': sorted(common),
325 b'heads': sorted(remoteheads),
325 b'heads': sorted(remoteheads),
326 }
326 }
327 ],
327 ],
328 b'fields': {b'bookmarks', b'parents', b'phase', b'revision'},
328 b'fields': {b'bookmarks', b'parents', b'phase', b'revision'},
329 },
329 },
330 ).result()
330 ).result()
331
331
332 # The context manager waits on all response data when exiting. So
332 # The context manager waits on all response data when exiting. So
333 # we need to remain in the context manager in order to stream data.
333 # we need to remain in the context manager in order to stream data.
334 return _processchangesetdata(repo, tr, objs)
334 return _processchangesetdata(repo, tr, objs)
335
335
336
336
337 def _processchangesetdata(repo, tr, objs):
337 def _processchangesetdata(repo, tr, objs):
338 repo.hook(b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs))
338 repo.hook(b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs))
339
339
340 urepo = repo.unfiltered()
340 urepo = repo.unfiltered()
341 cl = urepo.changelog
341 cl = urepo.changelog
342
342
343 cl.delayupdate(tr)
343 cl.delayupdate(tr)
344
344
345 # The first emitted object is a header describing the data that
345 # The first emitted object is a header describing the data that
346 # follows.
346 # follows.
347 meta = next(objs)
347 meta = next(objs)
348
348
349 progress = repo.ui.makeprogress(
349 progress = repo.ui.makeprogress(
350 _(b'changesets'), unit=_(b'chunks'), total=meta.get(b'totalitems')
350 _(b'changesets'), unit=_(b'chunks'), total=meta.get(b'totalitems')
351 )
351 )
352
352
353 manifestnodes = {}
353 manifestnodes = {}
354 added = []
354 added = []
355
355
356 def linkrev(node):
356 def linkrev(node):
357 repo.ui.debug(b'add changeset %s\n' % short(node))
357 repo.ui.debug(b'add changeset %s\n' % short(node))
358 # Linkrev for changelog is always self.
358 # Linkrev for changelog is always self.
359 return len(cl)
359 return len(cl)
360
360
361 def ondupchangeset(cl, node):
361 def ondupchangeset(cl, node):
362 added.append(node)
362 added.append(node)
363
363
364 def onchangeset(cl, node):
364 def onchangeset(cl, node):
365 progress.increment()
365 progress.increment()
366
366
367 rev = cl.rev(node)
367 rev = cl.rev(node)
368 revision = cl.changelogrevision(rev)
368 revision = cl.changelogrevision(rev)
369 added.append(node)
369 added.append(node)
370
370
371 # We need to preserve the mapping of changelog revision to node
371 # We need to preserve the mapping of changelog revision to node
372 # so we can set the linkrev accordingly when manifests are added.
372 # so we can set the linkrev accordingly when manifests are added.
373 manifestnodes[rev] = revision.manifest
373 manifestnodes[rev] = revision.manifest
374
374
375 repo.register_changeset(rev, revision)
375 repo.register_changeset(rev, revision)
376
376
377 nodesbyphase = {phase: set() for phase in phases.phasenames.values()}
377 nodesbyphase = {phase: set() for phase in phases.phasenames.values()}
378 remotebookmarks = {}
378 remotebookmarks = {}
379
379
380 # addgroup() expects a 7-tuple describing revisions. This normalizes
380 # addgroup() expects a 7-tuple describing revisions. This normalizes
381 # the wire data to that format.
381 # the wire data to that format.
382 #
382 #
383 # This loop also aggregates non-revision metadata, such as phase
383 # This loop also aggregates non-revision metadata, such as phase
384 # data.
384 # data.
385 def iterrevisions():
385 def iterrevisions():
386 for cset in objs:
386 for cset in objs:
387 node = cset[b'node']
387 node = cset[b'node']
388
388
389 if b'phase' in cset:
389 if b'phase' in cset:
390 nodesbyphase[cset[b'phase']].add(node)
390 nodesbyphase[cset[b'phase']].add(node)
391
391
392 for mark in cset.get(b'bookmarks', []):
392 for mark in cset.get(b'bookmarks', []):
393 remotebookmarks[mark] = node
393 remotebookmarks[mark] = node
394
394
395 # TODO add mechanism for extensions to examine records so they
395 # TODO add mechanism for extensions to examine records so they
396 # can siphon off custom data fields.
396 # can siphon off custom data fields.
397
397
398 extrafields = {}
398 extrafields = {}
399
399
400 for field, size in cset.get(b'fieldsfollowing', []):
400 for field, size in cset.get(b'fieldsfollowing', []):
401 extrafields[field] = next(objs)
401 extrafields[field] = next(objs)
402
402
403 # Some entries might only be metadata only updates.
403 # Some entries might only be metadata only updates.
404 if b'revision' not in extrafields:
404 if b'revision' not in extrafields:
405 continue
405 continue
406
406
407 data = extrafields[b'revision']
407 data = extrafields[b'revision']
408
408
409 yield (
409 yield (
410 node,
410 node,
411 cset[b'parents'][0],
411 cset[b'parents'][0],
412 cset[b'parents'][1],
412 cset[b'parents'][1],
413 # Linknode is always itself for changesets.
413 # Linknode is always itself for changesets.
414 cset[b'node'],
414 cset[b'node'],
415 # We always send full revisions. So delta base is not set.
415 # We always send full revisions. So delta base is not set.
416 nullid,
416 nullid,
417 mdiff.trivialdiffheader(len(data)) + data,
417 mdiff.trivialdiffheader(len(data)) + data,
418 # Flags not yet supported.
418 # Flags not yet supported.
419 0,
419 0,
420 )
420 )
421
421
422 cl.addgroup(
422 cl.addgroup(
423 iterrevisions(),
423 iterrevisions(),
424 linkrev,
424 linkrev,
425 weakref.proxy(tr),
425 weakref.proxy(tr),
426 alwayscache=True,
426 addrevisioncb=onchangeset,
427 addrevisioncb=onchangeset,
427 duplicaterevisioncb=ondupchangeset,
428 duplicaterevisioncb=ondupchangeset,
428 )
429 )
429
430
430 progress.complete()
431 progress.complete()
431
432
432 return {
433 return {
433 b'added': added,
434 b'added': added,
434 b'nodesbyphase': nodesbyphase,
435 b'nodesbyphase': nodesbyphase,
435 b'bookmarks': remotebookmarks,
436 b'bookmarks': remotebookmarks,
436 b'manifestnodes': manifestnodes,
437 b'manifestnodes': manifestnodes,
437 }
438 }
438
439
439
440
440 def _fetchmanifests(repo, tr, remote, manifestnodes):
441 def _fetchmanifests(repo, tr, remote, manifestnodes):
441 rootmanifest = repo.manifestlog.getstorage(b'')
442 rootmanifest = repo.manifestlog.getstorage(b'')
442
443
443 # Some manifests can be shared between changesets. Filter out revisions
444 # Some manifests can be shared between changesets. Filter out revisions
444 # we already know about.
445 # we already know about.
445 fetchnodes = []
446 fetchnodes = []
446 linkrevs = {}
447 linkrevs = {}
447 seen = set()
448 seen = set()
448
449
449 for clrev, node in sorted(pycompat.iteritems(manifestnodes)):
450 for clrev, node in sorted(pycompat.iteritems(manifestnodes)):
450 if node in seen:
451 if node in seen:
451 continue
452 continue
452
453
453 try:
454 try:
454 rootmanifest.rev(node)
455 rootmanifest.rev(node)
455 except error.LookupError:
456 except error.LookupError:
456 fetchnodes.append(node)
457 fetchnodes.append(node)
457 linkrevs[node] = clrev
458 linkrevs[node] = clrev
458
459
459 seen.add(node)
460 seen.add(node)
460
461
461 # TODO handle tree manifests
462 # TODO handle tree manifests
462
463
463 # addgroup() expects 7-tuple describing revisions. This normalizes
464 # addgroup() expects 7-tuple describing revisions. This normalizes
464 # the wire data to that format.
465 # the wire data to that format.
465 def iterrevisions(objs, progress):
466 def iterrevisions(objs, progress):
466 for manifest in objs:
467 for manifest in objs:
467 node = manifest[b'node']
468 node = manifest[b'node']
468
469
469 extrafields = {}
470 extrafields = {}
470
471
471 for field, size in manifest.get(b'fieldsfollowing', []):
472 for field, size in manifest.get(b'fieldsfollowing', []):
472 extrafields[field] = next(objs)
473 extrafields[field] = next(objs)
473
474
474 if b'delta' in extrafields:
475 if b'delta' in extrafields:
475 basenode = manifest[b'deltabasenode']
476 basenode = manifest[b'deltabasenode']
476 delta = extrafields[b'delta']
477 delta = extrafields[b'delta']
477 elif b'revision' in extrafields:
478 elif b'revision' in extrafields:
478 basenode = nullid
479 basenode = nullid
479 revision = extrafields[b'revision']
480 revision = extrafields[b'revision']
480 delta = mdiff.trivialdiffheader(len(revision)) + revision
481 delta = mdiff.trivialdiffheader(len(revision)) + revision
481 else:
482 else:
482 continue
483 continue
483
484
484 yield (
485 yield (
485 node,
486 node,
486 manifest[b'parents'][0],
487 manifest[b'parents'][0],
487 manifest[b'parents'][1],
488 manifest[b'parents'][1],
488 # The value passed in is passed to the lookup function passed
489 # The value passed in is passed to the lookup function passed
489 # to addgroup(). We already have a map of manifest node to
490 # to addgroup(). We already have a map of manifest node to
490 # changelog revision number. So we just pass in the
491 # changelog revision number. So we just pass in the
491 # manifest node here and use linkrevs.__getitem__ as the
492 # manifest node here and use linkrevs.__getitem__ as the
492 # resolution function.
493 # resolution function.
493 node,
494 node,
494 basenode,
495 basenode,
495 delta,
496 delta,
496 # Flags not yet supported.
497 # Flags not yet supported.
497 0,
498 0,
498 )
499 )
499
500
500 progress.increment()
501 progress.increment()
501
502
502 progress = repo.ui.makeprogress(
503 progress = repo.ui.makeprogress(
503 _(b'manifests'), unit=_(b'chunks'), total=len(fetchnodes)
504 _(b'manifests'), unit=_(b'chunks'), total=len(fetchnodes)
504 )
505 )
505
506
506 commandmeta = remote.apidescriptor[b'commands'][b'manifestdata']
507 commandmeta = remote.apidescriptor[b'commands'][b'manifestdata']
507 batchsize = commandmeta.get(b'recommendedbatchsize', 10000)
508 batchsize = commandmeta.get(b'recommendedbatchsize', 10000)
508 # TODO make size configurable on client?
509 # TODO make size configurable on client?
509
510
510 # We send commands 1 at a time to the remote. This is not the most
511 # We send commands 1 at a time to the remote. This is not the most
511 # efficient because we incur a round trip at the end of each batch.
512 # efficient because we incur a round trip at the end of each batch.
512 # However, the existing frame-based reactor keeps consuming server
513 # However, the existing frame-based reactor keeps consuming server
513 # data in the background. And this results in response data buffering
514 # data in the background. And this results in response data buffering
514 # in memory. This can consume gigabytes of memory.
515 # in memory. This can consume gigabytes of memory.
515 # TODO send multiple commands in a request once background buffering
516 # TODO send multiple commands in a request once background buffering
516 # issues are resolved.
517 # issues are resolved.
517
518
518 added = []
519 added = []
519
520
520 for i in pycompat.xrange(0, len(fetchnodes), batchsize):
521 for i in pycompat.xrange(0, len(fetchnodes), batchsize):
521 batch = [node for node in fetchnodes[i : i + batchsize]]
522 batch = [node for node in fetchnodes[i : i + batchsize]]
522 if not batch:
523 if not batch:
523 continue
524 continue
524
525
525 with remote.commandexecutor() as e:
526 with remote.commandexecutor() as e:
526 objs = e.callcommand(
527 objs = e.callcommand(
527 b'manifestdata',
528 b'manifestdata',
528 {
529 {
529 b'tree': b'',
530 b'tree': b'',
530 b'nodes': batch,
531 b'nodes': batch,
531 b'fields': {b'parents', b'revision'},
532 b'fields': {b'parents', b'revision'},
532 b'haveparents': True,
533 b'haveparents': True,
533 },
534 },
534 ).result()
535 ).result()
535
536
536 # Chomp off header object.
537 # Chomp off header object.
537 next(objs)
538 next(objs)
538
539
539 def onchangeset(cl, node):
540 def onchangeset(cl, node):
540 added.append(node)
541 added.append(node)
541
542
542 rootmanifest.addgroup(
543 rootmanifest.addgroup(
543 iterrevisions(objs, progress),
544 iterrevisions(objs, progress),
544 linkrevs.__getitem__,
545 linkrevs.__getitem__,
545 weakref.proxy(tr),
546 weakref.proxy(tr),
546 addrevisioncb=onchangeset,
547 addrevisioncb=onchangeset,
547 duplicaterevisioncb=onchangeset,
548 duplicaterevisioncb=onchangeset,
548 )
549 )
549
550
550 progress.complete()
551 progress.complete()
551
552
552 return {
553 return {
553 b'added': added,
554 b'added': added,
554 b'linkrevs': linkrevs,
555 b'linkrevs': linkrevs,
555 }
556 }
556
557
557
558
558 def _derivefilesfrommanifests(repo, matcher, manifestnodes):
559 def _derivefilesfrommanifests(repo, matcher, manifestnodes):
559 """Determine what file nodes are relevant given a set of manifest nodes.
560 """Determine what file nodes are relevant given a set of manifest nodes.
560
561
561 Returns a dict mapping file paths to dicts of file node to first manifest
562 Returns a dict mapping file paths to dicts of file node to first manifest
562 node.
563 node.
563 """
564 """
564 ml = repo.manifestlog
565 ml = repo.manifestlog
565 fnodes = collections.defaultdict(dict)
566 fnodes = collections.defaultdict(dict)
566
567
567 progress = repo.ui.makeprogress(
568 progress = repo.ui.makeprogress(
568 _(b'scanning manifests'), total=len(manifestnodes)
569 _(b'scanning manifests'), total=len(manifestnodes)
569 )
570 )
570
571
571 with progress:
572 with progress:
572 for manifestnode in manifestnodes:
573 for manifestnode in manifestnodes:
573 m = ml.get(b'', manifestnode)
574 m = ml.get(b'', manifestnode)
574
575
575 # TODO this will pull in unwanted nodes because it takes the storage
576 # TODO this will pull in unwanted nodes because it takes the storage
576 # delta into consideration. What we really want is something that
577 # delta into consideration. What we really want is something that
577 # takes the delta between the manifest's parents. And ideally we
578 # takes the delta between the manifest's parents. And ideally we
578 # would ignore file nodes that are known locally. For now, ignore
579 # would ignore file nodes that are known locally. For now, ignore
579 # both these limitations. This will result in incremental fetches
580 # both these limitations. This will result in incremental fetches
580 # requesting data we already have. So this is far from ideal.
581 # requesting data we already have. So this is far from ideal.
581 md = m.readfast()
582 md = m.readfast()
582
583
583 for path, fnode in md.items():
584 for path, fnode in md.items():
584 if matcher(path):
585 if matcher(path):
585 fnodes[path].setdefault(fnode, manifestnode)
586 fnodes[path].setdefault(fnode, manifestnode)
586
587
587 progress.increment()
588 progress.increment()
588
589
589 return fnodes
590 return fnodes
590
591
591
592
592 def _fetchfiles(repo, tr, remote, fnodes, linkrevs):
593 def _fetchfiles(repo, tr, remote, fnodes, linkrevs):
593 """Fetch file data from explicit file revisions."""
594 """Fetch file data from explicit file revisions."""
594
595
595 def iterrevisions(objs, progress):
596 def iterrevisions(objs, progress):
596 for filerevision in objs:
597 for filerevision in objs:
597 node = filerevision[b'node']
598 node = filerevision[b'node']
598
599
599 extrafields = {}
600 extrafields = {}
600
601
601 for field, size in filerevision.get(b'fieldsfollowing', []):
602 for field, size in filerevision.get(b'fieldsfollowing', []):
602 extrafields[field] = next(objs)
603 extrafields[field] = next(objs)
603
604
604 if b'delta' in extrafields:
605 if b'delta' in extrafields:
605 basenode = filerevision[b'deltabasenode']
606 basenode = filerevision[b'deltabasenode']
606 delta = extrafields[b'delta']
607 delta = extrafields[b'delta']
607 elif b'revision' in extrafields:
608 elif b'revision' in extrafields:
608 basenode = nullid
609 basenode = nullid
609 revision = extrafields[b'revision']
610 revision = extrafields[b'revision']
610 delta = mdiff.trivialdiffheader(len(revision)) + revision
611 delta = mdiff.trivialdiffheader(len(revision)) + revision
611 else:
612 else:
612 continue
613 continue
613
614
614 yield (
615 yield (
615 node,
616 node,
616 filerevision[b'parents'][0],
617 filerevision[b'parents'][0],
617 filerevision[b'parents'][1],
618 filerevision[b'parents'][1],
618 node,
619 node,
619 basenode,
620 basenode,
620 delta,
621 delta,
621 # Flags not yet supported.
622 # Flags not yet supported.
622 0,
623 0,
623 )
624 )
624
625
625 progress.increment()
626 progress.increment()
626
627
627 progress = repo.ui.makeprogress(
628 progress = repo.ui.makeprogress(
628 _(b'files'),
629 _(b'files'),
629 unit=_(b'chunks'),
630 unit=_(b'chunks'),
630 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
631 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
631 )
632 )
632
633
633 # TODO make batch size configurable
634 # TODO make batch size configurable
634 batchsize = 10000
635 batchsize = 10000
635 fnodeslist = [x for x in sorted(fnodes.items())]
636 fnodeslist = [x for x in sorted(fnodes.items())]
636
637
637 for i in pycompat.xrange(0, len(fnodeslist), batchsize):
638 for i in pycompat.xrange(0, len(fnodeslist), batchsize):
638 batch = [x for x in fnodeslist[i : i + batchsize]]
639 batch = [x for x in fnodeslist[i : i + batchsize]]
639 if not batch:
640 if not batch:
640 continue
641 continue
641
642
642 with remote.commandexecutor() as e:
643 with remote.commandexecutor() as e:
643 fs = []
644 fs = []
644 locallinkrevs = {}
645 locallinkrevs = {}
645
646
646 for path, nodes in batch:
647 for path, nodes in batch:
647 fs.append(
648 fs.append(
648 (
649 (
649 path,
650 path,
650 e.callcommand(
651 e.callcommand(
651 b'filedata',
652 b'filedata',
652 {
653 {
653 b'path': path,
654 b'path': path,
654 b'nodes': sorted(nodes),
655 b'nodes': sorted(nodes),
655 b'fields': {b'parents', b'revision'},
656 b'fields': {b'parents', b'revision'},
656 b'haveparents': True,
657 b'haveparents': True,
657 },
658 },
658 ),
659 ),
659 )
660 )
660 )
661 )
661
662
662 locallinkrevs[path] = {
663 locallinkrevs[path] = {
663 node: linkrevs[manifestnode]
664 node: linkrevs[manifestnode]
664 for node, manifestnode in pycompat.iteritems(nodes)
665 for node, manifestnode in pycompat.iteritems(nodes)
665 }
666 }
666
667
667 for path, f in fs:
668 for path, f in fs:
668 objs = f.result()
669 objs = f.result()
669
670
670 # Chomp off header objects.
671 # Chomp off header objects.
671 next(objs)
672 next(objs)
672
673
673 store = repo.file(path)
674 store = repo.file(path)
674 store.addgroup(
675 store.addgroup(
675 iterrevisions(objs, progress),
676 iterrevisions(objs, progress),
676 locallinkrevs[path].__getitem__,
677 locallinkrevs[path].__getitem__,
677 weakref.proxy(tr),
678 weakref.proxy(tr),
678 )
679 )
679
680
680
681
681 def _fetchfilesfromcsets(
682 def _fetchfilesfromcsets(
682 repo, tr, remote, pathfilter, fnodes, csets, manlinkrevs, shallow=False
683 repo, tr, remote, pathfilter, fnodes, csets, manlinkrevs, shallow=False
683 ):
684 ):
684 """Fetch file data from explicit changeset revisions."""
685 """Fetch file data from explicit changeset revisions."""
685
686
686 def iterrevisions(objs, remaining, progress):
687 def iterrevisions(objs, remaining, progress):
687 while remaining:
688 while remaining:
688 filerevision = next(objs)
689 filerevision = next(objs)
689
690
690 node = filerevision[b'node']
691 node = filerevision[b'node']
691
692
692 extrafields = {}
693 extrafields = {}
693
694
694 for field, size in filerevision.get(b'fieldsfollowing', []):
695 for field, size in filerevision.get(b'fieldsfollowing', []):
695 extrafields[field] = next(objs)
696 extrafields[field] = next(objs)
696
697
697 if b'delta' in extrafields:
698 if b'delta' in extrafields:
698 basenode = filerevision[b'deltabasenode']
699 basenode = filerevision[b'deltabasenode']
699 delta = extrafields[b'delta']
700 delta = extrafields[b'delta']
700 elif b'revision' in extrafields:
701 elif b'revision' in extrafields:
701 basenode = nullid
702 basenode = nullid
702 revision = extrafields[b'revision']
703 revision = extrafields[b'revision']
703 delta = mdiff.trivialdiffheader(len(revision)) + revision
704 delta = mdiff.trivialdiffheader(len(revision)) + revision
704 else:
705 else:
705 continue
706 continue
706
707
707 if b'linknode' in filerevision:
708 if b'linknode' in filerevision:
708 linknode = filerevision[b'linknode']
709 linknode = filerevision[b'linknode']
709 else:
710 else:
710 linknode = node
711 linknode = node
711
712
712 yield (
713 yield (
713 node,
714 node,
714 filerevision[b'parents'][0],
715 filerevision[b'parents'][0],
715 filerevision[b'parents'][1],
716 filerevision[b'parents'][1],
716 linknode,
717 linknode,
717 basenode,
718 basenode,
718 delta,
719 delta,
719 # Flags not yet supported.
720 # Flags not yet supported.
720 0,
721 0,
721 )
722 )
722
723
723 progress.increment()
724 progress.increment()
724 remaining -= 1
725 remaining -= 1
725
726
726 progress = repo.ui.makeprogress(
727 progress = repo.ui.makeprogress(
727 _(b'files'),
728 _(b'files'),
728 unit=_(b'chunks'),
729 unit=_(b'chunks'),
729 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
730 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
730 )
731 )
731
732
732 commandmeta = remote.apidescriptor[b'commands'][b'filesdata']
733 commandmeta = remote.apidescriptor[b'commands'][b'filesdata']
733 batchsize = commandmeta.get(b'recommendedbatchsize', 50000)
734 batchsize = commandmeta.get(b'recommendedbatchsize', 50000)
734
735
735 shallowfiles = repository.REPO_FEATURE_SHALLOW_FILE_STORAGE in repo.features
736 shallowfiles = repository.REPO_FEATURE_SHALLOW_FILE_STORAGE in repo.features
736 fields = {b'parents', b'revision'}
737 fields = {b'parents', b'revision'}
737 clrev = repo.changelog.rev
738 clrev = repo.changelog.rev
738
739
739 # There are no guarantees that we'll have ancestor revisions if
740 # There are no guarantees that we'll have ancestor revisions if
740 # a) this repo has shallow file storage b) shallow data fetching is enabled.
741 # a) this repo has shallow file storage b) shallow data fetching is enabled.
741 # Force remote to not delta against possibly unknown revisions when these
742 # Force remote to not delta against possibly unknown revisions when these
742 # conditions hold.
743 # conditions hold.
743 haveparents = not (shallowfiles or shallow)
744 haveparents = not (shallowfiles or shallow)
744
745
745 # Similarly, we may not have calculated linkrevs for all incoming file
746 # Similarly, we may not have calculated linkrevs for all incoming file
746 # revisions. Ask the remote to do work for us in this case.
747 # revisions. Ask the remote to do work for us in this case.
747 if not haveparents:
748 if not haveparents:
748 fields.add(b'linknode')
749 fields.add(b'linknode')
749
750
750 for i in pycompat.xrange(0, len(csets), batchsize):
751 for i in pycompat.xrange(0, len(csets), batchsize):
751 batch = [x for x in csets[i : i + batchsize]]
752 batch = [x for x in csets[i : i + batchsize]]
752 if not batch:
753 if not batch:
753 continue
754 continue
754
755
755 with remote.commandexecutor() as e:
756 with remote.commandexecutor() as e:
756 args = {
757 args = {
757 b'revisions': [
758 b'revisions': [
758 {
759 {
759 b'type': b'changesetexplicit',
760 b'type': b'changesetexplicit',
760 b'nodes': batch,
761 b'nodes': batch,
761 }
762 }
762 ],
763 ],
763 b'fields': fields,
764 b'fields': fields,
764 b'haveparents': haveparents,
765 b'haveparents': haveparents,
765 }
766 }
766
767
767 if pathfilter:
768 if pathfilter:
768 args[b'pathfilter'] = pathfilter
769 args[b'pathfilter'] = pathfilter
769
770
770 objs = e.callcommand(b'filesdata', args).result()
771 objs = e.callcommand(b'filesdata', args).result()
771
772
772 # First object is an overall header.
773 # First object is an overall header.
773 overall = next(objs)
774 overall = next(objs)
774
775
775 # We have overall['totalpaths'] segments.
776 # We have overall['totalpaths'] segments.
776 for i in pycompat.xrange(overall[b'totalpaths']):
777 for i in pycompat.xrange(overall[b'totalpaths']):
777 header = next(objs)
778 header = next(objs)
778
779
779 path = header[b'path']
780 path = header[b'path']
780 store = repo.file(path)
781 store = repo.file(path)
781
782
782 linkrevs = {
783 linkrevs = {
783 fnode: manlinkrevs[mnode]
784 fnode: manlinkrevs[mnode]
784 for fnode, mnode in pycompat.iteritems(fnodes[path])
785 for fnode, mnode in pycompat.iteritems(fnodes[path])
785 }
786 }
786
787
787 def getlinkrev(node):
788 def getlinkrev(node):
788 if node in linkrevs:
789 if node in linkrevs:
789 return linkrevs[node]
790 return linkrevs[node]
790 else:
791 else:
791 return clrev(node)
792 return clrev(node)
792
793
793 store.addgroup(
794 store.addgroup(
794 iterrevisions(objs, header[b'totalitems'], progress),
795 iterrevisions(objs, header[b'totalitems'], progress),
795 getlinkrev,
796 getlinkrev,
796 weakref.proxy(tr),
797 weakref.proxy(tr),
797 maybemissingparents=shallow,
798 maybemissingparents=shallow,
798 )
799 )
@@ -1,1987 +1,1993 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 error
11 from .. import error
12 from . import util as interfaceutil
12 from . import util as interfaceutil
13
13
14 # Local repository feature string.
14 # Local repository feature string.
15
15
16 # Revlogs are being used for file storage.
16 # Revlogs are being used for file storage.
17 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
17 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
18 # The storage part of the repository is shared from an external source.
18 # The storage part of the repository is shared from an external source.
19 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
19 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
20 # LFS supported for backing file storage.
20 # LFS supported for backing file storage.
21 REPO_FEATURE_LFS = b'lfs'
21 REPO_FEATURE_LFS = b'lfs'
22 # Repository supports being stream cloned.
22 # Repository supports being stream cloned.
23 REPO_FEATURE_STREAM_CLONE = b'streamclone'
23 REPO_FEATURE_STREAM_CLONE = b'streamclone'
24 # Files storage may lack data for all ancestors.
24 # Files storage may lack data for all ancestors.
25 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
25 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
26
26
27 REVISION_FLAG_CENSORED = 1 << 15
27 REVISION_FLAG_CENSORED = 1 << 15
28 REVISION_FLAG_ELLIPSIS = 1 << 14
28 REVISION_FLAG_ELLIPSIS = 1 << 14
29 REVISION_FLAG_EXTSTORED = 1 << 13
29 REVISION_FLAG_EXTSTORED = 1 << 13
30 REVISION_FLAG_SIDEDATA = 1 << 12
30 REVISION_FLAG_SIDEDATA = 1 << 12
31 REVISION_FLAG_HASCOPIESINFO = 1 << 11
31 REVISION_FLAG_HASCOPIESINFO = 1 << 11
32
32
33 REVISION_FLAGS_KNOWN = (
33 REVISION_FLAGS_KNOWN = (
34 REVISION_FLAG_CENSORED
34 REVISION_FLAG_CENSORED
35 | REVISION_FLAG_ELLIPSIS
35 | REVISION_FLAG_ELLIPSIS
36 | REVISION_FLAG_EXTSTORED
36 | REVISION_FLAG_EXTSTORED
37 | REVISION_FLAG_SIDEDATA
37 | REVISION_FLAG_SIDEDATA
38 | REVISION_FLAG_HASCOPIESINFO
38 | REVISION_FLAG_HASCOPIESINFO
39 )
39 )
40
40
41 CG_DELTAMODE_STD = b'default'
41 CG_DELTAMODE_STD = b'default'
42 CG_DELTAMODE_PREV = b'previous'
42 CG_DELTAMODE_PREV = b'previous'
43 CG_DELTAMODE_FULL = b'fulltext'
43 CG_DELTAMODE_FULL = b'fulltext'
44 CG_DELTAMODE_P1 = b'p1'
44 CG_DELTAMODE_P1 = b'p1'
45
45
46
46
47 class ipeerconnection(interfaceutil.Interface):
47 class ipeerconnection(interfaceutil.Interface):
48 """Represents a "connection" to a repository.
48 """Represents a "connection" to a repository.
49
49
50 This is the base interface for representing a connection to a repository.
50 This is the base interface for representing a connection to a repository.
51 It holds basic properties and methods applicable to all peer types.
51 It holds basic properties and methods applicable to all peer types.
52
52
53 This is not a complete interface definition and should not be used
53 This is not a complete interface definition and should not be used
54 outside of this module.
54 outside of this module.
55 """
55 """
56
56
57 ui = interfaceutil.Attribute("""ui.ui instance""")
57 ui = interfaceutil.Attribute("""ui.ui instance""")
58
58
59 def url():
59 def url():
60 """Returns a URL string representing this peer.
60 """Returns a URL string representing this peer.
61
61
62 Currently, implementations expose the raw URL used to construct the
62 Currently, implementations expose the raw URL used to construct the
63 instance. It may contain credentials as part of the URL. The
63 instance. It may contain credentials as part of the URL. The
64 expectations of the value aren't well-defined and this could lead to
64 expectations of the value aren't well-defined and this could lead to
65 data leakage.
65 data leakage.
66
66
67 TODO audit/clean consumers and more clearly define the contents of this
67 TODO audit/clean consumers and more clearly define the contents of this
68 value.
68 value.
69 """
69 """
70
70
71 def local():
71 def local():
72 """Returns a local repository instance.
72 """Returns a local repository instance.
73
73
74 If the peer represents a local repository, returns an object that
74 If the peer represents a local repository, returns an object that
75 can be used to interface with it. Otherwise returns ``None``.
75 can be used to interface with it. Otherwise returns ``None``.
76 """
76 """
77
77
78 def peer():
78 def peer():
79 """Returns an object conforming to this interface.
79 """Returns an object conforming to this interface.
80
80
81 Most implementations will ``return self``.
81 Most implementations will ``return self``.
82 """
82 """
83
83
84 def canpush():
84 def canpush():
85 """Returns a boolean indicating if this peer can be pushed to."""
85 """Returns a boolean indicating if this peer can be pushed to."""
86
86
87 def close():
87 def close():
88 """Close the connection to this peer.
88 """Close the connection to this peer.
89
89
90 This is called when the peer will no longer be used. Resources
90 This is called when the peer will no longer be used. Resources
91 associated with the peer should be cleaned up.
91 associated with the peer should be cleaned up.
92 """
92 """
93
93
94
94
95 class ipeercapabilities(interfaceutil.Interface):
95 class ipeercapabilities(interfaceutil.Interface):
96 """Peer sub-interface related to capabilities."""
96 """Peer sub-interface related to capabilities."""
97
97
98 def capable(name):
98 def capable(name):
99 """Determine support for a named capability.
99 """Determine support for a named capability.
100
100
101 Returns ``False`` if capability not supported.
101 Returns ``False`` if capability not supported.
102
102
103 Returns ``True`` if boolean capability is supported. Returns a string
103 Returns ``True`` if boolean capability is supported. Returns a string
104 if capability support is non-boolean.
104 if capability support is non-boolean.
105
105
106 Capability strings may or may not map to wire protocol capabilities.
106 Capability strings may or may not map to wire protocol capabilities.
107 """
107 """
108
108
109 def requirecap(name, purpose):
109 def requirecap(name, purpose):
110 """Require a capability to be present.
110 """Require a capability to be present.
111
111
112 Raises a ``CapabilityError`` if the capability isn't present.
112 Raises a ``CapabilityError`` if the capability isn't present.
113 """
113 """
114
114
115
115
116 class ipeercommands(interfaceutil.Interface):
116 class ipeercommands(interfaceutil.Interface):
117 """Client-side interface for communicating over the wire protocol.
117 """Client-side interface for communicating over the wire protocol.
118
118
119 This interface is used as a gateway to the Mercurial wire protocol.
119 This interface is used as a gateway to the Mercurial wire protocol.
120 methods commonly call wire protocol commands of the same name.
120 methods commonly call wire protocol commands of the same name.
121 """
121 """
122
122
123 def branchmap():
123 def branchmap():
124 """Obtain heads in named branches.
124 """Obtain heads in named branches.
125
125
126 Returns a dict mapping branch name to an iterable of nodes that are
126 Returns a dict mapping branch name to an iterable of nodes that are
127 heads on that branch.
127 heads on that branch.
128 """
128 """
129
129
130 def capabilities():
130 def capabilities():
131 """Obtain capabilities of the peer.
131 """Obtain capabilities of the peer.
132
132
133 Returns a set of string capabilities.
133 Returns a set of string capabilities.
134 """
134 """
135
135
136 def clonebundles():
136 def clonebundles():
137 """Obtains the clone bundles manifest for the repo.
137 """Obtains the clone bundles manifest for the repo.
138
138
139 Returns the manifest as unparsed bytes.
139 Returns the manifest as unparsed bytes.
140 """
140 """
141
141
142 def debugwireargs(one, two, three=None, four=None, five=None):
142 def debugwireargs(one, two, three=None, four=None, five=None):
143 """Used to facilitate debugging of arguments passed over the wire."""
143 """Used to facilitate debugging of arguments passed over the wire."""
144
144
145 def getbundle(source, **kwargs):
145 def getbundle(source, **kwargs):
146 """Obtain remote repository data as a bundle.
146 """Obtain remote repository data as a bundle.
147
147
148 This command is how the bulk of repository data is transferred from
148 This command is how the bulk of repository data is transferred from
149 the peer to the local repository
149 the peer to the local repository
150
150
151 Returns a generator of bundle data.
151 Returns a generator of bundle data.
152 """
152 """
153
153
154 def heads():
154 def heads():
155 """Determine all known head revisions in the peer.
155 """Determine all known head revisions in the peer.
156
156
157 Returns an iterable of binary nodes.
157 Returns an iterable of binary nodes.
158 """
158 """
159
159
160 def known(nodes):
160 def known(nodes):
161 """Determine whether multiple nodes are known.
161 """Determine whether multiple nodes are known.
162
162
163 Accepts an iterable of nodes whose presence to check for.
163 Accepts an iterable of nodes whose presence to check for.
164
164
165 Returns an iterable of booleans indicating of the corresponding node
165 Returns an iterable of booleans indicating of the corresponding node
166 at that index is known to the peer.
166 at that index is known to the peer.
167 """
167 """
168
168
169 def listkeys(namespace):
169 def listkeys(namespace):
170 """Obtain all keys in a pushkey namespace.
170 """Obtain all keys in a pushkey namespace.
171
171
172 Returns an iterable of key names.
172 Returns an iterable of key names.
173 """
173 """
174
174
175 def lookup(key):
175 def lookup(key):
176 """Resolve a value to a known revision.
176 """Resolve a value to a known revision.
177
177
178 Returns a binary node of the resolved revision on success.
178 Returns a binary node of the resolved revision on success.
179 """
179 """
180
180
181 def pushkey(namespace, key, old, new):
181 def pushkey(namespace, key, old, new):
182 """Set a value using the ``pushkey`` protocol.
182 """Set a value using the ``pushkey`` protocol.
183
183
184 Arguments correspond to the pushkey namespace and key to operate on and
184 Arguments correspond to the pushkey namespace and key to operate on and
185 the old and new values for that key.
185 the old and new values for that key.
186
186
187 Returns a string with the peer result. The value inside varies by the
187 Returns a string with the peer result. The value inside varies by the
188 namespace.
188 namespace.
189 """
189 """
190
190
191 def stream_out():
191 def stream_out():
192 """Obtain streaming clone data.
192 """Obtain streaming clone data.
193
193
194 Successful result should be a generator of data chunks.
194 Successful result should be a generator of data chunks.
195 """
195 """
196
196
197 def unbundle(bundle, heads, url):
197 def unbundle(bundle, heads, url):
198 """Transfer repository data to the peer.
198 """Transfer repository data to the peer.
199
199
200 This is how the bulk of data during a push is transferred.
200 This is how the bulk of data during a push is transferred.
201
201
202 Returns the integer number of heads added to the peer.
202 Returns the integer number of heads added to the peer.
203 """
203 """
204
204
205
205
206 class ipeerlegacycommands(interfaceutil.Interface):
206 class ipeerlegacycommands(interfaceutil.Interface):
207 """Interface for implementing support for legacy wire protocol commands.
207 """Interface for implementing support for legacy wire protocol commands.
208
208
209 Wire protocol commands transition to legacy status when they are no longer
209 Wire protocol commands transition to legacy status when they are no longer
210 used by modern clients. To facilitate identifying which commands are
210 used by modern clients. To facilitate identifying which commands are
211 legacy, the interfaces are split.
211 legacy, the interfaces are split.
212 """
212 """
213
213
214 def between(pairs):
214 def between(pairs):
215 """Obtain nodes between pairs of nodes.
215 """Obtain nodes between pairs of nodes.
216
216
217 ``pairs`` is an iterable of node pairs.
217 ``pairs`` is an iterable of node pairs.
218
218
219 Returns an iterable of iterables of nodes corresponding to each
219 Returns an iterable of iterables of nodes corresponding to each
220 requested pair.
220 requested pair.
221 """
221 """
222
222
223 def branches(nodes):
223 def branches(nodes):
224 """Obtain ancestor changesets of specific nodes back to a branch point.
224 """Obtain ancestor changesets of specific nodes back to a branch point.
225
225
226 For each requested node, the peer finds the first ancestor node that is
226 For each requested node, the peer finds the first ancestor node that is
227 a DAG root or is a merge.
227 a DAG root or is a merge.
228
228
229 Returns an iterable of iterables with the resolved values for each node.
229 Returns an iterable of iterables with the resolved values for each node.
230 """
230 """
231
231
232 def changegroup(nodes, source):
232 def changegroup(nodes, source):
233 """Obtain a changegroup with data for descendants of specified nodes."""
233 """Obtain a changegroup with data for descendants of specified nodes."""
234
234
235 def changegroupsubset(bases, heads, source):
235 def changegroupsubset(bases, heads, source):
236 pass
236 pass
237
237
238
238
239 class ipeercommandexecutor(interfaceutil.Interface):
239 class ipeercommandexecutor(interfaceutil.Interface):
240 """Represents a mechanism to execute remote commands.
240 """Represents a mechanism to execute remote commands.
241
241
242 This is the primary interface for requesting that wire protocol commands
242 This is the primary interface for requesting that wire protocol commands
243 be executed. Instances of this interface are active in a context manager
243 be executed. Instances of this interface are active in a context manager
244 and have a well-defined lifetime. When the context manager exits, all
244 and have a well-defined lifetime. When the context manager exits, all
245 outstanding requests are waited on.
245 outstanding requests are waited on.
246 """
246 """
247
247
248 def callcommand(name, args):
248 def callcommand(name, args):
249 """Request that a named command be executed.
249 """Request that a named command be executed.
250
250
251 Receives the command name and a dictionary of command arguments.
251 Receives the command name and a dictionary of command arguments.
252
252
253 Returns a ``concurrent.futures.Future`` that will resolve to the
253 Returns a ``concurrent.futures.Future`` that will resolve to the
254 result of that command request. That exact value is left up to
254 result of that command request. That exact value is left up to
255 the implementation and possibly varies by command.
255 the implementation and possibly varies by command.
256
256
257 Not all commands can coexist with other commands in an executor
257 Not all commands can coexist with other commands in an executor
258 instance: it depends on the underlying wire protocol transport being
258 instance: it depends on the underlying wire protocol transport being
259 used and the command itself.
259 used and the command itself.
260
260
261 Implementations MAY call ``sendcommands()`` automatically if the
261 Implementations MAY call ``sendcommands()`` automatically if the
262 requested command can not coexist with other commands in this executor.
262 requested command can not coexist with other commands in this executor.
263
263
264 Implementations MAY call ``sendcommands()`` automatically when the
264 Implementations MAY call ``sendcommands()`` automatically when the
265 future's ``result()`` is called. So, consumers using multiple
265 future's ``result()`` is called. So, consumers using multiple
266 commands with an executor MUST ensure that ``result()`` is not called
266 commands with an executor MUST ensure that ``result()`` is not called
267 until all command requests have been issued.
267 until all command requests have been issued.
268 """
268 """
269
269
270 def sendcommands():
270 def sendcommands():
271 """Trigger submission of queued command requests.
271 """Trigger submission of queued command requests.
272
272
273 Not all transports submit commands as soon as they are requested to
273 Not all transports submit commands as soon as they are requested to
274 run. When called, this method forces queued command requests to be
274 run. When called, this method forces queued command requests to be
275 issued. It will no-op if all commands have already been sent.
275 issued. It will no-op if all commands have already been sent.
276
276
277 When called, no more new commands may be issued with this executor.
277 When called, no more new commands may be issued with this executor.
278 """
278 """
279
279
280 def close():
280 def close():
281 """Signal that this command request is finished.
281 """Signal that this command request is finished.
282
282
283 When called, no more new commands may be issued. All outstanding
283 When called, no more new commands may be issued. All outstanding
284 commands that have previously been issued are waited on before
284 commands that have previously been issued are waited on before
285 returning. This not only includes waiting for the futures to resolve,
285 returning. This not only includes waiting for the futures to resolve,
286 but also waiting for all response data to arrive. In other words,
286 but also waiting for all response data to arrive. In other words,
287 calling this waits for all on-wire state for issued command requests
287 calling this waits for all on-wire state for issued command requests
288 to finish.
288 to finish.
289
289
290 When used as a context manager, this method is called when exiting the
290 When used as a context manager, this method is called when exiting the
291 context manager.
291 context manager.
292
292
293 This method may call ``sendcommands()`` if there are buffered commands.
293 This method may call ``sendcommands()`` if there are buffered commands.
294 """
294 """
295
295
296
296
297 class ipeerrequests(interfaceutil.Interface):
297 class ipeerrequests(interfaceutil.Interface):
298 """Interface for executing commands on a peer."""
298 """Interface for executing commands on a peer."""
299
299
300 limitedarguments = interfaceutil.Attribute(
300 limitedarguments = interfaceutil.Attribute(
301 """True if the peer cannot receive large argument value for commands."""
301 """True if the peer cannot receive large argument value for commands."""
302 )
302 )
303
303
304 def commandexecutor():
304 def commandexecutor():
305 """A context manager that resolves to an ipeercommandexecutor.
305 """A context manager that resolves to an ipeercommandexecutor.
306
306
307 The object this resolves to can be used to issue command requests
307 The object this resolves to can be used to issue command requests
308 to the peer.
308 to the peer.
309
309
310 Callers should call its ``callcommand`` method to issue command
310 Callers should call its ``callcommand`` method to issue command
311 requests.
311 requests.
312
312
313 A new executor should be obtained for each distinct set of commands
313 A new executor should be obtained for each distinct set of commands
314 (possibly just a single command) that the consumer wants to execute
314 (possibly just a single command) that the consumer wants to execute
315 as part of a single operation or round trip. This is because some
315 as part of a single operation or round trip. This is because some
316 peers are half-duplex and/or don't support persistent connections.
316 peers are half-duplex and/or don't support persistent connections.
317 e.g. in the case of HTTP peers, commands sent to an executor represent
317 e.g. in the case of HTTP peers, commands sent to an executor represent
318 a single HTTP request. While some peers may support multiple command
318 a single HTTP request. While some peers may support multiple command
319 sends over the wire per executor, consumers need to code to the least
319 sends over the wire per executor, consumers need to code to the least
320 capable peer. So it should be assumed that command executors buffer
320 capable peer. So it should be assumed that command executors buffer
321 called commands until they are told to send them and that each
321 called commands until they are told to send them and that each
322 command executor could result in a new connection or wire-level request
322 command executor could result in a new connection or wire-level request
323 being issued.
323 being issued.
324 """
324 """
325
325
326
326
327 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
327 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
328 """Unified interface for peer repositories.
328 """Unified interface for peer repositories.
329
329
330 All peer instances must conform to this interface.
330 All peer instances must conform to this interface.
331 """
331 """
332
332
333
333
334 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
334 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
335 """Unified peer interface for wire protocol version 2 peers."""
335 """Unified peer interface for wire protocol version 2 peers."""
336
336
337 apidescriptor = interfaceutil.Attribute(
337 apidescriptor = interfaceutil.Attribute(
338 """Data structure holding description of server API."""
338 """Data structure holding description of server API."""
339 )
339 )
340
340
341
341
342 @interfaceutil.implementer(ipeerbase)
342 @interfaceutil.implementer(ipeerbase)
343 class peer(object):
343 class peer(object):
344 """Base class for peer repositories."""
344 """Base class for peer repositories."""
345
345
346 limitedarguments = False
346 limitedarguments = False
347
347
348 def capable(self, name):
348 def capable(self, name):
349 caps = self.capabilities()
349 caps = self.capabilities()
350 if name in caps:
350 if name in caps:
351 return True
351 return True
352
352
353 name = b'%s=' % name
353 name = b'%s=' % name
354 for cap in caps:
354 for cap in caps:
355 if cap.startswith(name):
355 if cap.startswith(name):
356 return cap[len(name) :]
356 return cap[len(name) :]
357
357
358 return False
358 return False
359
359
360 def requirecap(self, name, purpose):
360 def requirecap(self, name, purpose):
361 if self.capable(name):
361 if self.capable(name):
362 return
362 return
363
363
364 raise error.CapabilityError(
364 raise error.CapabilityError(
365 _(
365 _(
366 b'cannot %s; remote repository does not support the '
366 b'cannot %s; remote repository does not support the '
367 b'\'%s\' capability'
367 b'\'%s\' capability'
368 )
368 )
369 % (purpose, name)
369 % (purpose, name)
370 )
370 )
371
371
372
372
373 class iverifyproblem(interfaceutil.Interface):
373 class iverifyproblem(interfaceutil.Interface):
374 """Represents a problem with the integrity of the repository.
374 """Represents a problem with the integrity of the repository.
375
375
376 Instances of this interface are emitted to describe an integrity issue
376 Instances of this interface are emitted to describe an integrity issue
377 with a repository (e.g. corrupt storage, missing data, etc).
377 with a repository (e.g. corrupt storage, missing data, etc).
378
378
379 Instances are essentially messages associated with severity.
379 Instances are essentially messages associated with severity.
380 """
380 """
381
381
382 warning = interfaceutil.Attribute(
382 warning = interfaceutil.Attribute(
383 """Message indicating a non-fatal problem."""
383 """Message indicating a non-fatal problem."""
384 )
384 )
385
385
386 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
386 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
387
387
388 node = interfaceutil.Attribute(
388 node = interfaceutil.Attribute(
389 """Revision encountering the problem.
389 """Revision encountering the problem.
390
390
391 ``None`` means the problem doesn't apply to a single revision.
391 ``None`` means the problem doesn't apply to a single revision.
392 """
392 """
393 )
393 )
394
394
395
395
396 class irevisiondelta(interfaceutil.Interface):
396 class irevisiondelta(interfaceutil.Interface):
397 """Represents a delta between one revision and another.
397 """Represents a delta between one revision and another.
398
398
399 Instances convey enough information to allow a revision to be exchanged
399 Instances convey enough information to allow a revision to be exchanged
400 with another repository.
400 with another repository.
401
401
402 Instances represent the fulltext revision data or a delta against
402 Instances represent the fulltext revision data or a delta against
403 another revision. Therefore the ``revision`` and ``delta`` attributes
403 another revision. Therefore the ``revision`` and ``delta`` attributes
404 are mutually exclusive.
404 are mutually exclusive.
405
405
406 Typically used for changegroup generation.
406 Typically used for changegroup generation.
407 """
407 """
408
408
409 node = interfaceutil.Attribute("""20 byte node of this revision.""")
409 node = interfaceutil.Attribute("""20 byte node of this revision.""")
410
410
411 p1node = interfaceutil.Attribute(
411 p1node = interfaceutil.Attribute(
412 """20 byte node of 1st parent of this revision."""
412 """20 byte node of 1st parent of this revision."""
413 )
413 )
414
414
415 p2node = interfaceutil.Attribute(
415 p2node = interfaceutil.Attribute(
416 """20 byte node of 2nd parent of this revision."""
416 """20 byte node of 2nd parent of this revision."""
417 )
417 )
418
418
419 linknode = interfaceutil.Attribute(
419 linknode = interfaceutil.Attribute(
420 """20 byte node of the changelog revision this node is linked to."""
420 """20 byte node of the changelog revision this node is linked to."""
421 )
421 )
422
422
423 flags = interfaceutil.Attribute(
423 flags = interfaceutil.Attribute(
424 """2 bytes of integer flags that apply to this revision.
424 """2 bytes of integer flags that apply to this revision.
425
425
426 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
426 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
427 """
427 """
428 )
428 )
429
429
430 basenode = interfaceutil.Attribute(
430 basenode = interfaceutil.Attribute(
431 """20 byte node of the revision this data is a delta against.
431 """20 byte node of the revision this data is a delta against.
432
432
433 ``nullid`` indicates that the revision is a full revision and not
433 ``nullid`` indicates that the revision is a full revision and not
434 a delta.
434 a delta.
435 """
435 """
436 )
436 )
437
437
438 baserevisionsize = interfaceutil.Attribute(
438 baserevisionsize = interfaceutil.Attribute(
439 """Size of base revision this delta is against.
439 """Size of base revision this delta is against.
440
440
441 May be ``None`` if ``basenode`` is ``nullid``.
441 May be ``None`` if ``basenode`` is ``nullid``.
442 """
442 """
443 )
443 )
444
444
445 revision = interfaceutil.Attribute(
445 revision = interfaceutil.Attribute(
446 """Raw fulltext of revision data for this node."""
446 """Raw fulltext of revision data for this node."""
447 )
447 )
448
448
449 delta = interfaceutil.Attribute(
449 delta = interfaceutil.Attribute(
450 """Delta between ``basenode`` and ``node``.
450 """Delta between ``basenode`` and ``node``.
451
451
452 Stored in the bdiff delta format.
452 Stored in the bdiff delta format.
453 """
453 """
454 )
454 )
455
455
456
456
457 class ifilerevisionssequence(interfaceutil.Interface):
457 class ifilerevisionssequence(interfaceutil.Interface):
458 """Contains index data for all revisions of a file.
458 """Contains index data for all revisions of a file.
459
459
460 Types implementing this behave like lists of tuples. The index
460 Types implementing this behave like lists of tuples. The index
461 in the list corresponds to the revision number. The values contain
461 in the list corresponds to the revision number. The values contain
462 index metadata.
462 index metadata.
463
463
464 The *null* revision (revision number -1) is always the last item
464 The *null* revision (revision number -1) is always the last item
465 in the index.
465 in the index.
466 """
466 """
467
467
468 def __len__():
468 def __len__():
469 """The total number of revisions."""
469 """The total number of revisions."""
470
470
471 def __getitem__(rev):
471 def __getitem__(rev):
472 """Returns the object having a specific revision number.
472 """Returns the object having a specific revision number.
473
473
474 Returns an 8-tuple with the following fields:
474 Returns an 8-tuple with the following fields:
475
475
476 offset+flags
476 offset+flags
477 Contains the offset and flags for the revision. 64-bit unsigned
477 Contains the offset and flags for the revision. 64-bit unsigned
478 integer where first 6 bytes are the offset and the next 2 bytes
478 integer where first 6 bytes are the offset and the next 2 bytes
479 are flags. The offset can be 0 if it is not used by the store.
479 are flags. The offset can be 0 if it is not used by the store.
480 compressed size
480 compressed size
481 Size of the revision data in the store. It can be 0 if it isn't
481 Size of the revision data in the store. It can be 0 if it isn't
482 needed by the store.
482 needed by the store.
483 uncompressed size
483 uncompressed size
484 Fulltext size. It can be 0 if it isn't needed by the store.
484 Fulltext size. It can be 0 if it isn't needed by the store.
485 base revision
485 base revision
486 Revision number of revision the delta for storage is encoded
486 Revision number of revision the delta for storage is encoded
487 against. -1 indicates not encoded against a base revision.
487 against. -1 indicates not encoded against a base revision.
488 link revision
488 link revision
489 Revision number of changelog revision this entry is related to.
489 Revision number of changelog revision this entry is related to.
490 p1 revision
490 p1 revision
491 Revision number of 1st parent. -1 if no 1st parent.
491 Revision number of 1st parent. -1 if no 1st parent.
492 p2 revision
492 p2 revision
493 Revision number of 2nd parent. -1 if no 1st parent.
493 Revision number of 2nd parent. -1 if no 1st parent.
494 node
494 node
495 Binary node value for this revision number.
495 Binary node value for this revision number.
496
496
497 Negative values should index off the end of the sequence. ``-1``
497 Negative values should index off the end of the sequence. ``-1``
498 should return the null revision. ``-2`` should return the most
498 should return the null revision. ``-2`` should return the most
499 recent revision.
499 recent revision.
500 """
500 """
501
501
502 def __contains__(rev):
502 def __contains__(rev):
503 """Whether a revision number exists."""
503 """Whether a revision number exists."""
504
504
505 def insert(self, i, entry):
505 def insert(self, i, entry):
506 """Add an item to the index at specific revision."""
506 """Add an item to the index at specific revision."""
507
507
508
508
509 class ifileindex(interfaceutil.Interface):
509 class ifileindex(interfaceutil.Interface):
510 """Storage interface for index data of a single file.
510 """Storage interface for index data of a single file.
511
511
512 File storage data is divided into index metadata and data storage.
512 File storage data is divided into index metadata and data storage.
513 This interface defines the index portion of the interface.
513 This interface defines the index portion of the interface.
514
514
515 The index logically consists of:
515 The index logically consists of:
516
516
517 * A mapping between revision numbers and nodes.
517 * A mapping between revision numbers and nodes.
518 * DAG data (storing and querying the relationship between nodes).
518 * DAG data (storing and querying the relationship between nodes).
519 * Metadata to facilitate storage.
519 * Metadata to facilitate storage.
520 """
520 """
521
521
522 def __len__():
522 def __len__():
523 """Obtain the number of revisions stored for this file."""
523 """Obtain the number of revisions stored for this file."""
524
524
525 def __iter__():
525 def __iter__():
526 """Iterate over revision numbers for this file."""
526 """Iterate over revision numbers for this file."""
527
527
528 def hasnode(node):
528 def hasnode(node):
529 """Returns a bool indicating if a node is known to this store.
529 """Returns a bool indicating if a node is known to this store.
530
530
531 Implementations must only return True for full, binary node values:
531 Implementations must only return True for full, binary node values:
532 hex nodes, revision numbers, and partial node matches must be
532 hex nodes, revision numbers, and partial node matches must be
533 rejected.
533 rejected.
534
534
535 The null node is never present.
535 The null node is never present.
536 """
536 """
537
537
538 def revs(start=0, stop=None):
538 def revs(start=0, stop=None):
539 """Iterate over revision numbers for this file, with control."""
539 """Iterate over revision numbers for this file, with control."""
540
540
541 def parents(node):
541 def parents(node):
542 """Returns a 2-tuple of parent nodes for a revision.
542 """Returns a 2-tuple of parent nodes for a revision.
543
543
544 Values will be ``nullid`` if the parent is empty.
544 Values will be ``nullid`` if the parent is empty.
545 """
545 """
546
546
547 def parentrevs(rev):
547 def parentrevs(rev):
548 """Like parents() but operates on revision numbers."""
548 """Like parents() but operates on revision numbers."""
549
549
550 def rev(node):
550 def rev(node):
551 """Obtain the revision number given a node.
551 """Obtain the revision number given a node.
552
552
553 Raises ``error.LookupError`` if the node is not known.
553 Raises ``error.LookupError`` if the node is not known.
554 """
554 """
555
555
556 def node(rev):
556 def node(rev):
557 """Obtain the node value given a revision number.
557 """Obtain the node value given a revision number.
558
558
559 Raises ``IndexError`` if the node is not known.
559 Raises ``IndexError`` if the node is not known.
560 """
560 """
561
561
562 def lookup(node):
562 def lookup(node):
563 """Attempt to resolve a value to a node.
563 """Attempt to resolve a value to a node.
564
564
565 Value can be a binary node, hex node, revision number, or a string
565 Value can be a binary node, hex node, revision number, or a string
566 that can be converted to an integer.
566 that can be converted to an integer.
567
567
568 Raises ``error.LookupError`` if a node could not be resolved.
568 Raises ``error.LookupError`` if a node could not be resolved.
569 """
569 """
570
570
571 def linkrev(rev):
571 def linkrev(rev):
572 """Obtain the changeset revision number a revision is linked to."""
572 """Obtain the changeset revision number a revision is linked to."""
573
573
574 def iscensored(rev):
574 def iscensored(rev):
575 """Return whether a revision's content has been censored."""
575 """Return whether a revision's content has been censored."""
576
576
577 def commonancestorsheads(node1, node2):
577 def commonancestorsheads(node1, node2):
578 """Obtain an iterable of nodes containing heads of common ancestors.
578 """Obtain an iterable of nodes containing heads of common ancestors.
579
579
580 See ``ancestor.commonancestorsheads()``.
580 See ``ancestor.commonancestorsheads()``.
581 """
581 """
582
582
583 def descendants(revs):
583 def descendants(revs):
584 """Obtain descendant revision numbers for a set of revision numbers.
584 """Obtain descendant revision numbers for a set of revision numbers.
585
585
586 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
586 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
587 """
587 """
588
588
589 def heads(start=None, stop=None):
589 def heads(start=None, stop=None):
590 """Obtain a list of nodes that are DAG heads, with control.
590 """Obtain a list of nodes that are DAG heads, with control.
591
591
592 The set of revisions examined can be limited by specifying
592 The set of revisions examined can be limited by specifying
593 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
593 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
594 iterable of nodes. DAG traversal starts at earlier revision
594 iterable of nodes. DAG traversal starts at earlier revision
595 ``start`` and iterates forward until any node in ``stop`` is
595 ``start`` and iterates forward until any node in ``stop`` is
596 encountered.
596 encountered.
597 """
597 """
598
598
599 def children(node):
599 def children(node):
600 """Obtain nodes that are children of a node.
600 """Obtain nodes that are children of a node.
601
601
602 Returns a list of nodes.
602 Returns a list of nodes.
603 """
603 """
604
604
605
605
606 class ifiledata(interfaceutil.Interface):
606 class ifiledata(interfaceutil.Interface):
607 """Storage interface for data storage of a specific file.
607 """Storage interface for data storage of a specific file.
608
608
609 This complements ``ifileindex`` and provides an interface for accessing
609 This complements ``ifileindex`` and provides an interface for accessing
610 data for a tracked file.
610 data for a tracked file.
611 """
611 """
612
612
613 def size(rev):
613 def size(rev):
614 """Obtain the fulltext size of file data.
614 """Obtain the fulltext size of file data.
615
615
616 Any metadata is excluded from size measurements.
616 Any metadata is excluded from size measurements.
617 """
617 """
618
618
619 def revision(node, raw=False):
619 def revision(node, raw=False):
620 """Obtain fulltext data for a node.
620 """Obtain fulltext data for a node.
621
621
622 By default, any storage transformations are applied before the data
622 By default, any storage transformations are applied before the data
623 is returned. If ``raw`` is True, non-raw storage transformations
623 is returned. If ``raw`` is True, non-raw storage transformations
624 are not applied.
624 are not applied.
625
625
626 The fulltext data may contain a header containing metadata. Most
626 The fulltext data may contain a header containing metadata. Most
627 consumers should use ``read()`` to obtain the actual file data.
627 consumers should use ``read()`` to obtain the actual file data.
628 """
628 """
629
629
630 def rawdata(node):
630 def rawdata(node):
631 """Obtain raw data for a node."""
631 """Obtain raw data for a node."""
632
632
633 def read(node):
633 def read(node):
634 """Resolve file fulltext data.
634 """Resolve file fulltext data.
635
635
636 This is similar to ``revision()`` except any metadata in the data
636 This is similar to ``revision()`` except any metadata in the data
637 headers is stripped.
637 headers is stripped.
638 """
638 """
639
639
640 def renamed(node):
640 def renamed(node):
641 """Obtain copy metadata for a node.
641 """Obtain copy metadata for a node.
642
642
643 Returns ``False`` if no copy metadata is stored or a 2-tuple of
643 Returns ``False`` if no copy metadata is stored or a 2-tuple of
644 (path, node) from which this revision was copied.
644 (path, node) from which this revision was copied.
645 """
645 """
646
646
647 def cmp(node, fulltext):
647 def cmp(node, fulltext):
648 """Compare fulltext to another revision.
648 """Compare fulltext to another revision.
649
649
650 Returns True if the fulltext is different from what is stored.
650 Returns True if the fulltext is different from what is stored.
651
651
652 This takes copy metadata into account.
652 This takes copy metadata into account.
653
653
654 TODO better document the copy metadata and censoring logic.
654 TODO better document the copy metadata and censoring logic.
655 """
655 """
656
656
657 def emitrevisions(
657 def emitrevisions(
658 nodes,
658 nodes,
659 nodesorder=None,
659 nodesorder=None,
660 revisiondata=False,
660 revisiondata=False,
661 assumehaveparentrevisions=False,
661 assumehaveparentrevisions=False,
662 deltamode=CG_DELTAMODE_STD,
662 deltamode=CG_DELTAMODE_STD,
663 ):
663 ):
664 """Produce ``irevisiondelta`` for revisions.
664 """Produce ``irevisiondelta`` for revisions.
665
665
666 Given an iterable of nodes, emits objects conforming to the
666 Given an iterable of nodes, emits objects conforming to the
667 ``irevisiondelta`` interface that describe revisions in storage.
667 ``irevisiondelta`` interface that describe revisions in storage.
668
668
669 This method is a generator.
669 This method is a generator.
670
670
671 The input nodes may be unordered. Implementations must ensure that a
671 The input nodes may be unordered. Implementations must ensure that a
672 node's parents are emitted before the node itself. Transitively, this
672 node's parents are emitted before the node itself. Transitively, this
673 means that a node may only be emitted once all its ancestors in
673 means that a node may only be emitted once all its ancestors in
674 ``nodes`` have also been emitted.
674 ``nodes`` have also been emitted.
675
675
676 By default, emits "index" data (the ``node``, ``p1node``, and
676 By default, emits "index" data (the ``node``, ``p1node``, and
677 ``p2node`` attributes). If ``revisiondata`` is set, revision data
677 ``p2node`` attributes). If ``revisiondata`` is set, revision data
678 will also be present on the emitted objects.
678 will also be present on the emitted objects.
679
679
680 With default argument values, implementations can choose to emit
680 With default argument values, implementations can choose to emit
681 either fulltext revision data or a delta. When emitting deltas,
681 either fulltext revision data or a delta. When emitting deltas,
682 implementations must consider whether the delta's base revision
682 implementations must consider whether the delta's base revision
683 fulltext is available to the receiver.
683 fulltext is available to the receiver.
684
684
685 The base revision fulltext is guaranteed to be available if any of
685 The base revision fulltext is guaranteed to be available if any of
686 the following are met:
686 the following are met:
687
687
688 * Its fulltext revision was emitted by this method call.
688 * Its fulltext revision was emitted by this method call.
689 * A delta for that revision was emitted by this method call.
689 * A delta for that revision was emitted by this method call.
690 * ``assumehaveparentrevisions`` is True and the base revision is a
690 * ``assumehaveparentrevisions`` is True and the base revision is a
691 parent of the node.
691 parent of the node.
692
692
693 ``nodesorder`` can be used to control the order that revisions are
693 ``nodesorder`` can be used to control the order that revisions are
694 emitted. By default, revisions can be reordered as long as they are
694 emitted. By default, revisions can be reordered as long as they are
695 in DAG topological order (see above). If the value is ``nodes``,
695 in DAG topological order (see above). If the value is ``nodes``,
696 the iteration order from ``nodes`` should be used. If the value is
696 the iteration order from ``nodes`` should be used. If the value is
697 ``storage``, then the native order from the backing storage layer
697 ``storage``, then the native order from the backing storage layer
698 is used. (Not all storage layers will have strong ordering and behavior
698 is used. (Not all storage layers will have strong ordering and behavior
699 of this mode is storage-dependent.) ``nodes`` ordering can force
699 of this mode is storage-dependent.) ``nodes`` ordering can force
700 revisions to be emitted before their ancestors, so consumers should
700 revisions to be emitted before their ancestors, so consumers should
701 use it with care.
701 use it with care.
702
702
703 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
703 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
704 be set and it is the caller's responsibility to resolve it, if needed.
704 be set and it is the caller's responsibility to resolve it, if needed.
705
705
706 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
706 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
707 all revision data should be emitted as deltas against the revision
707 all revision data should be emitted as deltas against the revision
708 emitted just prior. The initial revision should be a delta against its
708 emitted just prior. The initial revision should be a delta against its
709 1st parent.
709 1st parent.
710 """
710 """
711
711
712
712
713 class ifilemutation(interfaceutil.Interface):
713 class ifilemutation(interfaceutil.Interface):
714 """Storage interface for mutation events of a tracked file."""
714 """Storage interface for mutation events of a tracked file."""
715
715
716 def add(filedata, meta, transaction, linkrev, p1, p2):
716 def add(filedata, meta, transaction, linkrev, p1, p2):
717 """Add a new revision to the store.
717 """Add a new revision to the store.
718
718
719 Takes file data, dictionary of metadata, a transaction, linkrev,
719 Takes file data, dictionary of metadata, a transaction, linkrev,
720 and parent nodes.
720 and parent nodes.
721
721
722 Returns the node that was added.
722 Returns the node that was added.
723
723
724 May no-op if a revision matching the supplied data is already stored.
724 May no-op if a revision matching the supplied data is already stored.
725 """
725 """
726
726
727 def addrevision(
727 def addrevision(
728 revisiondata,
728 revisiondata,
729 transaction,
729 transaction,
730 linkrev,
730 linkrev,
731 p1,
731 p1,
732 p2,
732 p2,
733 node=None,
733 node=None,
734 flags=0,
734 flags=0,
735 cachedelta=None,
735 cachedelta=None,
736 ):
736 ):
737 """Add a new revision to the store.
737 """Add a new revision to the store.
738
738
739 This is similar to ``add()`` except it operates at a lower level.
739 This is similar to ``add()`` except it operates at a lower level.
740
740
741 The data passed in already contains a metadata header, if any.
741 The data passed in already contains a metadata header, if any.
742
742
743 ``node`` and ``flags`` can be used to define the expected node and
743 ``node`` and ``flags`` can be used to define the expected node and
744 the flags to use with storage. ``flags`` is a bitwise value composed
744 the flags to use with storage. ``flags`` is a bitwise value composed
745 of the various ``REVISION_FLAG_*`` constants.
745 of the various ``REVISION_FLAG_*`` constants.
746
746
747 ``add()`` is usually called when adding files from e.g. the working
747 ``add()`` is usually called when adding files from e.g. the working
748 directory. ``addrevision()`` is often called by ``add()`` and for
748 directory. ``addrevision()`` is often called by ``add()`` and for
749 scenarios where revision data has already been computed, such as when
749 scenarios where revision data has already been computed, such as when
750 applying raw data from a peer repo.
750 applying raw data from a peer repo.
751 """
751 """
752
752
753 def addgroup(
753 def addgroup(
754 deltas,
754 deltas,
755 linkmapper,
755 linkmapper,
756 transaction,
756 transaction,
757 addrevisioncb=None,
757 addrevisioncb=None,
758 duplicaterevisioncb=None,
758 duplicaterevisioncb=None,
759 maybemissingparents=False,
759 maybemissingparents=False,
760 ):
760 ):
761 """Process a series of deltas for storage.
761 """Process a series of deltas for storage.
762
762
763 ``deltas`` is an iterable of 7-tuples of
763 ``deltas`` is an iterable of 7-tuples of
764 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
764 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
765 to add.
765 to add.
766
766
767 The ``delta`` field contains ``mpatch`` data to apply to a base
767 The ``delta`` field contains ``mpatch`` data to apply to a base
768 revision, identified by ``deltabase``. The base node can be
768 revision, identified by ``deltabase``. The base node can be
769 ``nullid``, in which case the header from the delta can be ignored
769 ``nullid``, in which case the header from the delta can be ignored
770 and the delta used as the fulltext.
770 and the delta used as the fulltext.
771
771
772 ``alwayscache`` instructs the lower layers to cache the content of the
773 newly added revision, even if it needs to be explicitly computed.
774 This used to be the default when ``addrevisioncb`` was provided up to
775 Mercurial 5.8.
776
772 ``addrevisioncb`` should be called for each node as it is committed.
777 ``addrevisioncb`` should be called for each node as it is committed.
778 ``duplicaterevisioncb`` should be called for each pre-existing node.
773
779
774 ``maybemissingparents`` is a bool indicating whether the incoming
780 ``maybemissingparents`` is a bool indicating whether the incoming
775 data may reference parents/ancestor revisions that aren't present.
781 data may reference parents/ancestor revisions that aren't present.
776 This flag is set when receiving data into a "shallow" store that
782 This flag is set when receiving data into a "shallow" store that
777 doesn't hold all history.
783 doesn't hold all history.
778
784
779 Returns a list of nodes that were processed. A node will be in the list
785 Returns a list of nodes that were processed. A node will be in the list
780 even if it existed in the store previously.
786 even if it existed in the store previously.
781 """
787 """
782
788
783 def censorrevision(tr, node, tombstone=b''):
789 def censorrevision(tr, node, tombstone=b''):
784 """Remove the content of a single revision.
790 """Remove the content of a single revision.
785
791
786 The specified ``node`` will have its content purged from storage.
792 The specified ``node`` will have its content purged from storage.
787 Future attempts to access the revision data for this node will
793 Future attempts to access the revision data for this node will
788 result in failure.
794 result in failure.
789
795
790 A ``tombstone`` message can optionally be stored. This message may be
796 A ``tombstone`` message can optionally be stored. This message may be
791 displayed to users when they attempt to access the missing revision
797 displayed to users when they attempt to access the missing revision
792 data.
798 data.
793
799
794 Storage backends may have stored deltas against the previous content
800 Storage backends may have stored deltas against the previous content
795 in this revision. As part of censoring a revision, these storage
801 in this revision. As part of censoring a revision, these storage
796 backends are expected to rewrite any internally stored deltas such
802 backends are expected to rewrite any internally stored deltas such
797 that they no longer reference the deleted content.
803 that they no longer reference the deleted content.
798 """
804 """
799
805
800 def getstrippoint(minlink):
806 def getstrippoint(minlink):
801 """Find the minimum revision that must be stripped to strip a linkrev.
807 """Find the minimum revision that must be stripped to strip a linkrev.
802
808
803 Returns a 2-tuple containing the minimum revision number and a set
809 Returns a 2-tuple containing the minimum revision number and a set
804 of all revisions numbers that would be broken by this strip.
810 of all revisions numbers that would be broken by this strip.
805
811
806 TODO this is highly revlog centric and should be abstracted into
812 TODO this is highly revlog centric and should be abstracted into
807 a higher-level deletion API. ``repair.strip()`` relies on this.
813 a higher-level deletion API. ``repair.strip()`` relies on this.
808 """
814 """
809
815
810 def strip(minlink, transaction):
816 def strip(minlink, transaction):
811 """Remove storage of items starting at a linkrev.
817 """Remove storage of items starting at a linkrev.
812
818
813 This uses ``getstrippoint()`` to determine the first node to remove.
819 This uses ``getstrippoint()`` to determine the first node to remove.
814 Then it effectively truncates storage for all revisions after that.
820 Then it effectively truncates storage for all revisions after that.
815
821
816 TODO this is highly revlog centric and should be abstracted into a
822 TODO this is highly revlog centric and should be abstracted into a
817 higher-level deletion API.
823 higher-level deletion API.
818 """
824 """
819
825
820
826
821 class ifilestorage(ifileindex, ifiledata, ifilemutation):
827 class ifilestorage(ifileindex, ifiledata, ifilemutation):
822 """Complete storage interface for a single tracked file."""
828 """Complete storage interface for a single tracked file."""
823
829
824 def files():
830 def files():
825 """Obtain paths that are backing storage for this file.
831 """Obtain paths that are backing storage for this file.
826
832
827 TODO this is used heavily by verify code and there should probably
833 TODO this is used heavily by verify code and there should probably
828 be a better API for that.
834 be a better API for that.
829 """
835 """
830
836
831 def storageinfo(
837 def storageinfo(
832 exclusivefiles=False,
838 exclusivefiles=False,
833 sharedfiles=False,
839 sharedfiles=False,
834 revisionscount=False,
840 revisionscount=False,
835 trackedsize=False,
841 trackedsize=False,
836 storedsize=False,
842 storedsize=False,
837 ):
843 ):
838 """Obtain information about storage for this file's data.
844 """Obtain information about storage for this file's data.
839
845
840 Returns a dict describing storage for this tracked path. The keys
846 Returns a dict describing storage for this tracked path. The keys
841 in the dict map to arguments of the same. The arguments are bools
847 in the dict map to arguments of the same. The arguments are bools
842 indicating whether to calculate and obtain that data.
848 indicating whether to calculate and obtain that data.
843
849
844 exclusivefiles
850 exclusivefiles
845 Iterable of (vfs, path) describing files that are exclusively
851 Iterable of (vfs, path) describing files that are exclusively
846 used to back storage for this tracked path.
852 used to back storage for this tracked path.
847
853
848 sharedfiles
854 sharedfiles
849 Iterable of (vfs, path) describing files that are used to back
855 Iterable of (vfs, path) describing files that are used to back
850 storage for this tracked path. Those files may also provide storage
856 storage for this tracked path. Those files may also provide storage
851 for other stored entities.
857 for other stored entities.
852
858
853 revisionscount
859 revisionscount
854 Number of revisions available for retrieval.
860 Number of revisions available for retrieval.
855
861
856 trackedsize
862 trackedsize
857 Total size in bytes of all tracked revisions. This is a sum of the
863 Total size in bytes of all tracked revisions. This is a sum of the
858 length of the fulltext of all revisions.
864 length of the fulltext of all revisions.
859
865
860 storedsize
866 storedsize
861 Total size in bytes used to store data for all tracked revisions.
867 Total size in bytes used to store data for all tracked revisions.
862 This is commonly less than ``trackedsize`` due to internal usage
868 This is commonly less than ``trackedsize`` due to internal usage
863 of deltas rather than fulltext revisions.
869 of deltas rather than fulltext revisions.
864
870
865 Not all storage backends may support all queries are have a reasonable
871 Not all storage backends may support all queries are have a reasonable
866 value to use. In that case, the value should be set to ``None`` and
872 value to use. In that case, the value should be set to ``None`` and
867 callers are expected to handle this special value.
873 callers are expected to handle this special value.
868 """
874 """
869
875
870 def verifyintegrity(state):
876 def verifyintegrity(state):
871 """Verifies the integrity of file storage.
877 """Verifies the integrity of file storage.
872
878
873 ``state`` is a dict holding state of the verifier process. It can be
879 ``state`` is a dict holding state of the verifier process. It can be
874 used to communicate data between invocations of multiple storage
880 used to communicate data between invocations of multiple storage
875 primitives.
881 primitives.
876
882
877 If individual revisions cannot have their revision content resolved,
883 If individual revisions cannot have their revision content resolved,
878 the method is expected to set the ``skipread`` key to a set of nodes
884 the method is expected to set the ``skipread`` key to a set of nodes
879 that encountered problems. If set, the method can also add the node(s)
885 that encountered problems. If set, the method can also add the node(s)
880 to ``safe_renamed`` in order to indicate nodes that may perform the
886 to ``safe_renamed`` in order to indicate nodes that may perform the
881 rename checks with currently accessible data.
887 rename checks with currently accessible data.
882
888
883 The method yields objects conforming to the ``iverifyproblem``
889 The method yields objects conforming to the ``iverifyproblem``
884 interface.
890 interface.
885 """
891 """
886
892
887
893
888 class idirs(interfaceutil.Interface):
894 class idirs(interfaceutil.Interface):
889 """Interface representing a collection of directories from paths.
895 """Interface representing a collection of directories from paths.
890
896
891 This interface is essentially a derived data structure representing
897 This interface is essentially a derived data structure representing
892 directories from a collection of paths.
898 directories from a collection of paths.
893 """
899 """
894
900
895 def addpath(path):
901 def addpath(path):
896 """Add a path to the collection.
902 """Add a path to the collection.
897
903
898 All directories in the path will be added to the collection.
904 All directories in the path will be added to the collection.
899 """
905 """
900
906
901 def delpath(path):
907 def delpath(path):
902 """Remove a path from the collection.
908 """Remove a path from the collection.
903
909
904 If the removal was the last path in a particular directory, the
910 If the removal was the last path in a particular directory, the
905 directory is removed from the collection.
911 directory is removed from the collection.
906 """
912 """
907
913
908 def __iter__():
914 def __iter__():
909 """Iterate over the directories in this collection of paths."""
915 """Iterate over the directories in this collection of paths."""
910
916
911 def __contains__(path):
917 def __contains__(path):
912 """Whether a specific directory is in this collection."""
918 """Whether a specific directory is in this collection."""
913
919
914
920
915 class imanifestdict(interfaceutil.Interface):
921 class imanifestdict(interfaceutil.Interface):
916 """Interface representing a manifest data structure.
922 """Interface representing a manifest data structure.
917
923
918 A manifest is effectively a dict mapping paths to entries. Each entry
924 A manifest is effectively a dict mapping paths to entries. Each entry
919 consists of a binary node and extra flags affecting that entry.
925 consists of a binary node and extra flags affecting that entry.
920 """
926 """
921
927
922 def __getitem__(path):
928 def __getitem__(path):
923 """Returns the binary node value for a path in the manifest.
929 """Returns the binary node value for a path in the manifest.
924
930
925 Raises ``KeyError`` if the path does not exist in the manifest.
931 Raises ``KeyError`` if the path does not exist in the manifest.
926
932
927 Equivalent to ``self.find(path)[0]``.
933 Equivalent to ``self.find(path)[0]``.
928 """
934 """
929
935
930 def find(path):
936 def find(path):
931 """Returns the entry for a path in the manifest.
937 """Returns the entry for a path in the manifest.
932
938
933 Returns a 2-tuple of (node, flags).
939 Returns a 2-tuple of (node, flags).
934
940
935 Raises ``KeyError`` if the path does not exist in the manifest.
941 Raises ``KeyError`` if the path does not exist in the manifest.
936 """
942 """
937
943
938 def __len__():
944 def __len__():
939 """Return the number of entries in the manifest."""
945 """Return the number of entries in the manifest."""
940
946
941 def __nonzero__():
947 def __nonzero__():
942 """Returns True if the manifest has entries, False otherwise."""
948 """Returns True if the manifest has entries, False otherwise."""
943
949
944 __bool__ = __nonzero__
950 __bool__ = __nonzero__
945
951
946 def __setitem__(path, node):
952 def __setitem__(path, node):
947 """Define the node value for a path in the manifest.
953 """Define the node value for a path in the manifest.
948
954
949 If the path is already in the manifest, its flags will be copied to
955 If the path is already in the manifest, its flags will be copied to
950 the new entry.
956 the new entry.
951 """
957 """
952
958
953 def __contains__(path):
959 def __contains__(path):
954 """Whether a path exists in the manifest."""
960 """Whether a path exists in the manifest."""
955
961
956 def __delitem__(path):
962 def __delitem__(path):
957 """Remove a path from the manifest.
963 """Remove a path from the manifest.
958
964
959 Raises ``KeyError`` if the path is not in the manifest.
965 Raises ``KeyError`` if the path is not in the manifest.
960 """
966 """
961
967
962 def __iter__():
968 def __iter__():
963 """Iterate over paths in the manifest."""
969 """Iterate over paths in the manifest."""
964
970
965 def iterkeys():
971 def iterkeys():
966 """Iterate over paths in the manifest."""
972 """Iterate over paths in the manifest."""
967
973
968 def keys():
974 def keys():
969 """Obtain a list of paths in the manifest."""
975 """Obtain a list of paths in the manifest."""
970
976
971 def filesnotin(other, match=None):
977 def filesnotin(other, match=None):
972 """Obtain the set of paths in this manifest but not in another.
978 """Obtain the set of paths in this manifest but not in another.
973
979
974 ``match`` is an optional matcher function to be applied to both
980 ``match`` is an optional matcher function to be applied to both
975 manifests.
981 manifests.
976
982
977 Returns a set of paths.
983 Returns a set of paths.
978 """
984 """
979
985
980 def dirs():
986 def dirs():
981 """Returns an object implementing the ``idirs`` interface."""
987 """Returns an object implementing the ``idirs`` interface."""
982
988
983 def hasdir(dir):
989 def hasdir(dir):
984 """Returns a bool indicating if a directory is in this manifest."""
990 """Returns a bool indicating if a directory is in this manifest."""
985
991
986 def walk(match):
992 def walk(match):
987 """Generator of paths in manifest satisfying a matcher.
993 """Generator of paths in manifest satisfying a matcher.
988
994
989 If the matcher has explicit files listed and they don't exist in
995 If the matcher has explicit files listed and they don't exist in
990 the manifest, ``match.bad()`` is called for each missing file.
996 the manifest, ``match.bad()`` is called for each missing file.
991 """
997 """
992
998
993 def diff(other, match=None, clean=False):
999 def diff(other, match=None, clean=False):
994 """Find differences between this manifest and another.
1000 """Find differences between this manifest and another.
995
1001
996 This manifest is compared to ``other``.
1002 This manifest is compared to ``other``.
997
1003
998 If ``match`` is provided, the two manifests are filtered against this
1004 If ``match`` is provided, the two manifests are filtered against this
999 matcher and only entries satisfying the matcher are compared.
1005 matcher and only entries satisfying the matcher are compared.
1000
1006
1001 If ``clean`` is True, unchanged files are included in the returned
1007 If ``clean`` is True, unchanged files are included in the returned
1002 object.
1008 object.
1003
1009
1004 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1010 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1005 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1011 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1006 represents the node and flags for this manifest and ``(node2, flag2)``
1012 represents the node and flags for this manifest and ``(node2, flag2)``
1007 are the same for the other manifest.
1013 are the same for the other manifest.
1008 """
1014 """
1009
1015
1010 def setflag(path, flag):
1016 def setflag(path, flag):
1011 """Set the flag value for a given path.
1017 """Set the flag value for a given path.
1012
1018
1013 Raises ``KeyError`` if the path is not already in the manifest.
1019 Raises ``KeyError`` if the path is not already in the manifest.
1014 """
1020 """
1015
1021
1016 def get(path, default=None):
1022 def get(path, default=None):
1017 """Obtain the node value for a path or a default value if missing."""
1023 """Obtain the node value for a path or a default value if missing."""
1018
1024
1019 def flags(path):
1025 def flags(path):
1020 """Return the flags value for a path (default: empty bytestring)."""
1026 """Return the flags value for a path (default: empty bytestring)."""
1021
1027
1022 def copy():
1028 def copy():
1023 """Return a copy of this manifest."""
1029 """Return a copy of this manifest."""
1024
1030
1025 def items():
1031 def items():
1026 """Returns an iterable of (path, node) for items in this manifest."""
1032 """Returns an iterable of (path, node) for items in this manifest."""
1027
1033
1028 def iteritems():
1034 def iteritems():
1029 """Identical to items()."""
1035 """Identical to items()."""
1030
1036
1031 def iterentries():
1037 def iterentries():
1032 """Returns an iterable of (path, node, flags) for this manifest.
1038 """Returns an iterable of (path, node, flags) for this manifest.
1033
1039
1034 Similar to ``iteritems()`` except items are a 3-tuple and include
1040 Similar to ``iteritems()`` except items are a 3-tuple and include
1035 flags.
1041 flags.
1036 """
1042 """
1037
1043
1038 def text():
1044 def text():
1039 """Obtain the raw data representation for this manifest.
1045 """Obtain the raw data representation for this manifest.
1040
1046
1041 Result is used to create a manifest revision.
1047 Result is used to create a manifest revision.
1042 """
1048 """
1043
1049
1044 def fastdelta(base, changes):
1050 def fastdelta(base, changes):
1045 """Obtain a delta between this manifest and another given changes.
1051 """Obtain a delta between this manifest and another given changes.
1046
1052
1047 ``base`` in the raw data representation for another manifest.
1053 ``base`` in the raw data representation for another manifest.
1048
1054
1049 ``changes`` is an iterable of ``(path, to_delete)``.
1055 ``changes`` is an iterable of ``(path, to_delete)``.
1050
1056
1051 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1057 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1052 delta between ``base`` and this manifest.
1058 delta between ``base`` and this manifest.
1053
1059
1054 If this manifest implementation can't support ``fastdelta()``,
1060 If this manifest implementation can't support ``fastdelta()``,
1055 raise ``mercurial.manifest.FastdeltaUnavailable``.
1061 raise ``mercurial.manifest.FastdeltaUnavailable``.
1056 """
1062 """
1057
1063
1058
1064
1059 class imanifestrevisionbase(interfaceutil.Interface):
1065 class imanifestrevisionbase(interfaceutil.Interface):
1060 """Base interface representing a single revision of a manifest.
1066 """Base interface representing a single revision of a manifest.
1061
1067
1062 Should not be used as a primary interface: should always be inherited
1068 Should not be used as a primary interface: should always be inherited
1063 as part of a larger interface.
1069 as part of a larger interface.
1064 """
1070 """
1065
1071
1066 def copy():
1072 def copy():
1067 """Obtain a copy of this manifest instance.
1073 """Obtain a copy of this manifest instance.
1068
1074
1069 Returns an object conforming to the ``imanifestrevisionwritable``
1075 Returns an object conforming to the ``imanifestrevisionwritable``
1070 interface. The instance will be associated with the same
1076 interface. The instance will be associated with the same
1071 ``imanifestlog`` collection as this instance.
1077 ``imanifestlog`` collection as this instance.
1072 """
1078 """
1073
1079
1074 def read():
1080 def read():
1075 """Obtain the parsed manifest data structure.
1081 """Obtain the parsed manifest data structure.
1076
1082
1077 The returned object conforms to the ``imanifestdict`` interface.
1083 The returned object conforms to the ``imanifestdict`` interface.
1078 """
1084 """
1079
1085
1080
1086
1081 class imanifestrevisionstored(imanifestrevisionbase):
1087 class imanifestrevisionstored(imanifestrevisionbase):
1082 """Interface representing a manifest revision committed to storage."""
1088 """Interface representing a manifest revision committed to storage."""
1083
1089
1084 def node():
1090 def node():
1085 """The binary node for this manifest."""
1091 """The binary node for this manifest."""
1086
1092
1087 parents = interfaceutil.Attribute(
1093 parents = interfaceutil.Attribute(
1088 """List of binary nodes that are parents for this manifest revision."""
1094 """List of binary nodes that are parents for this manifest revision."""
1089 )
1095 )
1090
1096
1091 def readdelta(shallow=False):
1097 def readdelta(shallow=False):
1092 """Obtain the manifest data structure representing changes from parent.
1098 """Obtain the manifest data structure representing changes from parent.
1093
1099
1094 This manifest is compared to its 1st parent. A new manifest representing
1100 This manifest is compared to its 1st parent. A new manifest representing
1095 those differences is constructed.
1101 those differences is constructed.
1096
1102
1097 The returned object conforms to the ``imanifestdict`` interface.
1103 The returned object conforms to the ``imanifestdict`` interface.
1098 """
1104 """
1099
1105
1100 def readfast(shallow=False):
1106 def readfast(shallow=False):
1101 """Calls either ``read()`` or ``readdelta()``.
1107 """Calls either ``read()`` or ``readdelta()``.
1102
1108
1103 The faster of the two options is called.
1109 The faster of the two options is called.
1104 """
1110 """
1105
1111
1106 def find(key):
1112 def find(key):
1107 """Calls self.read().find(key)``.
1113 """Calls self.read().find(key)``.
1108
1114
1109 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1115 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1110 """
1116 """
1111
1117
1112
1118
1113 class imanifestrevisionwritable(imanifestrevisionbase):
1119 class imanifestrevisionwritable(imanifestrevisionbase):
1114 """Interface representing a manifest revision that can be committed."""
1120 """Interface representing a manifest revision that can be committed."""
1115
1121
1116 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1122 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1117 """Add this revision to storage.
1123 """Add this revision to storage.
1118
1124
1119 Takes a transaction object, the changeset revision number it will
1125 Takes a transaction object, the changeset revision number it will
1120 be associated with, its parent nodes, and lists of added and
1126 be associated with, its parent nodes, and lists of added and
1121 removed paths.
1127 removed paths.
1122
1128
1123 If match is provided, storage can choose not to inspect or write out
1129 If match is provided, storage can choose not to inspect or write out
1124 items that do not match. Storage is still required to be able to provide
1130 items that do not match. Storage is still required to be able to provide
1125 the full manifest in the future for any directories written (these
1131 the full manifest in the future for any directories written (these
1126 manifests should not be "narrowed on disk").
1132 manifests should not be "narrowed on disk").
1127
1133
1128 Returns the binary node of the created revision.
1134 Returns the binary node of the created revision.
1129 """
1135 """
1130
1136
1131
1137
1132 class imanifeststorage(interfaceutil.Interface):
1138 class imanifeststorage(interfaceutil.Interface):
1133 """Storage interface for manifest data."""
1139 """Storage interface for manifest data."""
1134
1140
1135 tree = interfaceutil.Attribute(
1141 tree = interfaceutil.Attribute(
1136 """The path to the directory this manifest tracks.
1142 """The path to the directory this manifest tracks.
1137
1143
1138 The empty bytestring represents the root manifest.
1144 The empty bytestring represents the root manifest.
1139 """
1145 """
1140 )
1146 )
1141
1147
1142 index = interfaceutil.Attribute(
1148 index = interfaceutil.Attribute(
1143 """An ``ifilerevisionssequence`` instance."""
1149 """An ``ifilerevisionssequence`` instance."""
1144 )
1150 )
1145
1151
1146 indexfile = interfaceutil.Attribute(
1152 indexfile = interfaceutil.Attribute(
1147 """Path of revlog index file.
1153 """Path of revlog index file.
1148
1154
1149 TODO this is revlog specific and should not be exposed.
1155 TODO this is revlog specific and should not be exposed.
1150 """
1156 """
1151 )
1157 )
1152
1158
1153 opener = interfaceutil.Attribute(
1159 opener = interfaceutil.Attribute(
1154 """VFS opener to use to access underlying files used for storage.
1160 """VFS opener to use to access underlying files used for storage.
1155
1161
1156 TODO this is revlog specific and should not be exposed.
1162 TODO this is revlog specific and should not be exposed.
1157 """
1163 """
1158 )
1164 )
1159
1165
1160 version = interfaceutil.Attribute(
1166 version = interfaceutil.Attribute(
1161 """Revlog version number.
1167 """Revlog version number.
1162
1168
1163 TODO this is revlog specific and should not be exposed.
1169 TODO this is revlog specific and should not be exposed.
1164 """
1170 """
1165 )
1171 )
1166
1172
1167 _generaldelta = interfaceutil.Attribute(
1173 _generaldelta = interfaceutil.Attribute(
1168 """Whether generaldelta storage is being used.
1174 """Whether generaldelta storage is being used.
1169
1175
1170 TODO this is revlog specific and should not be exposed.
1176 TODO this is revlog specific and should not be exposed.
1171 """
1177 """
1172 )
1178 )
1173
1179
1174 fulltextcache = interfaceutil.Attribute(
1180 fulltextcache = interfaceutil.Attribute(
1175 """Dict with cache of fulltexts.
1181 """Dict with cache of fulltexts.
1176
1182
1177 TODO this doesn't feel appropriate for the storage interface.
1183 TODO this doesn't feel appropriate for the storage interface.
1178 """
1184 """
1179 )
1185 )
1180
1186
1181 def __len__():
1187 def __len__():
1182 """Obtain the number of revisions stored for this manifest."""
1188 """Obtain the number of revisions stored for this manifest."""
1183
1189
1184 def __iter__():
1190 def __iter__():
1185 """Iterate over revision numbers for this manifest."""
1191 """Iterate over revision numbers for this manifest."""
1186
1192
1187 def rev(node):
1193 def rev(node):
1188 """Obtain the revision number given a binary node.
1194 """Obtain the revision number given a binary node.
1189
1195
1190 Raises ``error.LookupError`` if the node is not known.
1196 Raises ``error.LookupError`` if the node is not known.
1191 """
1197 """
1192
1198
1193 def node(rev):
1199 def node(rev):
1194 """Obtain the node value given a revision number.
1200 """Obtain the node value given a revision number.
1195
1201
1196 Raises ``error.LookupError`` if the revision is not known.
1202 Raises ``error.LookupError`` if the revision is not known.
1197 """
1203 """
1198
1204
1199 def lookup(value):
1205 def lookup(value):
1200 """Attempt to resolve a value to a node.
1206 """Attempt to resolve a value to a node.
1201
1207
1202 Value can be a binary node, hex node, revision number, or a bytes
1208 Value can be a binary node, hex node, revision number, or a bytes
1203 that can be converted to an integer.
1209 that can be converted to an integer.
1204
1210
1205 Raises ``error.LookupError`` if a ndoe could not be resolved.
1211 Raises ``error.LookupError`` if a ndoe could not be resolved.
1206 """
1212 """
1207
1213
1208 def parents(node):
1214 def parents(node):
1209 """Returns a 2-tuple of parent nodes for a node.
1215 """Returns a 2-tuple of parent nodes for a node.
1210
1216
1211 Values will be ``nullid`` if the parent is empty.
1217 Values will be ``nullid`` if the parent is empty.
1212 """
1218 """
1213
1219
1214 def parentrevs(rev):
1220 def parentrevs(rev):
1215 """Like parents() but operates on revision numbers."""
1221 """Like parents() but operates on revision numbers."""
1216
1222
1217 def linkrev(rev):
1223 def linkrev(rev):
1218 """Obtain the changeset revision number a revision is linked to."""
1224 """Obtain the changeset revision number a revision is linked to."""
1219
1225
1220 def revision(node, _df=None, raw=False):
1226 def revision(node, _df=None, raw=False):
1221 """Obtain fulltext data for a node."""
1227 """Obtain fulltext data for a node."""
1222
1228
1223 def rawdata(node, _df=None):
1229 def rawdata(node, _df=None):
1224 """Obtain raw data for a node."""
1230 """Obtain raw data for a node."""
1225
1231
1226 def revdiff(rev1, rev2):
1232 def revdiff(rev1, rev2):
1227 """Obtain a delta between two revision numbers.
1233 """Obtain a delta between two revision numbers.
1228
1234
1229 The returned data is the result of ``bdiff.bdiff()`` on the raw
1235 The returned data is the result of ``bdiff.bdiff()`` on the raw
1230 revision data.
1236 revision data.
1231 """
1237 """
1232
1238
1233 def cmp(node, fulltext):
1239 def cmp(node, fulltext):
1234 """Compare fulltext to another revision.
1240 """Compare fulltext to another revision.
1235
1241
1236 Returns True if the fulltext is different from what is stored.
1242 Returns True if the fulltext is different from what is stored.
1237 """
1243 """
1238
1244
1239 def emitrevisions(
1245 def emitrevisions(
1240 nodes,
1246 nodes,
1241 nodesorder=None,
1247 nodesorder=None,
1242 revisiondata=False,
1248 revisiondata=False,
1243 assumehaveparentrevisions=False,
1249 assumehaveparentrevisions=False,
1244 ):
1250 ):
1245 """Produce ``irevisiondelta`` describing revisions.
1251 """Produce ``irevisiondelta`` describing revisions.
1246
1252
1247 See the documentation for ``ifiledata`` for more.
1253 See the documentation for ``ifiledata`` for more.
1248 """
1254 """
1249
1255
1250 def addgroup(
1256 def addgroup(
1251 deltas,
1257 deltas,
1252 linkmapper,
1258 linkmapper,
1253 transaction,
1259 transaction,
1254 addrevisioncb=None,
1260 addrevisioncb=None,
1255 duplicaterevisioncb=None,
1261 duplicaterevisioncb=None,
1256 ):
1262 ):
1257 """Process a series of deltas for storage.
1263 """Process a series of deltas for storage.
1258
1264
1259 See the documentation in ``ifilemutation`` for more.
1265 See the documentation in ``ifilemutation`` for more.
1260 """
1266 """
1261
1267
1262 def rawsize(rev):
1268 def rawsize(rev):
1263 """Obtain the size of tracked data.
1269 """Obtain the size of tracked data.
1264
1270
1265 Is equivalent to ``len(m.rawdata(node))``.
1271 Is equivalent to ``len(m.rawdata(node))``.
1266
1272
1267 TODO this method is only used by upgrade code and may be removed.
1273 TODO this method is only used by upgrade code and may be removed.
1268 """
1274 """
1269
1275
1270 def getstrippoint(minlink):
1276 def getstrippoint(minlink):
1271 """Find minimum revision that must be stripped to strip a linkrev.
1277 """Find minimum revision that must be stripped to strip a linkrev.
1272
1278
1273 See the documentation in ``ifilemutation`` for more.
1279 See the documentation in ``ifilemutation`` for more.
1274 """
1280 """
1275
1281
1276 def strip(minlink, transaction):
1282 def strip(minlink, transaction):
1277 """Remove storage of items starting at a linkrev.
1283 """Remove storage of items starting at a linkrev.
1278
1284
1279 See the documentation in ``ifilemutation`` for more.
1285 See the documentation in ``ifilemutation`` for more.
1280 """
1286 """
1281
1287
1282 def checksize():
1288 def checksize():
1283 """Obtain the expected sizes of backing files.
1289 """Obtain the expected sizes of backing files.
1284
1290
1285 TODO this is used by verify and it should not be part of the interface.
1291 TODO this is used by verify and it should not be part of the interface.
1286 """
1292 """
1287
1293
1288 def files():
1294 def files():
1289 """Obtain paths that are backing storage for this manifest.
1295 """Obtain paths that are backing storage for this manifest.
1290
1296
1291 TODO this is used by verify and there should probably be a better API
1297 TODO this is used by verify and there should probably be a better API
1292 for this functionality.
1298 for this functionality.
1293 """
1299 """
1294
1300
1295 def deltaparent(rev):
1301 def deltaparent(rev):
1296 """Obtain the revision that a revision is delta'd against.
1302 """Obtain the revision that a revision is delta'd against.
1297
1303
1298 TODO delta encoding is an implementation detail of storage and should
1304 TODO delta encoding is an implementation detail of storage and should
1299 not be exposed to the storage interface.
1305 not be exposed to the storage interface.
1300 """
1306 """
1301
1307
1302 def clone(tr, dest, **kwargs):
1308 def clone(tr, dest, **kwargs):
1303 """Clone this instance to another."""
1309 """Clone this instance to another."""
1304
1310
1305 def clearcaches(clear_persisted_data=False):
1311 def clearcaches(clear_persisted_data=False):
1306 """Clear any caches associated with this instance."""
1312 """Clear any caches associated with this instance."""
1307
1313
1308 def dirlog(d):
1314 def dirlog(d):
1309 """Obtain a manifest storage instance for a tree."""
1315 """Obtain a manifest storage instance for a tree."""
1310
1316
1311 def add(
1317 def add(
1312 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1318 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1313 ):
1319 ):
1314 """Add a revision to storage.
1320 """Add a revision to storage.
1315
1321
1316 ``m`` is an object conforming to ``imanifestdict``.
1322 ``m`` is an object conforming to ``imanifestdict``.
1317
1323
1318 ``link`` is the linkrev revision number.
1324 ``link`` is the linkrev revision number.
1319
1325
1320 ``p1`` and ``p2`` are the parent revision numbers.
1326 ``p1`` and ``p2`` are the parent revision numbers.
1321
1327
1322 ``added`` and ``removed`` are iterables of added and removed paths,
1328 ``added`` and ``removed`` are iterables of added and removed paths,
1323 respectively.
1329 respectively.
1324
1330
1325 ``readtree`` is a function that can be used to read the child tree(s)
1331 ``readtree`` is a function that can be used to read the child tree(s)
1326 when recursively writing the full tree structure when using
1332 when recursively writing the full tree structure when using
1327 treemanifets.
1333 treemanifets.
1328
1334
1329 ``match`` is a matcher that can be used to hint to storage that not all
1335 ``match`` is a matcher that can be used to hint to storage that not all
1330 paths must be inspected; this is an optimization and can be safely
1336 paths must be inspected; this is an optimization and can be safely
1331 ignored. Note that the storage must still be able to reproduce a full
1337 ignored. Note that the storage must still be able to reproduce a full
1332 manifest including files that did not match.
1338 manifest including files that did not match.
1333 """
1339 """
1334
1340
1335 def storageinfo(
1341 def storageinfo(
1336 exclusivefiles=False,
1342 exclusivefiles=False,
1337 sharedfiles=False,
1343 sharedfiles=False,
1338 revisionscount=False,
1344 revisionscount=False,
1339 trackedsize=False,
1345 trackedsize=False,
1340 storedsize=False,
1346 storedsize=False,
1341 ):
1347 ):
1342 """Obtain information about storage for this manifest's data.
1348 """Obtain information about storage for this manifest's data.
1343
1349
1344 See ``ifilestorage.storageinfo()`` for a description of this method.
1350 See ``ifilestorage.storageinfo()`` for a description of this method.
1345 This one behaves the same way, except for manifest data.
1351 This one behaves the same way, except for manifest data.
1346 """
1352 """
1347
1353
1348
1354
1349 class imanifestlog(interfaceutil.Interface):
1355 class imanifestlog(interfaceutil.Interface):
1350 """Interface representing a collection of manifest snapshots.
1356 """Interface representing a collection of manifest snapshots.
1351
1357
1352 Represents the root manifest in a repository.
1358 Represents the root manifest in a repository.
1353
1359
1354 Also serves as a means to access nested tree manifests and to cache
1360 Also serves as a means to access nested tree manifests and to cache
1355 tree manifests.
1361 tree manifests.
1356 """
1362 """
1357
1363
1358 def __getitem__(node):
1364 def __getitem__(node):
1359 """Obtain a manifest instance for a given binary node.
1365 """Obtain a manifest instance for a given binary node.
1360
1366
1361 Equivalent to calling ``self.get('', node)``.
1367 Equivalent to calling ``self.get('', node)``.
1362
1368
1363 The returned object conforms to the ``imanifestrevisionstored``
1369 The returned object conforms to the ``imanifestrevisionstored``
1364 interface.
1370 interface.
1365 """
1371 """
1366
1372
1367 def get(tree, node, verify=True):
1373 def get(tree, node, verify=True):
1368 """Retrieve the manifest instance for a given directory and binary node.
1374 """Retrieve the manifest instance for a given directory and binary node.
1369
1375
1370 ``node`` always refers to the node of the root manifest (which will be
1376 ``node`` always refers to the node of the root manifest (which will be
1371 the only manifest if flat manifests are being used).
1377 the only manifest if flat manifests are being used).
1372
1378
1373 If ``tree`` is the empty string, the root manifest is returned.
1379 If ``tree`` is the empty string, the root manifest is returned.
1374 Otherwise the manifest for the specified directory will be returned
1380 Otherwise the manifest for the specified directory will be returned
1375 (requires tree manifests).
1381 (requires tree manifests).
1376
1382
1377 If ``verify`` is True, ``LookupError`` is raised if the node is not
1383 If ``verify`` is True, ``LookupError`` is raised if the node is not
1378 known.
1384 known.
1379
1385
1380 The returned object conforms to the ``imanifestrevisionstored``
1386 The returned object conforms to the ``imanifestrevisionstored``
1381 interface.
1387 interface.
1382 """
1388 """
1383
1389
1384 def getstorage(tree):
1390 def getstorage(tree):
1385 """Retrieve an interface to storage for a particular tree.
1391 """Retrieve an interface to storage for a particular tree.
1386
1392
1387 If ``tree`` is the empty bytestring, storage for the root manifest will
1393 If ``tree`` is the empty bytestring, storage for the root manifest will
1388 be returned. Otherwise storage for a tree manifest is returned.
1394 be returned. Otherwise storage for a tree manifest is returned.
1389
1395
1390 TODO formalize interface for returned object.
1396 TODO formalize interface for returned object.
1391 """
1397 """
1392
1398
1393 def clearcaches():
1399 def clearcaches():
1394 """Clear caches associated with this collection."""
1400 """Clear caches associated with this collection."""
1395
1401
1396 def rev(node):
1402 def rev(node):
1397 """Obtain the revision number for a binary node.
1403 """Obtain the revision number for a binary node.
1398
1404
1399 Raises ``error.LookupError`` if the node is not known.
1405 Raises ``error.LookupError`` if the node is not known.
1400 """
1406 """
1401
1407
1402 def update_caches(transaction):
1408 def update_caches(transaction):
1403 """update whatever cache are relevant for the used storage."""
1409 """update whatever cache are relevant for the used storage."""
1404
1410
1405
1411
1406 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1412 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1407 """Local repository sub-interface providing access to tracked file storage.
1413 """Local repository sub-interface providing access to tracked file storage.
1408
1414
1409 This interface defines how a repository accesses storage for a single
1415 This interface defines how a repository accesses storage for a single
1410 tracked file path.
1416 tracked file path.
1411 """
1417 """
1412
1418
1413 def file(f):
1419 def file(f):
1414 """Obtain a filelog for a tracked path.
1420 """Obtain a filelog for a tracked path.
1415
1421
1416 The returned type conforms to the ``ifilestorage`` interface.
1422 The returned type conforms to the ``ifilestorage`` interface.
1417 """
1423 """
1418
1424
1419
1425
1420 class ilocalrepositorymain(interfaceutil.Interface):
1426 class ilocalrepositorymain(interfaceutil.Interface):
1421 """Main interface for local repositories.
1427 """Main interface for local repositories.
1422
1428
1423 This currently captures the reality of things - not how things should be.
1429 This currently captures the reality of things - not how things should be.
1424 """
1430 """
1425
1431
1426 supportedformats = interfaceutil.Attribute(
1432 supportedformats = interfaceutil.Attribute(
1427 """Set of requirements that apply to stream clone.
1433 """Set of requirements that apply to stream clone.
1428
1434
1429 This is actually a class attribute and is shared among all instances.
1435 This is actually a class attribute and is shared among all instances.
1430 """
1436 """
1431 )
1437 )
1432
1438
1433 supported = interfaceutil.Attribute(
1439 supported = interfaceutil.Attribute(
1434 """Set of requirements that this repo is capable of opening."""
1440 """Set of requirements that this repo is capable of opening."""
1435 )
1441 )
1436
1442
1437 requirements = interfaceutil.Attribute(
1443 requirements = interfaceutil.Attribute(
1438 """Set of requirements this repo uses."""
1444 """Set of requirements this repo uses."""
1439 )
1445 )
1440
1446
1441 features = interfaceutil.Attribute(
1447 features = interfaceutil.Attribute(
1442 """Set of "features" this repository supports.
1448 """Set of "features" this repository supports.
1443
1449
1444 A "feature" is a loosely-defined term. It can refer to a feature
1450 A "feature" is a loosely-defined term. It can refer to a feature
1445 in the classical sense or can describe an implementation detail
1451 in the classical sense or can describe an implementation detail
1446 of the repository. For example, a ``readonly`` feature may denote
1452 of the repository. For example, a ``readonly`` feature may denote
1447 the repository as read-only. Or a ``revlogfilestore`` feature may
1453 the repository as read-only. Or a ``revlogfilestore`` feature may
1448 denote that the repository is using revlogs for file storage.
1454 denote that the repository is using revlogs for file storage.
1449
1455
1450 The intent of features is to provide a machine-queryable mechanism
1456 The intent of features is to provide a machine-queryable mechanism
1451 for repo consumers to test for various repository characteristics.
1457 for repo consumers to test for various repository characteristics.
1452
1458
1453 Features are similar to ``requirements``. The main difference is that
1459 Features are similar to ``requirements``. The main difference is that
1454 requirements are stored on-disk and represent requirements to open the
1460 requirements are stored on-disk and represent requirements to open the
1455 repository. Features are more run-time capabilities of the repository
1461 repository. Features are more run-time capabilities of the repository
1456 and more granular capabilities (which may be derived from requirements).
1462 and more granular capabilities (which may be derived from requirements).
1457 """
1463 """
1458 )
1464 )
1459
1465
1460 filtername = interfaceutil.Attribute(
1466 filtername = interfaceutil.Attribute(
1461 """Name of the repoview that is active on this repo."""
1467 """Name of the repoview that is active on this repo."""
1462 )
1468 )
1463
1469
1464 wvfs = interfaceutil.Attribute(
1470 wvfs = interfaceutil.Attribute(
1465 """VFS used to access the working directory."""
1471 """VFS used to access the working directory."""
1466 )
1472 )
1467
1473
1468 vfs = interfaceutil.Attribute(
1474 vfs = interfaceutil.Attribute(
1469 """VFS rooted at the .hg directory.
1475 """VFS rooted at the .hg directory.
1470
1476
1471 Used to access repository data not in the store.
1477 Used to access repository data not in the store.
1472 """
1478 """
1473 )
1479 )
1474
1480
1475 svfs = interfaceutil.Attribute(
1481 svfs = interfaceutil.Attribute(
1476 """VFS rooted at the store.
1482 """VFS rooted at the store.
1477
1483
1478 Used to access repository data in the store. Typically .hg/store.
1484 Used to access repository data in the store. Typically .hg/store.
1479 But can point elsewhere if the store is shared.
1485 But can point elsewhere if the store is shared.
1480 """
1486 """
1481 )
1487 )
1482
1488
1483 root = interfaceutil.Attribute(
1489 root = interfaceutil.Attribute(
1484 """Path to the root of the working directory."""
1490 """Path to the root of the working directory."""
1485 )
1491 )
1486
1492
1487 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1493 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1488
1494
1489 origroot = interfaceutil.Attribute(
1495 origroot = interfaceutil.Attribute(
1490 """The filesystem path that was used to construct the repo."""
1496 """The filesystem path that was used to construct the repo."""
1491 )
1497 )
1492
1498
1493 auditor = interfaceutil.Attribute(
1499 auditor = interfaceutil.Attribute(
1494 """A pathauditor for the working directory.
1500 """A pathauditor for the working directory.
1495
1501
1496 This checks if a path refers to a nested repository.
1502 This checks if a path refers to a nested repository.
1497
1503
1498 Operates on the filesystem.
1504 Operates on the filesystem.
1499 """
1505 """
1500 )
1506 )
1501
1507
1502 nofsauditor = interfaceutil.Attribute(
1508 nofsauditor = interfaceutil.Attribute(
1503 """A pathauditor for the working directory.
1509 """A pathauditor for the working directory.
1504
1510
1505 This is like ``auditor`` except it doesn't do filesystem checks.
1511 This is like ``auditor`` except it doesn't do filesystem checks.
1506 """
1512 """
1507 )
1513 )
1508
1514
1509 baseui = interfaceutil.Attribute(
1515 baseui = interfaceutil.Attribute(
1510 """Original ui instance passed into constructor."""
1516 """Original ui instance passed into constructor."""
1511 )
1517 )
1512
1518
1513 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1519 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1514
1520
1515 sharedpath = interfaceutil.Attribute(
1521 sharedpath = interfaceutil.Attribute(
1516 """Path to the .hg directory of the repo this repo was shared from."""
1522 """Path to the .hg directory of the repo this repo was shared from."""
1517 )
1523 )
1518
1524
1519 store = interfaceutil.Attribute("""A store instance.""")
1525 store = interfaceutil.Attribute("""A store instance.""")
1520
1526
1521 spath = interfaceutil.Attribute("""Path to the store.""")
1527 spath = interfaceutil.Attribute("""Path to the store.""")
1522
1528
1523 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1529 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1524
1530
1525 cachevfs = interfaceutil.Attribute(
1531 cachevfs = interfaceutil.Attribute(
1526 """A VFS used to access the cache directory.
1532 """A VFS used to access the cache directory.
1527
1533
1528 Typically .hg/cache.
1534 Typically .hg/cache.
1529 """
1535 """
1530 )
1536 )
1531
1537
1532 wcachevfs = interfaceutil.Attribute(
1538 wcachevfs = interfaceutil.Attribute(
1533 """A VFS used to access the cache directory dedicated to working copy
1539 """A VFS used to access the cache directory dedicated to working copy
1534
1540
1535 Typically .hg/wcache.
1541 Typically .hg/wcache.
1536 """
1542 """
1537 )
1543 )
1538
1544
1539 filteredrevcache = interfaceutil.Attribute(
1545 filteredrevcache = interfaceutil.Attribute(
1540 """Holds sets of revisions to be filtered."""
1546 """Holds sets of revisions to be filtered."""
1541 )
1547 )
1542
1548
1543 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1549 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1544
1550
1545 filecopiesmode = interfaceutil.Attribute(
1551 filecopiesmode = interfaceutil.Attribute(
1546 """The way files copies should be dealt with in this repo."""
1552 """The way files copies should be dealt with in this repo."""
1547 )
1553 )
1548
1554
1549 def close():
1555 def close():
1550 """Close the handle on this repository."""
1556 """Close the handle on this repository."""
1551
1557
1552 def peer():
1558 def peer():
1553 """Obtain an object conforming to the ``peer`` interface."""
1559 """Obtain an object conforming to the ``peer`` interface."""
1554
1560
1555 def unfiltered():
1561 def unfiltered():
1556 """Obtain an unfiltered/raw view of this repo."""
1562 """Obtain an unfiltered/raw view of this repo."""
1557
1563
1558 def filtered(name, visibilityexceptions=None):
1564 def filtered(name, visibilityexceptions=None):
1559 """Obtain a named view of this repository."""
1565 """Obtain a named view of this repository."""
1560
1566
1561 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1567 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1562
1568
1563 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1569 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1564
1570
1565 manifestlog = interfaceutil.Attribute(
1571 manifestlog = interfaceutil.Attribute(
1566 """An instance conforming to the ``imanifestlog`` interface.
1572 """An instance conforming to the ``imanifestlog`` interface.
1567
1573
1568 Provides access to manifests for the repository.
1574 Provides access to manifests for the repository.
1569 """
1575 """
1570 )
1576 )
1571
1577
1572 dirstate = interfaceutil.Attribute("""Working directory state.""")
1578 dirstate = interfaceutil.Attribute("""Working directory state.""")
1573
1579
1574 narrowpats = interfaceutil.Attribute(
1580 narrowpats = interfaceutil.Attribute(
1575 """Matcher patterns for this repository's narrowspec."""
1581 """Matcher patterns for this repository's narrowspec."""
1576 )
1582 )
1577
1583
1578 def narrowmatch(match=None, includeexact=False):
1584 def narrowmatch(match=None, includeexact=False):
1579 """Obtain a matcher for the narrowspec."""
1585 """Obtain a matcher for the narrowspec."""
1580
1586
1581 def setnarrowpats(newincludes, newexcludes):
1587 def setnarrowpats(newincludes, newexcludes):
1582 """Define the narrowspec for this repository."""
1588 """Define the narrowspec for this repository."""
1583
1589
1584 def __getitem__(changeid):
1590 def __getitem__(changeid):
1585 """Try to resolve a changectx."""
1591 """Try to resolve a changectx."""
1586
1592
1587 def __contains__(changeid):
1593 def __contains__(changeid):
1588 """Whether a changeset exists."""
1594 """Whether a changeset exists."""
1589
1595
1590 def __nonzero__():
1596 def __nonzero__():
1591 """Always returns True."""
1597 """Always returns True."""
1592 return True
1598 return True
1593
1599
1594 __bool__ = __nonzero__
1600 __bool__ = __nonzero__
1595
1601
1596 def __len__():
1602 def __len__():
1597 """Returns the number of changesets in the repo."""
1603 """Returns the number of changesets in the repo."""
1598
1604
1599 def __iter__():
1605 def __iter__():
1600 """Iterate over revisions in the changelog."""
1606 """Iterate over revisions in the changelog."""
1601
1607
1602 def revs(expr, *args):
1608 def revs(expr, *args):
1603 """Evaluate a revset.
1609 """Evaluate a revset.
1604
1610
1605 Emits revisions.
1611 Emits revisions.
1606 """
1612 """
1607
1613
1608 def set(expr, *args):
1614 def set(expr, *args):
1609 """Evaluate a revset.
1615 """Evaluate a revset.
1610
1616
1611 Emits changectx instances.
1617 Emits changectx instances.
1612 """
1618 """
1613
1619
1614 def anyrevs(specs, user=False, localalias=None):
1620 def anyrevs(specs, user=False, localalias=None):
1615 """Find revisions matching one of the given revsets."""
1621 """Find revisions matching one of the given revsets."""
1616
1622
1617 def url():
1623 def url():
1618 """Returns a string representing the location of this repo."""
1624 """Returns a string representing the location of this repo."""
1619
1625
1620 def hook(name, throw=False, **args):
1626 def hook(name, throw=False, **args):
1621 """Call a hook."""
1627 """Call a hook."""
1622
1628
1623 def tags():
1629 def tags():
1624 """Return a mapping of tag to node."""
1630 """Return a mapping of tag to node."""
1625
1631
1626 def tagtype(tagname):
1632 def tagtype(tagname):
1627 """Return the type of a given tag."""
1633 """Return the type of a given tag."""
1628
1634
1629 def tagslist():
1635 def tagslist():
1630 """Return a list of tags ordered by revision."""
1636 """Return a list of tags ordered by revision."""
1631
1637
1632 def nodetags(node):
1638 def nodetags(node):
1633 """Return the tags associated with a node."""
1639 """Return the tags associated with a node."""
1634
1640
1635 def nodebookmarks(node):
1641 def nodebookmarks(node):
1636 """Return the list of bookmarks pointing to the specified node."""
1642 """Return the list of bookmarks pointing to the specified node."""
1637
1643
1638 def branchmap():
1644 def branchmap():
1639 """Return a mapping of branch to heads in that branch."""
1645 """Return a mapping of branch to heads in that branch."""
1640
1646
1641 def revbranchcache():
1647 def revbranchcache():
1642 pass
1648 pass
1643
1649
1644 def register_changeset(rev, changelogrevision):
1650 def register_changeset(rev, changelogrevision):
1645 """Extension point for caches for new nodes.
1651 """Extension point for caches for new nodes.
1646
1652
1647 Multiple consumers are expected to need parts of the changelogrevision,
1653 Multiple consumers are expected to need parts of the changelogrevision,
1648 so it is provided as optimization to avoid duplicate lookups. A simple
1654 so it is provided as optimization to avoid duplicate lookups. A simple
1649 cache would be fragile when other revisions are accessed, too."""
1655 cache would be fragile when other revisions are accessed, too."""
1650 pass
1656 pass
1651
1657
1652 def branchtip(branchtip, ignoremissing=False):
1658 def branchtip(branchtip, ignoremissing=False):
1653 """Return the tip node for a given branch."""
1659 """Return the tip node for a given branch."""
1654
1660
1655 def lookup(key):
1661 def lookup(key):
1656 """Resolve the node for a revision."""
1662 """Resolve the node for a revision."""
1657
1663
1658 def lookupbranch(key):
1664 def lookupbranch(key):
1659 """Look up the branch name of the given revision or branch name."""
1665 """Look up the branch name of the given revision or branch name."""
1660
1666
1661 def known(nodes):
1667 def known(nodes):
1662 """Determine whether a series of nodes is known.
1668 """Determine whether a series of nodes is known.
1663
1669
1664 Returns a list of bools.
1670 Returns a list of bools.
1665 """
1671 """
1666
1672
1667 def local():
1673 def local():
1668 """Whether the repository is local."""
1674 """Whether the repository is local."""
1669 return True
1675 return True
1670
1676
1671 def publishing():
1677 def publishing():
1672 """Whether the repository is a publishing repository."""
1678 """Whether the repository is a publishing repository."""
1673
1679
1674 def cancopy():
1680 def cancopy():
1675 pass
1681 pass
1676
1682
1677 def shared():
1683 def shared():
1678 """The type of shared repository or None."""
1684 """The type of shared repository or None."""
1679
1685
1680 def wjoin(f, *insidef):
1686 def wjoin(f, *insidef):
1681 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1687 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1682
1688
1683 def setparents(p1, p2):
1689 def setparents(p1, p2):
1684 """Set the parent nodes of the working directory."""
1690 """Set the parent nodes of the working directory."""
1685
1691
1686 def filectx(path, changeid=None, fileid=None):
1692 def filectx(path, changeid=None, fileid=None):
1687 """Obtain a filectx for the given file revision."""
1693 """Obtain a filectx for the given file revision."""
1688
1694
1689 def getcwd():
1695 def getcwd():
1690 """Obtain the current working directory from the dirstate."""
1696 """Obtain the current working directory from the dirstate."""
1691
1697
1692 def pathto(f, cwd=None):
1698 def pathto(f, cwd=None):
1693 """Obtain the relative path to a file."""
1699 """Obtain the relative path to a file."""
1694
1700
1695 def adddatafilter(name, fltr):
1701 def adddatafilter(name, fltr):
1696 pass
1702 pass
1697
1703
1698 def wread(filename):
1704 def wread(filename):
1699 """Read a file from wvfs, using data filters."""
1705 """Read a file from wvfs, using data filters."""
1700
1706
1701 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1707 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1702 """Write data to a file in the wvfs, using data filters."""
1708 """Write data to a file in the wvfs, using data filters."""
1703
1709
1704 def wwritedata(filename, data):
1710 def wwritedata(filename, data):
1705 """Resolve data for writing to the wvfs, using data filters."""
1711 """Resolve data for writing to the wvfs, using data filters."""
1706
1712
1707 def currenttransaction():
1713 def currenttransaction():
1708 """Obtain the current transaction instance or None."""
1714 """Obtain the current transaction instance or None."""
1709
1715
1710 def transaction(desc, report=None):
1716 def transaction(desc, report=None):
1711 """Open a new transaction to write to the repository."""
1717 """Open a new transaction to write to the repository."""
1712
1718
1713 def undofiles():
1719 def undofiles():
1714 """Returns a list of (vfs, path) for files to undo transactions."""
1720 """Returns a list of (vfs, path) for files to undo transactions."""
1715
1721
1716 def recover():
1722 def recover():
1717 """Roll back an interrupted transaction."""
1723 """Roll back an interrupted transaction."""
1718
1724
1719 def rollback(dryrun=False, force=False):
1725 def rollback(dryrun=False, force=False):
1720 """Undo the last transaction.
1726 """Undo the last transaction.
1721
1727
1722 DANGEROUS.
1728 DANGEROUS.
1723 """
1729 """
1724
1730
1725 def updatecaches(tr=None, full=False):
1731 def updatecaches(tr=None, full=False):
1726 """Warm repo caches."""
1732 """Warm repo caches."""
1727
1733
1728 def invalidatecaches():
1734 def invalidatecaches():
1729 """Invalidate cached data due to the repository mutating."""
1735 """Invalidate cached data due to the repository mutating."""
1730
1736
1731 def invalidatevolatilesets():
1737 def invalidatevolatilesets():
1732 pass
1738 pass
1733
1739
1734 def invalidatedirstate():
1740 def invalidatedirstate():
1735 """Invalidate the dirstate."""
1741 """Invalidate the dirstate."""
1736
1742
1737 def invalidate(clearfilecache=False):
1743 def invalidate(clearfilecache=False):
1738 pass
1744 pass
1739
1745
1740 def invalidateall():
1746 def invalidateall():
1741 pass
1747 pass
1742
1748
1743 def lock(wait=True):
1749 def lock(wait=True):
1744 """Lock the repository store and return a lock instance."""
1750 """Lock the repository store and return a lock instance."""
1745
1751
1746 def wlock(wait=True):
1752 def wlock(wait=True):
1747 """Lock the non-store parts of the repository."""
1753 """Lock the non-store parts of the repository."""
1748
1754
1749 def currentwlock():
1755 def currentwlock():
1750 """Return the wlock if it's held or None."""
1756 """Return the wlock if it's held or None."""
1751
1757
1752 def checkcommitpatterns(wctx, match, status, fail):
1758 def checkcommitpatterns(wctx, match, status, fail):
1753 pass
1759 pass
1754
1760
1755 def commit(
1761 def commit(
1756 text=b'',
1762 text=b'',
1757 user=None,
1763 user=None,
1758 date=None,
1764 date=None,
1759 match=None,
1765 match=None,
1760 force=False,
1766 force=False,
1761 editor=False,
1767 editor=False,
1762 extra=None,
1768 extra=None,
1763 ):
1769 ):
1764 """Add a new revision to the repository."""
1770 """Add a new revision to the repository."""
1765
1771
1766 def commitctx(ctx, error=False, origctx=None):
1772 def commitctx(ctx, error=False, origctx=None):
1767 """Commit a commitctx instance to the repository."""
1773 """Commit a commitctx instance to the repository."""
1768
1774
1769 def destroying():
1775 def destroying():
1770 """Inform the repository that nodes are about to be destroyed."""
1776 """Inform the repository that nodes are about to be destroyed."""
1771
1777
1772 def destroyed():
1778 def destroyed():
1773 """Inform the repository that nodes have been destroyed."""
1779 """Inform the repository that nodes have been destroyed."""
1774
1780
1775 def status(
1781 def status(
1776 node1=b'.',
1782 node1=b'.',
1777 node2=None,
1783 node2=None,
1778 match=None,
1784 match=None,
1779 ignored=False,
1785 ignored=False,
1780 clean=False,
1786 clean=False,
1781 unknown=False,
1787 unknown=False,
1782 listsubrepos=False,
1788 listsubrepos=False,
1783 ):
1789 ):
1784 """Convenience method to call repo[x].status()."""
1790 """Convenience method to call repo[x].status()."""
1785
1791
1786 def addpostdsstatus(ps):
1792 def addpostdsstatus(ps):
1787 pass
1793 pass
1788
1794
1789 def postdsstatus():
1795 def postdsstatus():
1790 pass
1796 pass
1791
1797
1792 def clearpostdsstatus():
1798 def clearpostdsstatus():
1793 pass
1799 pass
1794
1800
1795 def heads(start=None):
1801 def heads(start=None):
1796 """Obtain list of nodes that are DAG heads."""
1802 """Obtain list of nodes that are DAG heads."""
1797
1803
1798 def branchheads(branch=None, start=None, closed=False):
1804 def branchheads(branch=None, start=None, closed=False):
1799 pass
1805 pass
1800
1806
1801 def branches(nodes):
1807 def branches(nodes):
1802 pass
1808 pass
1803
1809
1804 def between(pairs):
1810 def between(pairs):
1805 pass
1811 pass
1806
1812
1807 def checkpush(pushop):
1813 def checkpush(pushop):
1808 pass
1814 pass
1809
1815
1810 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1816 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1811
1817
1812 def pushkey(namespace, key, old, new):
1818 def pushkey(namespace, key, old, new):
1813 pass
1819 pass
1814
1820
1815 def listkeys(namespace):
1821 def listkeys(namespace):
1816 pass
1822 pass
1817
1823
1818 def debugwireargs(one, two, three=None, four=None, five=None):
1824 def debugwireargs(one, two, three=None, four=None, five=None):
1819 pass
1825 pass
1820
1826
1821 def savecommitmessage(text):
1827 def savecommitmessage(text):
1822 pass
1828 pass
1823
1829
1824
1830
1825 class completelocalrepository(
1831 class completelocalrepository(
1826 ilocalrepositorymain, ilocalrepositoryfilestorage
1832 ilocalrepositorymain, ilocalrepositoryfilestorage
1827 ):
1833 ):
1828 """Complete interface for a local repository."""
1834 """Complete interface for a local repository."""
1829
1835
1830
1836
1831 class iwireprotocolcommandcacher(interfaceutil.Interface):
1837 class iwireprotocolcommandcacher(interfaceutil.Interface):
1832 """Represents a caching backend for wire protocol commands.
1838 """Represents a caching backend for wire protocol commands.
1833
1839
1834 Wire protocol version 2 supports transparent caching of many commands.
1840 Wire protocol version 2 supports transparent caching of many commands.
1835 To leverage this caching, servers can activate objects that cache
1841 To leverage this caching, servers can activate objects that cache
1836 command responses. Objects handle both cache writing and reading.
1842 command responses. Objects handle both cache writing and reading.
1837 This interface defines how that response caching mechanism works.
1843 This interface defines how that response caching mechanism works.
1838
1844
1839 Wire protocol version 2 commands emit a series of objects that are
1845 Wire protocol version 2 commands emit a series of objects that are
1840 serialized and sent to the client. The caching layer exists between
1846 serialized and sent to the client. The caching layer exists between
1841 the invocation of the command function and the sending of its output
1847 the invocation of the command function and the sending of its output
1842 objects to an output layer.
1848 objects to an output layer.
1843
1849
1844 Instances of this interface represent a binding to a cache that
1850 Instances of this interface represent a binding to a cache that
1845 can serve a response (in place of calling a command function) and/or
1851 can serve a response (in place of calling a command function) and/or
1846 write responses to a cache for subsequent use.
1852 write responses to a cache for subsequent use.
1847
1853
1848 When a command request arrives, the following happens with regards
1854 When a command request arrives, the following happens with regards
1849 to this interface:
1855 to this interface:
1850
1856
1851 1. The server determines whether the command request is cacheable.
1857 1. The server determines whether the command request is cacheable.
1852 2. If it is, an instance of this interface is spawned.
1858 2. If it is, an instance of this interface is spawned.
1853 3. The cacher is activated in a context manager (``__enter__`` is called).
1859 3. The cacher is activated in a context manager (``__enter__`` is called).
1854 4. A cache *key* for that request is derived. This will call the
1860 4. A cache *key* for that request is derived. This will call the
1855 instance's ``adjustcachekeystate()`` method so the derivation
1861 instance's ``adjustcachekeystate()`` method so the derivation
1856 can be influenced.
1862 can be influenced.
1857 5. The cacher is informed of the derived cache key via a call to
1863 5. The cacher is informed of the derived cache key via a call to
1858 ``setcachekey()``.
1864 ``setcachekey()``.
1859 6. The cacher's ``lookup()`` method is called to test for presence of
1865 6. The cacher's ``lookup()`` method is called to test for presence of
1860 the derived key in the cache.
1866 the derived key in the cache.
1861 7. If ``lookup()`` returns a hit, that cached result is used in place
1867 7. If ``lookup()`` returns a hit, that cached result is used in place
1862 of invoking the command function. ``__exit__`` is called and the instance
1868 of invoking the command function. ``__exit__`` is called and the instance
1863 is discarded.
1869 is discarded.
1864 8. The command function is invoked.
1870 8. The command function is invoked.
1865 9. ``onobject()`` is called for each object emitted by the command
1871 9. ``onobject()`` is called for each object emitted by the command
1866 function.
1872 function.
1867 10. After the final object is seen, ``onfinished()`` is called.
1873 10. After the final object is seen, ``onfinished()`` is called.
1868 11. ``__exit__`` is called to signal the end of use of the instance.
1874 11. ``__exit__`` is called to signal the end of use of the instance.
1869
1875
1870 Cache *key* derivation can be influenced by the instance.
1876 Cache *key* derivation can be influenced by the instance.
1871
1877
1872 Cache keys are initially derived by a deterministic representation of
1878 Cache keys are initially derived by a deterministic representation of
1873 the command request. This includes the command name, arguments, protocol
1879 the command request. This includes the command name, arguments, protocol
1874 version, etc. This initial key derivation is performed by CBOR-encoding a
1880 version, etc. This initial key derivation is performed by CBOR-encoding a
1875 data structure and feeding that output into a hasher.
1881 data structure and feeding that output into a hasher.
1876
1882
1877 Instances of this interface can influence this initial key derivation
1883 Instances of this interface can influence this initial key derivation
1878 via ``adjustcachekeystate()``.
1884 via ``adjustcachekeystate()``.
1879
1885
1880 The instance is informed of the derived cache key via a call to
1886 The instance is informed of the derived cache key via a call to
1881 ``setcachekey()``. The instance must store the key locally so it can
1887 ``setcachekey()``. The instance must store the key locally so it can
1882 be consulted on subsequent operations that may require it.
1888 be consulted on subsequent operations that may require it.
1883
1889
1884 When constructed, the instance has access to a callable that can be used
1890 When constructed, the instance has access to a callable that can be used
1885 for encoding response objects. This callable receives as its single
1891 for encoding response objects. This callable receives as its single
1886 argument an object emitted by a command function. It returns an iterable
1892 argument an object emitted by a command function. It returns an iterable
1887 of bytes chunks representing the encoded object. Unless the cacher is
1893 of bytes chunks representing the encoded object. Unless the cacher is
1888 caching native Python objects in memory or has a way of reconstructing
1894 caching native Python objects in memory or has a way of reconstructing
1889 the original Python objects, implementations typically call this function
1895 the original Python objects, implementations typically call this function
1890 to produce bytes from the output objects and then store those bytes in
1896 to produce bytes from the output objects and then store those bytes in
1891 the cache. When it comes time to re-emit those bytes, they are wrapped
1897 the cache. When it comes time to re-emit those bytes, they are wrapped
1892 in a ``wireprototypes.encodedresponse`` instance to tell the output
1898 in a ``wireprototypes.encodedresponse`` instance to tell the output
1893 layer that they are pre-encoded.
1899 layer that they are pre-encoded.
1894
1900
1895 When receiving the objects emitted by the command function, instances
1901 When receiving the objects emitted by the command function, instances
1896 can choose what to do with those objects. The simplest thing to do is
1902 can choose what to do with those objects. The simplest thing to do is
1897 re-emit the original objects. They will be forwarded to the output
1903 re-emit the original objects. They will be forwarded to the output
1898 layer and will be processed as if the cacher did not exist.
1904 layer and will be processed as if the cacher did not exist.
1899
1905
1900 Implementations could also choose to not emit objects - instead locally
1906 Implementations could also choose to not emit objects - instead locally
1901 buffering objects or their encoded representation. They could then emit
1907 buffering objects or their encoded representation. They could then emit
1902 a single "coalesced" object when ``onfinished()`` is called. In
1908 a single "coalesced" object when ``onfinished()`` is called. In
1903 this way, the implementation would function as a filtering layer of
1909 this way, the implementation would function as a filtering layer of
1904 sorts.
1910 sorts.
1905
1911
1906 When caching objects, typically the encoded form of the object will
1912 When caching objects, typically the encoded form of the object will
1907 be stored. Keep in mind that if the original object is forwarded to
1913 be stored. Keep in mind that if the original object is forwarded to
1908 the output layer, it will need to be encoded there as well. For large
1914 the output layer, it will need to be encoded there as well. For large
1909 output, this redundant encoding could add overhead. Implementations
1915 output, this redundant encoding could add overhead. Implementations
1910 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1916 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1911 instances to avoid this overhead.
1917 instances to avoid this overhead.
1912 """
1918 """
1913
1919
1914 def __enter__():
1920 def __enter__():
1915 """Marks the instance as active.
1921 """Marks the instance as active.
1916
1922
1917 Should return self.
1923 Should return self.
1918 """
1924 """
1919
1925
1920 def __exit__(exctype, excvalue, exctb):
1926 def __exit__(exctype, excvalue, exctb):
1921 """Called when cacher is no longer used.
1927 """Called when cacher is no longer used.
1922
1928
1923 This can be used by implementations to perform cleanup actions (e.g.
1929 This can be used by implementations to perform cleanup actions (e.g.
1924 disconnecting network sockets, aborting a partially cached response.
1930 disconnecting network sockets, aborting a partially cached response.
1925 """
1931 """
1926
1932
1927 def adjustcachekeystate(state):
1933 def adjustcachekeystate(state):
1928 """Influences cache key derivation by adjusting state to derive key.
1934 """Influences cache key derivation by adjusting state to derive key.
1929
1935
1930 A dict defining the state used to derive the cache key is passed.
1936 A dict defining the state used to derive the cache key is passed.
1931
1937
1932 Implementations can modify this dict to record additional state that
1938 Implementations can modify this dict to record additional state that
1933 is wanted to influence key derivation.
1939 is wanted to influence key derivation.
1934
1940
1935 Implementations are *highly* encouraged to not modify or delete
1941 Implementations are *highly* encouraged to not modify or delete
1936 existing keys.
1942 existing keys.
1937 """
1943 """
1938
1944
1939 def setcachekey(key):
1945 def setcachekey(key):
1940 """Record the derived cache key for this request.
1946 """Record the derived cache key for this request.
1941
1947
1942 Instances may mutate the key for internal usage, as desired. e.g.
1948 Instances may mutate the key for internal usage, as desired. e.g.
1943 instances may wish to prepend the repo name, introduce path
1949 instances may wish to prepend the repo name, introduce path
1944 components for filesystem or URL addressing, etc. Behavior is up to
1950 components for filesystem or URL addressing, etc. Behavior is up to
1945 the cache.
1951 the cache.
1946
1952
1947 Returns a bool indicating if the request is cacheable by this
1953 Returns a bool indicating if the request is cacheable by this
1948 instance.
1954 instance.
1949 """
1955 """
1950
1956
1951 def lookup():
1957 def lookup():
1952 """Attempt to resolve an entry in the cache.
1958 """Attempt to resolve an entry in the cache.
1953
1959
1954 The instance is instructed to look for the cache key that it was
1960 The instance is instructed to look for the cache key that it was
1955 informed about via the call to ``setcachekey()``.
1961 informed about via the call to ``setcachekey()``.
1956
1962
1957 If there's no cache hit or the cacher doesn't wish to use the cached
1963 If there's no cache hit or the cacher doesn't wish to use the cached
1958 entry, ``None`` should be returned.
1964 entry, ``None`` should be returned.
1959
1965
1960 Else, a dict defining the cached result should be returned. The
1966 Else, a dict defining the cached result should be returned. The
1961 dict may have the following keys:
1967 dict may have the following keys:
1962
1968
1963 objs
1969 objs
1964 An iterable of objects that should be sent to the client. That
1970 An iterable of objects that should be sent to the client. That
1965 iterable of objects is expected to be what the command function
1971 iterable of objects is expected to be what the command function
1966 would return if invoked or an equivalent representation thereof.
1972 would return if invoked or an equivalent representation thereof.
1967 """
1973 """
1968
1974
1969 def onobject(obj):
1975 def onobject(obj):
1970 """Called when a new object is emitted from the command function.
1976 """Called when a new object is emitted from the command function.
1971
1977
1972 Receives as its argument the object that was emitted from the
1978 Receives as its argument the object that was emitted from the
1973 command function.
1979 command function.
1974
1980
1975 This method returns an iterator of objects to forward to the output
1981 This method returns an iterator of objects to forward to the output
1976 layer. The easiest implementation is a generator that just
1982 layer. The easiest implementation is a generator that just
1977 ``yield obj``.
1983 ``yield obj``.
1978 """
1984 """
1979
1985
1980 def onfinished():
1986 def onfinished():
1981 """Called after all objects have been emitted from the command function.
1987 """Called after all objects have been emitted from the command function.
1982
1988
1983 Implementations should return an iterator of objects to forward to
1989 Implementations should return an iterator of objects to forward to
1984 the output layer.
1990 the output layer.
1985
1991
1986 This method can be a generator.
1992 This method can be a generator.
1987 """
1993 """
@@ -1,2349 +1,2351 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 .pycompat import getattr
22 from .pycompat import getattr
23 from . import (
23 from . import (
24 encoding,
24 encoding,
25 error,
25 error,
26 match as matchmod,
26 match as matchmod,
27 mdiff,
27 mdiff,
28 pathutil,
28 pathutil,
29 policy,
29 policy,
30 pycompat,
30 pycompat,
31 revlog,
31 revlog,
32 util,
32 util,
33 )
33 )
34 from .interfaces import (
34 from .interfaces import (
35 repository,
35 repository,
36 util as interfaceutil,
36 util as interfaceutil,
37 )
37 )
38
38
39 parsers = policy.importmod('parsers')
39 parsers = policy.importmod('parsers')
40 propertycache = util.propertycache
40 propertycache = util.propertycache
41
41
42 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
42 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
43 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
43 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
44
44
45
45
46 def _parse(data):
46 def _parse(data):
47 # This method does a little bit of excessive-looking
47 # This method does a little bit of excessive-looking
48 # precondition checking. This is so that the behavior of this
48 # precondition checking. This is so that the behavior of this
49 # class exactly matches its C counterpart to try and help
49 # class exactly matches its C counterpart to try and help
50 # prevent surprise breakage for anyone that develops against
50 # prevent surprise breakage for anyone that develops against
51 # the pure version.
51 # the pure version.
52 if data and data[-1:] != b'\n':
52 if data and data[-1:] != b'\n':
53 raise ValueError(b'Manifest did not end in a newline.')
53 raise ValueError(b'Manifest did not end in a newline.')
54 prev = None
54 prev = None
55 for l in data.splitlines():
55 for l in data.splitlines():
56 if prev is not None and prev > l:
56 if prev is not None and prev > l:
57 raise ValueError(b'Manifest lines not in sorted order.')
57 raise ValueError(b'Manifest lines not in sorted order.')
58 prev = l
58 prev = l
59 f, n = l.split(b'\0')
59 f, n = l.split(b'\0')
60 nl = len(n)
60 nl = len(n)
61 flags = n[-1:]
61 flags = n[-1:]
62 if flags in _manifestflags:
62 if flags in _manifestflags:
63 n = n[:-1]
63 n = n[:-1]
64 nl -= 1
64 nl -= 1
65 else:
65 else:
66 flags = b''
66 flags = b''
67 if nl not in (40, 64):
67 if nl not in (40, 64):
68 raise ValueError(b'Invalid manifest line')
68 raise ValueError(b'Invalid manifest line')
69
69
70 yield f, bin(n), flags
70 yield f, bin(n), flags
71
71
72
72
73 def _text(it):
73 def _text(it):
74 files = []
74 files = []
75 lines = []
75 lines = []
76 for f, n, fl in it:
76 for f, n, fl in it:
77 files.append(f)
77 files.append(f)
78 # if this is changed to support newlines in filenames,
78 # if this is changed to support newlines in filenames,
79 # be sure to check the templates/ dir again (especially *-raw.tmpl)
79 # be sure to check the templates/ dir again (especially *-raw.tmpl)
80 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
80 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
81
81
82 _checkforbidden(files)
82 _checkforbidden(files)
83 return b''.join(lines)
83 return b''.join(lines)
84
84
85
85
86 class lazymanifestiter(object):
86 class lazymanifestiter(object):
87 def __init__(self, lm):
87 def __init__(self, lm):
88 self.pos = 0
88 self.pos = 0
89 self.lm = lm
89 self.lm = lm
90
90
91 def __iter__(self):
91 def __iter__(self):
92 return self
92 return self
93
93
94 def next(self):
94 def next(self):
95 try:
95 try:
96 data, pos = self.lm._get(self.pos)
96 data, pos = self.lm._get(self.pos)
97 except IndexError:
97 except IndexError:
98 raise StopIteration
98 raise StopIteration
99 if pos == -1:
99 if pos == -1:
100 self.pos += 1
100 self.pos += 1
101 return data[0]
101 return data[0]
102 self.pos += 1
102 self.pos += 1
103 zeropos = data.find(b'\x00', pos)
103 zeropos = data.find(b'\x00', pos)
104 return data[pos:zeropos]
104 return data[pos:zeropos]
105
105
106 __next__ = next
106 __next__ = next
107
107
108
108
109 class lazymanifestiterentries(object):
109 class lazymanifestiterentries(object):
110 def __init__(self, lm):
110 def __init__(self, lm):
111 self.lm = lm
111 self.lm = lm
112 self.pos = 0
112 self.pos = 0
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return self
115 return self
116
116
117 def next(self):
117 def next(self):
118 try:
118 try:
119 data, pos = self.lm._get(self.pos)
119 data, pos = self.lm._get(self.pos)
120 except IndexError:
120 except IndexError:
121 raise StopIteration
121 raise StopIteration
122 if pos == -1:
122 if pos == -1:
123 self.pos += 1
123 self.pos += 1
124 return data
124 return data
125 zeropos = data.find(b'\x00', pos)
125 zeropos = data.find(b'\x00', pos)
126 nlpos = data.find(b'\n', pos)
126 nlpos = data.find(b'\n', pos)
127 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
127 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
128 raise error.StorageError(b'Invalid manifest line')
128 raise error.StorageError(b'Invalid manifest line')
129 flags = data[nlpos - 1 : nlpos]
129 flags = data[nlpos - 1 : nlpos]
130 if flags in _manifestflags:
130 if flags in _manifestflags:
131 hlen = nlpos - zeropos - 2
131 hlen = nlpos - zeropos - 2
132 else:
132 else:
133 hlen = nlpos - zeropos - 1
133 hlen = nlpos - zeropos - 1
134 flags = b''
134 flags = b''
135 if hlen not in (40, 64):
135 if hlen not in (40, 64):
136 raise error.StorageError(b'Invalid manifest line')
136 raise error.StorageError(b'Invalid manifest line')
137 hashval = unhexlify(
137 hashval = unhexlify(
138 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
138 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
139 )
139 )
140 self.pos += 1
140 self.pos += 1
141 return (data[pos:zeropos], hashval, flags)
141 return (data[pos:zeropos], hashval, flags)
142
142
143 __next__ = next
143 __next__ = next
144
144
145
145
146 def unhexlify(data, extra, pos, length):
146 def unhexlify(data, extra, pos, length):
147 s = bin(data[pos : pos + length])
147 s = bin(data[pos : pos + length])
148 if extra:
148 if extra:
149 s += chr(extra & 0xFF)
149 s += chr(extra & 0xFF)
150 return s
150 return s
151
151
152
152
153 def _cmp(a, b):
153 def _cmp(a, b):
154 return (a > b) - (a < b)
154 return (a > b) - (a < b)
155
155
156
156
157 _manifestflags = {b'', b'l', b't', b'x'}
157 _manifestflags = {b'', b'l', b't', b'x'}
158
158
159
159
160 class _lazymanifest(object):
160 class _lazymanifest(object):
161 """A pure python manifest backed by a byte string. It is supplimented with
161 """A pure python manifest backed by a byte string. It is supplimented with
162 internal lists as it is modified, until it is compacted back to a pure byte
162 internal lists as it is modified, until it is compacted back to a pure byte
163 string.
163 string.
164
164
165 ``data`` is the initial manifest data.
165 ``data`` is the initial manifest data.
166
166
167 ``positions`` is a list of offsets, one per manifest entry. Positive
167 ``positions`` is a list of offsets, one per manifest entry. Positive
168 values are offsets into ``data``, negative values are offsets into the
168 values are offsets into ``data``, negative values are offsets into the
169 ``extradata`` list. When an entry is removed, its entry is dropped from
169 ``extradata`` list. When an entry is removed, its entry is dropped from
170 ``positions``. The values are encoded such that when walking the list and
170 ``positions``. The values are encoded such that when walking the list and
171 indexing into ``data`` or ``extradata`` as appropriate, the entries are
171 indexing into ``data`` or ``extradata`` as appropriate, the entries are
172 sorted by filename.
172 sorted by filename.
173
173
174 ``extradata`` is a list of (key, hash, flags) for entries that were added or
174 ``extradata`` is a list of (key, hash, flags) for entries that were added or
175 modified since the manifest was created or compacted.
175 modified since the manifest was created or compacted.
176 """
176 """
177
177
178 def __init__(
178 def __init__(
179 self,
179 self,
180 data,
180 data,
181 positions=None,
181 positions=None,
182 extrainfo=None,
182 extrainfo=None,
183 extradata=None,
183 extradata=None,
184 hasremovals=False,
184 hasremovals=False,
185 ):
185 ):
186 if positions is None:
186 if positions is None:
187 self.positions = self.findlines(data)
187 self.positions = self.findlines(data)
188 self.extrainfo = [0] * len(self.positions)
188 self.extrainfo = [0] * len(self.positions)
189 self.data = data
189 self.data = data
190 self.extradata = []
190 self.extradata = []
191 self.hasremovals = False
191 self.hasremovals = False
192 else:
192 else:
193 self.positions = positions[:]
193 self.positions = positions[:]
194 self.extrainfo = extrainfo[:]
194 self.extrainfo = extrainfo[:]
195 self.extradata = extradata[:]
195 self.extradata = extradata[:]
196 self.data = data
196 self.data = data
197 self.hasremovals = hasremovals
197 self.hasremovals = hasremovals
198
198
199 def findlines(self, data):
199 def findlines(self, data):
200 if not data:
200 if not data:
201 return []
201 return []
202 pos = data.find(b"\n")
202 pos = data.find(b"\n")
203 if pos == -1 or data[-1:] != b'\n':
203 if pos == -1 or data[-1:] != b'\n':
204 raise ValueError(b"Manifest did not end in a newline.")
204 raise ValueError(b"Manifest did not end in a newline.")
205 positions = [0]
205 positions = [0]
206 prev = data[: data.find(b'\x00')]
206 prev = data[: data.find(b'\x00')]
207 while pos < len(data) - 1 and pos != -1:
207 while pos < len(data) - 1 and pos != -1:
208 positions.append(pos + 1)
208 positions.append(pos + 1)
209 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
209 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
210 if nexts < prev:
210 if nexts < prev:
211 raise ValueError(b"Manifest lines not in sorted order.")
211 raise ValueError(b"Manifest lines not in sorted order.")
212 prev = nexts
212 prev = nexts
213 pos = data.find(b"\n", pos + 1)
213 pos = data.find(b"\n", pos + 1)
214 return positions
214 return positions
215
215
216 def _get(self, index):
216 def _get(self, index):
217 # get the position encoded in pos:
217 # get the position encoded in pos:
218 # positive number is an index in 'data'
218 # positive number is an index in 'data'
219 # negative number is in extrapieces
219 # negative number is in extrapieces
220 pos = self.positions[index]
220 pos = self.positions[index]
221 if pos >= 0:
221 if pos >= 0:
222 return self.data, pos
222 return self.data, pos
223 return self.extradata[-pos - 1], -1
223 return self.extradata[-pos - 1], -1
224
224
225 def _getkey(self, pos):
225 def _getkey(self, pos):
226 if pos >= 0:
226 if pos >= 0:
227 return self.data[pos : self.data.find(b'\x00', pos + 1)]
227 return self.data[pos : self.data.find(b'\x00', pos + 1)]
228 return self.extradata[-pos - 1][0]
228 return self.extradata[-pos - 1][0]
229
229
230 def bsearch(self, key):
230 def bsearch(self, key):
231 first = 0
231 first = 0
232 last = len(self.positions) - 1
232 last = len(self.positions) - 1
233
233
234 while first <= last:
234 while first <= last:
235 midpoint = (first + last) // 2
235 midpoint = (first + last) // 2
236 nextpos = self.positions[midpoint]
236 nextpos = self.positions[midpoint]
237 candidate = self._getkey(nextpos)
237 candidate = self._getkey(nextpos)
238 r = _cmp(key, candidate)
238 r = _cmp(key, candidate)
239 if r == 0:
239 if r == 0:
240 return midpoint
240 return midpoint
241 else:
241 else:
242 if r < 0:
242 if r < 0:
243 last = midpoint - 1
243 last = midpoint - 1
244 else:
244 else:
245 first = midpoint + 1
245 first = midpoint + 1
246 return -1
246 return -1
247
247
248 def bsearch2(self, key):
248 def bsearch2(self, key):
249 # same as the above, but will always return the position
249 # same as the above, but will always return the position
250 # done for performance reasons
250 # done for performance reasons
251 first = 0
251 first = 0
252 last = len(self.positions) - 1
252 last = len(self.positions) - 1
253
253
254 while first <= last:
254 while first <= last:
255 midpoint = (first + last) // 2
255 midpoint = (first + last) // 2
256 nextpos = self.positions[midpoint]
256 nextpos = self.positions[midpoint]
257 candidate = self._getkey(nextpos)
257 candidate = self._getkey(nextpos)
258 r = _cmp(key, candidate)
258 r = _cmp(key, candidate)
259 if r == 0:
259 if r == 0:
260 return (midpoint, True)
260 return (midpoint, True)
261 else:
261 else:
262 if r < 0:
262 if r < 0:
263 last = midpoint - 1
263 last = midpoint - 1
264 else:
264 else:
265 first = midpoint + 1
265 first = midpoint + 1
266 return (first, False)
266 return (first, False)
267
267
268 def __contains__(self, key):
268 def __contains__(self, key):
269 return self.bsearch(key) != -1
269 return self.bsearch(key) != -1
270
270
271 def __getitem__(self, key):
271 def __getitem__(self, key):
272 if not isinstance(key, bytes):
272 if not isinstance(key, bytes):
273 raise TypeError(b"getitem: manifest keys must be a bytes.")
273 raise TypeError(b"getitem: manifest keys must be a bytes.")
274 needle = self.bsearch(key)
274 needle = self.bsearch(key)
275 if needle == -1:
275 if needle == -1:
276 raise KeyError
276 raise KeyError
277 data, pos = self._get(needle)
277 data, pos = self._get(needle)
278 if pos == -1:
278 if pos == -1:
279 return (data[1], data[2])
279 return (data[1], data[2])
280 zeropos = data.find(b'\x00', pos)
280 zeropos = data.find(b'\x00', pos)
281 nlpos = data.find(b'\n', zeropos)
281 nlpos = data.find(b'\n', zeropos)
282 assert 0 <= needle <= len(self.positions)
282 assert 0 <= needle <= len(self.positions)
283 assert len(self.extrainfo) == len(self.positions)
283 assert len(self.extrainfo) == len(self.positions)
284 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
284 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
285 raise error.StorageError(b'Invalid manifest line')
285 raise error.StorageError(b'Invalid manifest line')
286 hlen = nlpos - zeropos - 1
286 hlen = nlpos - zeropos - 1
287 flags = data[nlpos - 1 : nlpos]
287 flags = data[nlpos - 1 : nlpos]
288 if flags in _manifestflags:
288 if flags in _manifestflags:
289 hlen -= 1
289 hlen -= 1
290 else:
290 else:
291 flags = b''
291 flags = b''
292 if hlen not in (40, 64):
292 if hlen not in (40, 64):
293 raise error.StorageError(b'Invalid manifest line')
293 raise error.StorageError(b'Invalid manifest line')
294 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
294 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
295 return (hashval, flags)
295 return (hashval, flags)
296
296
297 def __delitem__(self, key):
297 def __delitem__(self, key):
298 needle, found = self.bsearch2(key)
298 needle, found = self.bsearch2(key)
299 if not found:
299 if not found:
300 raise KeyError
300 raise KeyError
301 cur = self.positions[needle]
301 cur = self.positions[needle]
302 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
302 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
303 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
303 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
304 if cur >= 0:
304 if cur >= 0:
305 # This does NOT unsort the list as far as the search functions are
305 # This does NOT unsort the list as far as the search functions are
306 # concerned, as they only examine lines mapped by self.positions.
306 # concerned, as they only examine lines mapped by self.positions.
307 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
307 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
308 self.hasremovals = True
308 self.hasremovals = True
309
309
310 def __setitem__(self, key, value):
310 def __setitem__(self, key, value):
311 if not isinstance(key, bytes):
311 if not isinstance(key, bytes):
312 raise TypeError(b"setitem: manifest keys must be a byte string.")
312 raise TypeError(b"setitem: manifest keys must be a byte string.")
313 if not isinstance(value, tuple) or len(value) != 2:
313 if not isinstance(value, tuple) or len(value) != 2:
314 raise TypeError(
314 raise TypeError(
315 b"Manifest values must be a tuple of (node, flags)."
315 b"Manifest values must be a tuple of (node, flags)."
316 )
316 )
317 hashval = value[0]
317 hashval = value[0]
318 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
318 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
319 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
319 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
320 flags = value[1]
320 flags = value[1]
321 if not isinstance(flags, bytes) or len(flags) > 1:
321 if not isinstance(flags, bytes) or len(flags) > 1:
322 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
322 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
323 needle, found = self.bsearch2(key)
323 needle, found = self.bsearch2(key)
324 if found:
324 if found:
325 # put the item
325 # put the item
326 pos = self.positions[needle]
326 pos = self.positions[needle]
327 if pos < 0:
327 if pos < 0:
328 self.extradata[-pos - 1] = (key, hashval, value[1])
328 self.extradata[-pos - 1] = (key, hashval, value[1])
329 else:
329 else:
330 # just don't bother
330 # just don't bother
331 self.extradata.append((key, hashval, value[1]))
331 self.extradata.append((key, hashval, value[1]))
332 self.positions[needle] = -len(self.extradata)
332 self.positions[needle] = -len(self.extradata)
333 else:
333 else:
334 # not found, put it in with extra positions
334 # not found, put it in with extra positions
335 self.extradata.append((key, hashval, value[1]))
335 self.extradata.append((key, hashval, value[1]))
336 self.positions = (
336 self.positions = (
337 self.positions[:needle]
337 self.positions[:needle]
338 + [-len(self.extradata)]
338 + [-len(self.extradata)]
339 + self.positions[needle:]
339 + self.positions[needle:]
340 )
340 )
341 self.extrainfo = (
341 self.extrainfo = (
342 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
342 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
343 )
343 )
344
344
345 def copy(self):
345 def copy(self):
346 # XXX call _compact like in C?
346 # XXX call _compact like in C?
347 return _lazymanifest(
347 return _lazymanifest(
348 self.data,
348 self.data,
349 self.positions,
349 self.positions,
350 self.extrainfo,
350 self.extrainfo,
351 self.extradata,
351 self.extradata,
352 self.hasremovals,
352 self.hasremovals,
353 )
353 )
354
354
355 def _compact(self):
355 def _compact(self):
356 # hopefully not called TOO often
356 # hopefully not called TOO often
357 if len(self.extradata) == 0 and not self.hasremovals:
357 if len(self.extradata) == 0 and not self.hasremovals:
358 return
358 return
359 l = []
359 l = []
360 i = 0
360 i = 0
361 offset = 0
361 offset = 0
362 self.extrainfo = [0] * len(self.positions)
362 self.extrainfo = [0] * len(self.positions)
363 while i < len(self.positions):
363 while i < len(self.positions):
364 if self.positions[i] >= 0:
364 if self.positions[i] >= 0:
365 cur = self.positions[i]
365 cur = self.positions[i]
366 last_cut = cur
366 last_cut = cur
367
367
368 # Collect all contiguous entries in the buffer at the current
368 # Collect all contiguous entries in the buffer at the current
369 # offset, breaking out only for added/modified items held in
369 # offset, breaking out only for added/modified items held in
370 # extradata, or a deleted line prior to the next position.
370 # extradata, or a deleted line prior to the next position.
371 while True:
371 while True:
372 self.positions[i] = offset
372 self.positions[i] = offset
373 i += 1
373 i += 1
374 if i == len(self.positions) or self.positions[i] < 0:
374 if i == len(self.positions) or self.positions[i] < 0:
375 break
375 break
376
376
377 # A removed file has no positions[] entry, but does have an
377 # A removed file has no positions[] entry, but does have an
378 # overwritten first byte. Break out and find the end of the
378 # overwritten first byte. Break out and find the end of the
379 # current good entry/entries if there is a removed file
379 # current good entry/entries if there is a removed file
380 # before the next position.
380 # before the next position.
381 if (
381 if (
382 self.hasremovals
382 self.hasremovals
383 and self.data.find(b'\n\x00', cur, self.positions[i])
383 and self.data.find(b'\n\x00', cur, self.positions[i])
384 != -1
384 != -1
385 ):
385 ):
386 break
386 break
387
387
388 offset += self.positions[i] - cur
388 offset += self.positions[i] - cur
389 cur = self.positions[i]
389 cur = self.positions[i]
390 end_cut = self.data.find(b'\n', cur)
390 end_cut = self.data.find(b'\n', cur)
391 if end_cut != -1:
391 if end_cut != -1:
392 end_cut += 1
392 end_cut += 1
393 offset += end_cut - cur
393 offset += end_cut - cur
394 l.append(self.data[last_cut:end_cut])
394 l.append(self.data[last_cut:end_cut])
395 else:
395 else:
396 while i < len(self.positions) and self.positions[i] < 0:
396 while i < len(self.positions) and self.positions[i] < 0:
397 cur = self.positions[i]
397 cur = self.positions[i]
398 t = self.extradata[-cur - 1]
398 t = self.extradata[-cur - 1]
399 l.append(self._pack(t))
399 l.append(self._pack(t))
400 self.positions[i] = offset
400 self.positions[i] = offset
401 # Hashes are either 20 bytes (old sha1s) or 32
401 # Hashes are either 20 bytes (old sha1s) or 32
402 # bytes (new non-sha1).
402 # bytes (new non-sha1).
403 hlen = 20
403 hlen = 20
404 if len(t[1]) > 25:
404 if len(t[1]) > 25:
405 hlen = 32
405 hlen = 32
406 if len(t[1]) > hlen:
406 if len(t[1]) > hlen:
407 self.extrainfo[i] = ord(t[1][hlen + 1])
407 self.extrainfo[i] = ord(t[1][hlen + 1])
408 offset += len(l[-1])
408 offset += len(l[-1])
409 i += 1
409 i += 1
410 self.data = b''.join(l)
410 self.data = b''.join(l)
411 self.hasremovals = False
411 self.hasremovals = False
412 self.extradata = []
412 self.extradata = []
413
413
414 def _pack(self, d):
414 def _pack(self, d):
415 n = d[1]
415 n = d[1]
416 assert len(n) in (20, 32)
416 assert len(n) in (20, 32)
417 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
417 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
418
418
419 def text(self):
419 def text(self):
420 self._compact()
420 self._compact()
421 return self.data
421 return self.data
422
422
423 def diff(self, m2, clean=False):
423 def diff(self, m2, clean=False):
424 '''Finds changes between the current manifest and m2.'''
424 '''Finds changes between the current manifest and m2.'''
425 # XXX think whether efficiency matters here
425 # XXX think whether efficiency matters here
426 diff = {}
426 diff = {}
427
427
428 for fn, e1, flags in self.iterentries():
428 for fn, e1, flags in self.iterentries():
429 if fn not in m2:
429 if fn not in m2:
430 diff[fn] = (e1, flags), (None, b'')
430 diff[fn] = (e1, flags), (None, b'')
431 else:
431 else:
432 e2 = m2[fn]
432 e2 = m2[fn]
433 if (e1, flags) != e2:
433 if (e1, flags) != e2:
434 diff[fn] = (e1, flags), e2
434 diff[fn] = (e1, flags), e2
435 elif clean:
435 elif clean:
436 diff[fn] = None
436 diff[fn] = None
437
437
438 for fn, e2, flags in m2.iterentries():
438 for fn, e2, flags in m2.iterentries():
439 if fn not in self:
439 if fn not in self:
440 diff[fn] = (None, b''), (e2, flags)
440 diff[fn] = (None, b''), (e2, flags)
441
441
442 return diff
442 return diff
443
443
444 def iterentries(self):
444 def iterentries(self):
445 return lazymanifestiterentries(self)
445 return lazymanifestiterentries(self)
446
446
447 def iterkeys(self):
447 def iterkeys(self):
448 return lazymanifestiter(self)
448 return lazymanifestiter(self)
449
449
450 def __iter__(self):
450 def __iter__(self):
451 return lazymanifestiter(self)
451 return lazymanifestiter(self)
452
452
453 def __len__(self):
453 def __len__(self):
454 return len(self.positions)
454 return len(self.positions)
455
455
456 def filtercopy(self, filterfn):
456 def filtercopy(self, filterfn):
457 # XXX should be optimized
457 # XXX should be optimized
458 c = _lazymanifest(b'')
458 c = _lazymanifest(b'')
459 for f, n, fl in self.iterentries():
459 for f, n, fl in self.iterentries():
460 if filterfn(f):
460 if filterfn(f):
461 c[f] = n, fl
461 c[f] = n, fl
462 return c
462 return c
463
463
464
464
465 try:
465 try:
466 _lazymanifest = parsers.lazymanifest
466 _lazymanifest = parsers.lazymanifest
467 except AttributeError:
467 except AttributeError:
468 pass
468 pass
469
469
470
470
471 @interfaceutil.implementer(repository.imanifestdict)
471 @interfaceutil.implementer(repository.imanifestdict)
472 class manifestdict(object):
472 class manifestdict(object):
473 def __init__(self, data=b''):
473 def __init__(self, data=b''):
474 self._lm = _lazymanifest(data)
474 self._lm = _lazymanifest(data)
475
475
476 def __getitem__(self, key):
476 def __getitem__(self, key):
477 return self._lm[key][0]
477 return self._lm[key][0]
478
478
479 def find(self, key):
479 def find(self, key):
480 return self._lm[key]
480 return self._lm[key]
481
481
482 def __len__(self):
482 def __len__(self):
483 return len(self._lm)
483 return len(self._lm)
484
484
485 def __nonzero__(self):
485 def __nonzero__(self):
486 # nonzero is covered by the __len__ function, but implementing it here
486 # nonzero is covered by the __len__ function, but implementing it here
487 # makes it easier for extensions to override.
487 # makes it easier for extensions to override.
488 return len(self._lm) != 0
488 return len(self._lm) != 0
489
489
490 __bool__ = __nonzero__
490 __bool__ = __nonzero__
491
491
492 def __setitem__(self, key, node):
492 def __setitem__(self, key, node):
493 self._lm[key] = node, self.flags(key)
493 self._lm[key] = node, self.flags(key)
494
494
495 def __contains__(self, key):
495 def __contains__(self, key):
496 if key is None:
496 if key is None:
497 return False
497 return False
498 return key in self._lm
498 return key in self._lm
499
499
500 def __delitem__(self, key):
500 def __delitem__(self, key):
501 del self._lm[key]
501 del self._lm[key]
502
502
503 def __iter__(self):
503 def __iter__(self):
504 return self._lm.__iter__()
504 return self._lm.__iter__()
505
505
506 def iterkeys(self):
506 def iterkeys(self):
507 return self._lm.iterkeys()
507 return self._lm.iterkeys()
508
508
509 def keys(self):
509 def keys(self):
510 return list(self.iterkeys())
510 return list(self.iterkeys())
511
511
512 def filesnotin(self, m2, match=None):
512 def filesnotin(self, m2, match=None):
513 '''Set of files in this manifest that are not in the other'''
513 '''Set of files in this manifest that are not in the other'''
514 if match is not None:
514 if match is not None:
515 match = matchmod.badmatch(match, lambda path, msg: None)
515 match = matchmod.badmatch(match, lambda path, msg: None)
516 sm2 = set(m2.walk(match))
516 sm2 = set(m2.walk(match))
517 return {f for f in self.walk(match) if f not in sm2}
517 return {f for f in self.walk(match) if f not in sm2}
518 return {f for f in self if f not in m2}
518 return {f for f in self if f not in m2}
519
519
520 @propertycache
520 @propertycache
521 def _dirs(self):
521 def _dirs(self):
522 return pathutil.dirs(self)
522 return pathutil.dirs(self)
523
523
524 def dirs(self):
524 def dirs(self):
525 return self._dirs
525 return self._dirs
526
526
527 def hasdir(self, dir):
527 def hasdir(self, dir):
528 return dir in self._dirs
528 return dir in self._dirs
529
529
530 def _filesfastpath(self, match):
530 def _filesfastpath(self, match):
531 """Checks whether we can correctly and quickly iterate over matcher
531 """Checks whether we can correctly and quickly iterate over matcher
532 files instead of over manifest files."""
532 files instead of over manifest files."""
533 files = match.files()
533 files = match.files()
534 return len(files) < 100 and (
534 return len(files) < 100 and (
535 match.isexact()
535 match.isexact()
536 or (match.prefix() and all(fn in self for fn in files))
536 or (match.prefix() and all(fn in self for fn in files))
537 )
537 )
538
538
539 def walk(self, match):
539 def walk(self, match):
540 """Generates matching file names.
540 """Generates matching file names.
541
541
542 Equivalent to manifest.matches(match).iterkeys(), but without creating
542 Equivalent to manifest.matches(match).iterkeys(), but without creating
543 an entirely new manifest.
543 an entirely new manifest.
544
544
545 It also reports nonexistent files by marking them bad with match.bad().
545 It also reports nonexistent files by marking them bad with match.bad().
546 """
546 """
547 if match.always():
547 if match.always():
548 for f in iter(self):
548 for f in iter(self):
549 yield f
549 yield f
550 return
550 return
551
551
552 fset = set(match.files())
552 fset = set(match.files())
553
553
554 # avoid the entire walk if we're only looking for specific files
554 # avoid the entire walk if we're only looking for specific files
555 if self._filesfastpath(match):
555 if self._filesfastpath(match):
556 for fn in sorted(fset):
556 for fn in sorted(fset):
557 if fn in self:
557 if fn in self:
558 yield fn
558 yield fn
559 return
559 return
560
560
561 for fn in self:
561 for fn in self:
562 if fn in fset:
562 if fn in fset:
563 # specified pattern is the exact name
563 # specified pattern is the exact name
564 fset.remove(fn)
564 fset.remove(fn)
565 if match(fn):
565 if match(fn):
566 yield fn
566 yield fn
567
567
568 # for dirstate.walk, files=[''] means "walk the whole tree".
568 # for dirstate.walk, files=[''] means "walk the whole tree".
569 # follow that here, too
569 # follow that here, too
570 fset.discard(b'')
570 fset.discard(b'')
571
571
572 for fn in sorted(fset):
572 for fn in sorted(fset):
573 if not self.hasdir(fn):
573 if not self.hasdir(fn):
574 match.bad(fn, None)
574 match.bad(fn, None)
575
575
576 def _matches(self, match):
576 def _matches(self, match):
577 '''generate a new manifest filtered by the match argument'''
577 '''generate a new manifest filtered by the match argument'''
578 if match.always():
578 if match.always():
579 return self.copy()
579 return self.copy()
580
580
581 if self._filesfastpath(match):
581 if self._filesfastpath(match):
582 m = manifestdict()
582 m = manifestdict()
583 lm = self._lm
583 lm = self._lm
584 for fn in match.files():
584 for fn in match.files():
585 if fn in lm:
585 if fn in lm:
586 m._lm[fn] = lm[fn]
586 m._lm[fn] = lm[fn]
587 return m
587 return m
588
588
589 m = manifestdict()
589 m = manifestdict()
590 m._lm = self._lm.filtercopy(match)
590 m._lm = self._lm.filtercopy(match)
591 return m
591 return m
592
592
593 def diff(self, m2, match=None, clean=False):
593 def diff(self, m2, match=None, clean=False):
594 """Finds changes between the current manifest and m2.
594 """Finds changes between the current manifest and m2.
595
595
596 Args:
596 Args:
597 m2: the manifest to which this manifest should be compared.
597 m2: the manifest to which this manifest should be compared.
598 clean: if true, include files unchanged between these manifests
598 clean: if true, include files unchanged between these manifests
599 with a None value in the returned dictionary.
599 with a None value in the returned dictionary.
600
600
601 The result is returned as a dict with filename as key and
601 The result is returned as a dict with filename as key and
602 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
602 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
603 nodeid in the current/other manifest and fl1/fl2 is the flag
603 nodeid in the current/other manifest and fl1/fl2 is the flag
604 in the current/other manifest. Where the file does not exist,
604 in the current/other manifest. Where the file does not exist,
605 the nodeid will be None and the flags will be the empty
605 the nodeid will be None and the flags will be the empty
606 string.
606 string.
607 """
607 """
608 if match:
608 if match:
609 m1 = self._matches(match)
609 m1 = self._matches(match)
610 m2 = m2._matches(match)
610 m2 = m2._matches(match)
611 return m1.diff(m2, clean=clean)
611 return m1.diff(m2, clean=clean)
612 return self._lm.diff(m2._lm, clean)
612 return self._lm.diff(m2._lm, clean)
613
613
614 def setflag(self, key, flag):
614 def setflag(self, key, flag):
615 if flag not in _manifestflags:
615 if flag not in _manifestflags:
616 raise TypeError(b"Invalid manifest flag set.")
616 raise TypeError(b"Invalid manifest flag set.")
617 self._lm[key] = self[key], flag
617 self._lm[key] = self[key], flag
618
618
619 def get(self, key, default=None):
619 def get(self, key, default=None):
620 try:
620 try:
621 return self._lm[key][0]
621 return self._lm[key][0]
622 except KeyError:
622 except KeyError:
623 return default
623 return default
624
624
625 def flags(self, key):
625 def flags(self, key):
626 try:
626 try:
627 return self._lm[key][1]
627 return self._lm[key][1]
628 except KeyError:
628 except KeyError:
629 return b''
629 return b''
630
630
631 def copy(self):
631 def copy(self):
632 c = manifestdict()
632 c = manifestdict()
633 c._lm = self._lm.copy()
633 c._lm = self._lm.copy()
634 return c
634 return c
635
635
636 def items(self):
636 def items(self):
637 return (x[:2] for x in self._lm.iterentries())
637 return (x[:2] for x in self._lm.iterentries())
638
638
639 def iteritems(self):
639 def iteritems(self):
640 return (x[:2] for x in self._lm.iterentries())
640 return (x[:2] for x in self._lm.iterentries())
641
641
642 def iterentries(self):
642 def iterentries(self):
643 return self._lm.iterentries()
643 return self._lm.iterentries()
644
644
645 def text(self):
645 def text(self):
646 # most likely uses native version
646 # most likely uses native version
647 return self._lm.text()
647 return self._lm.text()
648
648
649 def fastdelta(self, base, changes):
649 def fastdelta(self, base, changes):
650 """Given a base manifest text as a bytearray and a list of changes
650 """Given a base manifest text as a bytearray and a list of changes
651 relative to that text, compute a delta that can be used by revlog.
651 relative to that text, compute a delta that can be used by revlog.
652 """
652 """
653 delta = []
653 delta = []
654 dstart = None
654 dstart = None
655 dend = None
655 dend = None
656 dline = [b""]
656 dline = [b""]
657 start = 0
657 start = 0
658 # zero copy representation of base as a buffer
658 # zero copy representation of base as a buffer
659 addbuf = util.buffer(base)
659 addbuf = util.buffer(base)
660
660
661 changes = list(changes)
661 changes = list(changes)
662 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
662 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
663 # start with a readonly loop that finds the offset of
663 # start with a readonly loop that finds the offset of
664 # each line and creates the deltas
664 # each line and creates the deltas
665 for f, todelete in changes:
665 for f, todelete in changes:
666 # bs will either be the index of the item or the insert point
666 # bs will either be the index of the item or the insert point
667 start, end = _msearch(addbuf, f, start)
667 start, end = _msearch(addbuf, f, start)
668 if not todelete:
668 if not todelete:
669 h, fl = self._lm[f]
669 h, fl = self._lm[f]
670 l = b"%s\0%s%s\n" % (f, hex(h), fl)
670 l = b"%s\0%s%s\n" % (f, hex(h), fl)
671 else:
671 else:
672 if start == end:
672 if start == end:
673 # item we want to delete was not found, error out
673 # item we want to delete was not found, error out
674 raise AssertionError(
674 raise AssertionError(
675 _(b"failed to remove %s from manifest") % f
675 _(b"failed to remove %s from manifest") % f
676 )
676 )
677 l = b""
677 l = b""
678 if dstart is not None and dstart <= start and dend >= start:
678 if dstart is not None and dstart <= start and dend >= start:
679 if dend < end:
679 if dend < end:
680 dend = end
680 dend = end
681 if l:
681 if l:
682 dline.append(l)
682 dline.append(l)
683 else:
683 else:
684 if dstart is not None:
684 if dstart is not None:
685 delta.append([dstart, dend, b"".join(dline)])
685 delta.append([dstart, dend, b"".join(dline)])
686 dstart = start
686 dstart = start
687 dend = end
687 dend = end
688 dline = [l]
688 dline = [l]
689
689
690 if dstart is not None:
690 if dstart is not None:
691 delta.append([dstart, dend, b"".join(dline)])
691 delta.append([dstart, dend, b"".join(dline)])
692 # apply the delta to the base, and get a delta for addrevision
692 # apply the delta to the base, and get a delta for addrevision
693 deltatext, arraytext = _addlistdelta(base, delta)
693 deltatext, arraytext = _addlistdelta(base, delta)
694 else:
694 else:
695 # For large changes, it's much cheaper to just build the text and
695 # For large changes, it's much cheaper to just build the text and
696 # diff it.
696 # diff it.
697 arraytext = bytearray(self.text())
697 arraytext = bytearray(self.text())
698 deltatext = mdiff.textdiff(
698 deltatext = mdiff.textdiff(
699 util.buffer(base), util.buffer(arraytext)
699 util.buffer(base), util.buffer(arraytext)
700 )
700 )
701
701
702 return arraytext, deltatext
702 return arraytext, deltatext
703
703
704
704
705 def _msearch(m, s, lo=0, hi=None):
705 def _msearch(m, s, lo=0, hi=None):
706 """return a tuple (start, end) that says where to find s within m.
706 """return a tuple (start, end) that says where to find s within m.
707
707
708 If the string is found m[start:end] are the line containing
708 If the string is found m[start:end] are the line containing
709 that string. If start == end the string was not found and
709 that string. If start == end the string was not found and
710 they indicate the proper sorted insertion point.
710 they indicate the proper sorted insertion point.
711
711
712 m should be a buffer, a memoryview or a byte string.
712 m should be a buffer, a memoryview or a byte string.
713 s is a byte string"""
713 s is a byte string"""
714
714
715 def advance(i, c):
715 def advance(i, c):
716 while i < lenm and m[i : i + 1] != c:
716 while i < lenm and m[i : i + 1] != c:
717 i += 1
717 i += 1
718 return i
718 return i
719
719
720 if not s:
720 if not s:
721 return (lo, lo)
721 return (lo, lo)
722 lenm = len(m)
722 lenm = len(m)
723 if not hi:
723 if not hi:
724 hi = lenm
724 hi = lenm
725 while lo < hi:
725 while lo < hi:
726 mid = (lo + hi) // 2
726 mid = (lo + hi) // 2
727 start = mid
727 start = mid
728 while start > 0 and m[start - 1 : start] != b'\n':
728 while start > 0 and m[start - 1 : start] != b'\n':
729 start -= 1
729 start -= 1
730 end = advance(start, b'\0')
730 end = advance(start, b'\0')
731 if bytes(m[start:end]) < s:
731 if bytes(m[start:end]) < s:
732 # we know that after the null there are 40 bytes of sha1
732 # we know that after the null there are 40 bytes of sha1
733 # this translates to the bisect lo = mid + 1
733 # this translates to the bisect lo = mid + 1
734 lo = advance(end + 40, b'\n') + 1
734 lo = advance(end + 40, b'\n') + 1
735 else:
735 else:
736 # this translates to the bisect hi = mid
736 # this translates to the bisect hi = mid
737 hi = start
737 hi = start
738 end = advance(lo, b'\0')
738 end = advance(lo, b'\0')
739 found = m[lo:end]
739 found = m[lo:end]
740 if s == found:
740 if s == found:
741 # we know that after the null there are 40 bytes of sha1
741 # we know that after the null there are 40 bytes of sha1
742 end = advance(end + 40, b'\n')
742 end = advance(end + 40, b'\n')
743 return (lo, end + 1)
743 return (lo, end + 1)
744 else:
744 else:
745 return (lo, lo)
745 return (lo, lo)
746
746
747
747
748 def _checkforbidden(l):
748 def _checkforbidden(l):
749 """Check filenames for illegal characters."""
749 """Check filenames for illegal characters."""
750 for f in l:
750 for f in l:
751 if b'\n' in f or b'\r' in f:
751 if b'\n' in f or b'\r' in f:
752 raise error.StorageError(
752 raise error.StorageError(
753 _(b"'\\n' and '\\r' disallowed in filenames: %r")
753 _(b"'\\n' and '\\r' disallowed in filenames: %r")
754 % pycompat.bytestr(f)
754 % pycompat.bytestr(f)
755 )
755 )
756
756
757
757
758 # apply the changes collected during the bisect loop to our addlist
758 # apply the changes collected during the bisect loop to our addlist
759 # return a delta suitable for addrevision
759 # return a delta suitable for addrevision
760 def _addlistdelta(addlist, x):
760 def _addlistdelta(addlist, x):
761 # for large addlist arrays, building a new array is cheaper
761 # for large addlist arrays, building a new array is cheaper
762 # than repeatedly modifying the existing one
762 # than repeatedly modifying the existing one
763 currentposition = 0
763 currentposition = 0
764 newaddlist = bytearray()
764 newaddlist = bytearray()
765
765
766 for start, end, content in x:
766 for start, end, content in x:
767 newaddlist += addlist[currentposition:start]
767 newaddlist += addlist[currentposition:start]
768 if content:
768 if content:
769 newaddlist += bytearray(content)
769 newaddlist += bytearray(content)
770
770
771 currentposition = end
771 currentposition = end
772
772
773 newaddlist += addlist[currentposition:]
773 newaddlist += addlist[currentposition:]
774
774
775 deltatext = b"".join(
775 deltatext = b"".join(
776 struct.pack(b">lll", start, end, len(content)) + content
776 struct.pack(b">lll", start, end, len(content)) + content
777 for start, end, content in x
777 for start, end, content in x
778 )
778 )
779 return deltatext, newaddlist
779 return deltatext, newaddlist
780
780
781
781
782 def _splittopdir(f):
782 def _splittopdir(f):
783 if b'/' in f:
783 if b'/' in f:
784 dir, subpath = f.split(b'/', 1)
784 dir, subpath = f.split(b'/', 1)
785 return dir + b'/', subpath
785 return dir + b'/', subpath
786 else:
786 else:
787 return b'', f
787 return b'', f
788
788
789
789
790 _noop = lambda s: None
790 _noop = lambda s: None
791
791
792
792
793 @interfaceutil.implementer(repository.imanifestdict)
793 @interfaceutil.implementer(repository.imanifestdict)
794 class treemanifest(object):
794 class treemanifest(object):
795 def __init__(self, dir=b'', text=b''):
795 def __init__(self, dir=b'', text=b''):
796 self._dir = dir
796 self._dir = dir
797 self._node = nullid
797 self._node = nullid
798 self._loadfunc = _noop
798 self._loadfunc = _noop
799 self._copyfunc = _noop
799 self._copyfunc = _noop
800 self._dirty = False
800 self._dirty = False
801 self._dirs = {}
801 self._dirs = {}
802 self._lazydirs = {}
802 self._lazydirs = {}
803 # Using _lazymanifest here is a little slower than plain old dicts
803 # Using _lazymanifest here is a little slower than plain old dicts
804 self._files = {}
804 self._files = {}
805 self._flags = {}
805 self._flags = {}
806 if text:
806 if text:
807
807
808 def readsubtree(subdir, subm):
808 def readsubtree(subdir, subm):
809 raise AssertionError(
809 raise AssertionError(
810 b'treemanifest constructor only accepts flat manifests'
810 b'treemanifest constructor only accepts flat manifests'
811 )
811 )
812
812
813 self.parse(text, readsubtree)
813 self.parse(text, readsubtree)
814 self._dirty = True # Mark flat manifest dirty after parsing
814 self._dirty = True # Mark flat manifest dirty after parsing
815
815
816 def _subpath(self, path):
816 def _subpath(self, path):
817 return self._dir + path
817 return self._dir + path
818
818
819 def _loadalllazy(self):
819 def _loadalllazy(self):
820 selfdirs = self._dirs
820 selfdirs = self._dirs
821 subpath = self._subpath
821 subpath = self._subpath
822 for d, (node, readsubtree, docopy) in pycompat.iteritems(
822 for d, (node, readsubtree, docopy) in pycompat.iteritems(
823 self._lazydirs
823 self._lazydirs
824 ):
824 ):
825 if docopy:
825 if docopy:
826 selfdirs[d] = readsubtree(subpath(d), node).copy()
826 selfdirs[d] = readsubtree(subpath(d), node).copy()
827 else:
827 else:
828 selfdirs[d] = readsubtree(subpath(d), node)
828 selfdirs[d] = readsubtree(subpath(d), node)
829 self._lazydirs = {}
829 self._lazydirs = {}
830
830
831 def _loadlazy(self, d):
831 def _loadlazy(self, d):
832 v = self._lazydirs.get(d)
832 v = self._lazydirs.get(d)
833 if v:
833 if v:
834 node, readsubtree, docopy = v
834 node, readsubtree, docopy = v
835 if docopy:
835 if docopy:
836 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
836 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
837 else:
837 else:
838 self._dirs[d] = readsubtree(self._subpath(d), node)
838 self._dirs[d] = readsubtree(self._subpath(d), node)
839 del self._lazydirs[d]
839 del self._lazydirs[d]
840
840
841 def _loadchildrensetlazy(self, visit):
841 def _loadchildrensetlazy(self, visit):
842 if not visit:
842 if not visit:
843 return None
843 return None
844 if visit == b'all' or visit == b'this':
844 if visit == b'all' or visit == b'this':
845 self._loadalllazy()
845 self._loadalllazy()
846 return None
846 return None
847
847
848 loadlazy = self._loadlazy
848 loadlazy = self._loadlazy
849 for k in visit:
849 for k in visit:
850 loadlazy(k + b'/')
850 loadlazy(k + b'/')
851 return visit
851 return visit
852
852
853 def _loaddifflazy(self, t1, t2):
853 def _loaddifflazy(self, t1, t2):
854 """load items in t1 and t2 if they're needed for diffing.
854 """load items in t1 and t2 if they're needed for diffing.
855
855
856 The criteria currently is:
856 The criteria currently is:
857 - if it's not present in _lazydirs in either t1 or t2, load it in the
857 - if it's not present in _lazydirs in either t1 or t2, load it in the
858 other (it may already be loaded or it may not exist, doesn't matter)
858 other (it may already be loaded or it may not exist, doesn't matter)
859 - if it's present in _lazydirs in both, compare the nodeid; if it
859 - if it's present in _lazydirs in both, compare the nodeid; if it
860 differs, load it in both
860 differs, load it in both
861 """
861 """
862 toloadlazy = []
862 toloadlazy = []
863 for d, v1 in pycompat.iteritems(t1._lazydirs):
863 for d, v1 in pycompat.iteritems(t1._lazydirs):
864 v2 = t2._lazydirs.get(d)
864 v2 = t2._lazydirs.get(d)
865 if not v2 or v2[0] != v1[0]:
865 if not v2 or v2[0] != v1[0]:
866 toloadlazy.append(d)
866 toloadlazy.append(d)
867 for d, v1 in pycompat.iteritems(t2._lazydirs):
867 for d, v1 in pycompat.iteritems(t2._lazydirs):
868 if d not in t1._lazydirs:
868 if d not in t1._lazydirs:
869 toloadlazy.append(d)
869 toloadlazy.append(d)
870
870
871 for d in toloadlazy:
871 for d in toloadlazy:
872 t1._loadlazy(d)
872 t1._loadlazy(d)
873 t2._loadlazy(d)
873 t2._loadlazy(d)
874
874
875 def __len__(self):
875 def __len__(self):
876 self._load()
876 self._load()
877 size = len(self._files)
877 size = len(self._files)
878 self._loadalllazy()
878 self._loadalllazy()
879 for m in self._dirs.values():
879 for m in self._dirs.values():
880 size += m.__len__()
880 size += m.__len__()
881 return size
881 return size
882
882
883 def __nonzero__(self):
883 def __nonzero__(self):
884 # Faster than "__len() != 0" since it avoids loading sub-manifests
884 # Faster than "__len() != 0" since it avoids loading sub-manifests
885 return not self._isempty()
885 return not self._isempty()
886
886
887 __bool__ = __nonzero__
887 __bool__ = __nonzero__
888
888
889 def _isempty(self):
889 def _isempty(self):
890 self._load() # for consistency; already loaded by all callers
890 self._load() # for consistency; already loaded by all callers
891 # See if we can skip loading everything.
891 # See if we can skip loading everything.
892 if self._files or (
892 if self._files or (
893 self._dirs and any(not m._isempty() for m in self._dirs.values())
893 self._dirs and any(not m._isempty() for m in self._dirs.values())
894 ):
894 ):
895 return False
895 return False
896 self._loadalllazy()
896 self._loadalllazy()
897 return not self._dirs or all(m._isempty() for m in self._dirs.values())
897 return not self._dirs or all(m._isempty() for m in self._dirs.values())
898
898
899 @encoding.strmethod
899 @encoding.strmethod
900 def __repr__(self):
900 def __repr__(self):
901 return (
901 return (
902 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
902 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
903 % (
903 % (
904 self._dir,
904 self._dir,
905 hex(self._node),
905 hex(self._node),
906 bool(self._loadfunc is _noop),
906 bool(self._loadfunc is _noop),
907 self._dirty,
907 self._dirty,
908 id(self),
908 id(self),
909 )
909 )
910 )
910 )
911
911
912 def dir(self):
912 def dir(self):
913 """The directory that this tree manifest represents, including a
913 """The directory that this tree manifest represents, including a
914 trailing '/'. Empty string for the repo root directory."""
914 trailing '/'. Empty string for the repo root directory."""
915 return self._dir
915 return self._dir
916
916
917 def node(self):
917 def node(self):
918 """This node of this instance. nullid for unsaved instances. Should
918 """This node of this instance. nullid for unsaved instances. Should
919 be updated when the instance is read or written from a revlog.
919 be updated when the instance is read or written from a revlog.
920 """
920 """
921 assert not self._dirty
921 assert not self._dirty
922 return self._node
922 return self._node
923
923
924 def setnode(self, node):
924 def setnode(self, node):
925 self._node = node
925 self._node = node
926 self._dirty = False
926 self._dirty = False
927
927
928 def iterentries(self):
928 def iterentries(self):
929 self._load()
929 self._load()
930 self._loadalllazy()
930 self._loadalllazy()
931 for p, n in sorted(
931 for p, n in sorted(
932 itertools.chain(self._dirs.items(), self._files.items())
932 itertools.chain(self._dirs.items(), self._files.items())
933 ):
933 ):
934 if p in self._files:
934 if p in self._files:
935 yield self._subpath(p), n, self._flags.get(p, b'')
935 yield self._subpath(p), n, self._flags.get(p, b'')
936 else:
936 else:
937 for x in n.iterentries():
937 for x in n.iterentries():
938 yield x
938 yield x
939
939
940 def items(self):
940 def items(self):
941 self._load()
941 self._load()
942 self._loadalllazy()
942 self._loadalllazy()
943 for p, n in sorted(
943 for p, n in sorted(
944 itertools.chain(self._dirs.items(), self._files.items())
944 itertools.chain(self._dirs.items(), self._files.items())
945 ):
945 ):
946 if p in self._files:
946 if p in self._files:
947 yield self._subpath(p), n
947 yield self._subpath(p), n
948 else:
948 else:
949 for f, sn in pycompat.iteritems(n):
949 for f, sn in pycompat.iteritems(n):
950 yield f, sn
950 yield f, sn
951
951
952 iteritems = items
952 iteritems = items
953
953
954 def iterkeys(self):
954 def iterkeys(self):
955 self._load()
955 self._load()
956 self._loadalllazy()
956 self._loadalllazy()
957 for p in sorted(itertools.chain(self._dirs, self._files)):
957 for p in sorted(itertools.chain(self._dirs, self._files)):
958 if p in self._files:
958 if p in self._files:
959 yield self._subpath(p)
959 yield self._subpath(p)
960 else:
960 else:
961 for f in self._dirs[p]:
961 for f in self._dirs[p]:
962 yield f
962 yield f
963
963
964 def keys(self):
964 def keys(self):
965 return list(self.iterkeys())
965 return list(self.iterkeys())
966
966
967 def __iter__(self):
967 def __iter__(self):
968 return self.iterkeys()
968 return self.iterkeys()
969
969
970 def __contains__(self, f):
970 def __contains__(self, f):
971 if f is None:
971 if f is None:
972 return False
972 return False
973 self._load()
973 self._load()
974 dir, subpath = _splittopdir(f)
974 dir, subpath = _splittopdir(f)
975 if dir:
975 if dir:
976 self._loadlazy(dir)
976 self._loadlazy(dir)
977
977
978 if dir not in self._dirs:
978 if dir not in self._dirs:
979 return False
979 return False
980
980
981 return self._dirs[dir].__contains__(subpath)
981 return self._dirs[dir].__contains__(subpath)
982 else:
982 else:
983 return f in self._files
983 return f in self._files
984
984
985 def get(self, f, default=None):
985 def get(self, f, default=None):
986 self._load()
986 self._load()
987 dir, subpath = _splittopdir(f)
987 dir, subpath = _splittopdir(f)
988 if dir:
988 if dir:
989 self._loadlazy(dir)
989 self._loadlazy(dir)
990
990
991 if dir not in self._dirs:
991 if dir not in self._dirs:
992 return default
992 return default
993 return self._dirs[dir].get(subpath, default)
993 return self._dirs[dir].get(subpath, default)
994 else:
994 else:
995 return self._files.get(f, default)
995 return self._files.get(f, default)
996
996
997 def __getitem__(self, f):
997 def __getitem__(self, f):
998 self._load()
998 self._load()
999 dir, subpath = _splittopdir(f)
999 dir, subpath = _splittopdir(f)
1000 if dir:
1000 if dir:
1001 self._loadlazy(dir)
1001 self._loadlazy(dir)
1002
1002
1003 return self._dirs[dir].__getitem__(subpath)
1003 return self._dirs[dir].__getitem__(subpath)
1004 else:
1004 else:
1005 return self._files[f]
1005 return self._files[f]
1006
1006
1007 def flags(self, f):
1007 def flags(self, f):
1008 self._load()
1008 self._load()
1009 dir, subpath = _splittopdir(f)
1009 dir, subpath = _splittopdir(f)
1010 if dir:
1010 if dir:
1011 self._loadlazy(dir)
1011 self._loadlazy(dir)
1012
1012
1013 if dir not in self._dirs:
1013 if dir not in self._dirs:
1014 return b''
1014 return b''
1015 return self._dirs[dir].flags(subpath)
1015 return self._dirs[dir].flags(subpath)
1016 else:
1016 else:
1017 if f in self._lazydirs or f in self._dirs:
1017 if f in self._lazydirs or f in self._dirs:
1018 return b''
1018 return b''
1019 return self._flags.get(f, b'')
1019 return self._flags.get(f, b'')
1020
1020
1021 def find(self, f):
1021 def find(self, f):
1022 self._load()
1022 self._load()
1023 dir, subpath = _splittopdir(f)
1023 dir, subpath = _splittopdir(f)
1024 if dir:
1024 if dir:
1025 self._loadlazy(dir)
1025 self._loadlazy(dir)
1026
1026
1027 return self._dirs[dir].find(subpath)
1027 return self._dirs[dir].find(subpath)
1028 else:
1028 else:
1029 return self._files[f], self._flags.get(f, b'')
1029 return self._files[f], self._flags.get(f, b'')
1030
1030
1031 def __delitem__(self, f):
1031 def __delitem__(self, f):
1032 self._load()
1032 self._load()
1033 dir, subpath = _splittopdir(f)
1033 dir, subpath = _splittopdir(f)
1034 if dir:
1034 if dir:
1035 self._loadlazy(dir)
1035 self._loadlazy(dir)
1036
1036
1037 self._dirs[dir].__delitem__(subpath)
1037 self._dirs[dir].__delitem__(subpath)
1038 # If the directory is now empty, remove it
1038 # If the directory is now empty, remove it
1039 if self._dirs[dir]._isempty():
1039 if self._dirs[dir]._isempty():
1040 del self._dirs[dir]
1040 del self._dirs[dir]
1041 else:
1041 else:
1042 del self._files[f]
1042 del self._files[f]
1043 if f in self._flags:
1043 if f in self._flags:
1044 del self._flags[f]
1044 del self._flags[f]
1045 self._dirty = True
1045 self._dirty = True
1046
1046
1047 def __setitem__(self, f, n):
1047 def __setitem__(self, f, n):
1048 assert n is not None
1048 assert n is not None
1049 self._load()
1049 self._load()
1050 dir, subpath = _splittopdir(f)
1050 dir, subpath = _splittopdir(f)
1051 if dir:
1051 if dir:
1052 self._loadlazy(dir)
1052 self._loadlazy(dir)
1053 if dir not in self._dirs:
1053 if dir not in self._dirs:
1054 self._dirs[dir] = treemanifest(self._subpath(dir))
1054 self._dirs[dir] = treemanifest(self._subpath(dir))
1055 self._dirs[dir].__setitem__(subpath, n)
1055 self._dirs[dir].__setitem__(subpath, n)
1056 else:
1056 else:
1057 # manifest nodes are either 20 bytes or 32 bytes,
1057 # manifest nodes are either 20 bytes or 32 bytes,
1058 # depending on the hash in use. Assert this as historically
1058 # depending on the hash in use. Assert this as historically
1059 # sometimes extra bytes were added.
1059 # sometimes extra bytes were added.
1060 assert len(n) in (20, 32)
1060 assert len(n) in (20, 32)
1061 self._files[f] = n
1061 self._files[f] = n
1062 self._dirty = True
1062 self._dirty = True
1063
1063
1064 def _load(self):
1064 def _load(self):
1065 if self._loadfunc is not _noop:
1065 if self._loadfunc is not _noop:
1066 lf, self._loadfunc = self._loadfunc, _noop
1066 lf, self._loadfunc = self._loadfunc, _noop
1067 lf(self)
1067 lf(self)
1068 elif self._copyfunc is not _noop:
1068 elif self._copyfunc is not _noop:
1069 cf, self._copyfunc = self._copyfunc, _noop
1069 cf, self._copyfunc = self._copyfunc, _noop
1070 cf(self)
1070 cf(self)
1071
1071
1072 def setflag(self, f, flags):
1072 def setflag(self, f, flags):
1073 """Set the flags (symlink, executable) for path f."""
1073 """Set the flags (symlink, executable) for path f."""
1074 if flags not in _manifestflags:
1074 if flags not in _manifestflags:
1075 raise TypeError(b"Invalid manifest flag set.")
1075 raise TypeError(b"Invalid manifest flag set.")
1076 self._load()
1076 self._load()
1077 dir, subpath = _splittopdir(f)
1077 dir, subpath = _splittopdir(f)
1078 if dir:
1078 if dir:
1079 self._loadlazy(dir)
1079 self._loadlazy(dir)
1080 if dir not in self._dirs:
1080 if dir not in self._dirs:
1081 self._dirs[dir] = treemanifest(self._subpath(dir))
1081 self._dirs[dir] = treemanifest(self._subpath(dir))
1082 self._dirs[dir].setflag(subpath, flags)
1082 self._dirs[dir].setflag(subpath, flags)
1083 else:
1083 else:
1084 self._flags[f] = flags
1084 self._flags[f] = flags
1085 self._dirty = True
1085 self._dirty = True
1086
1086
1087 def copy(self):
1087 def copy(self):
1088 copy = treemanifest(self._dir)
1088 copy = treemanifest(self._dir)
1089 copy._node = self._node
1089 copy._node = self._node
1090 copy._dirty = self._dirty
1090 copy._dirty = self._dirty
1091 if self._copyfunc is _noop:
1091 if self._copyfunc is _noop:
1092
1092
1093 def _copyfunc(s):
1093 def _copyfunc(s):
1094 self._load()
1094 self._load()
1095 s._lazydirs = {
1095 s._lazydirs = {
1096 d: (n, r, True)
1096 d: (n, r, True)
1097 for d, (n, r, c) in pycompat.iteritems(self._lazydirs)
1097 for d, (n, r, c) in pycompat.iteritems(self._lazydirs)
1098 }
1098 }
1099 sdirs = s._dirs
1099 sdirs = s._dirs
1100 for d, v in pycompat.iteritems(self._dirs):
1100 for d, v in pycompat.iteritems(self._dirs):
1101 sdirs[d] = v.copy()
1101 sdirs[d] = v.copy()
1102 s._files = dict.copy(self._files)
1102 s._files = dict.copy(self._files)
1103 s._flags = dict.copy(self._flags)
1103 s._flags = dict.copy(self._flags)
1104
1104
1105 if self._loadfunc is _noop:
1105 if self._loadfunc is _noop:
1106 _copyfunc(copy)
1106 _copyfunc(copy)
1107 else:
1107 else:
1108 copy._copyfunc = _copyfunc
1108 copy._copyfunc = _copyfunc
1109 else:
1109 else:
1110 copy._copyfunc = self._copyfunc
1110 copy._copyfunc = self._copyfunc
1111 return copy
1111 return copy
1112
1112
1113 def filesnotin(self, m2, match=None):
1113 def filesnotin(self, m2, match=None):
1114 '''Set of files in this manifest that are not in the other'''
1114 '''Set of files in this manifest that are not in the other'''
1115 if match and not match.always():
1115 if match and not match.always():
1116 m1 = self._matches(match)
1116 m1 = self._matches(match)
1117 m2 = m2._matches(match)
1117 m2 = m2._matches(match)
1118 return m1.filesnotin(m2)
1118 return m1.filesnotin(m2)
1119
1119
1120 files = set()
1120 files = set()
1121
1121
1122 def _filesnotin(t1, t2):
1122 def _filesnotin(t1, t2):
1123 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1123 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1124 return
1124 return
1125 t1._load()
1125 t1._load()
1126 t2._load()
1126 t2._load()
1127 self._loaddifflazy(t1, t2)
1127 self._loaddifflazy(t1, t2)
1128 for d, m1 in pycompat.iteritems(t1._dirs):
1128 for d, m1 in pycompat.iteritems(t1._dirs):
1129 if d in t2._dirs:
1129 if d in t2._dirs:
1130 m2 = t2._dirs[d]
1130 m2 = t2._dirs[d]
1131 _filesnotin(m1, m2)
1131 _filesnotin(m1, m2)
1132 else:
1132 else:
1133 files.update(m1.iterkeys())
1133 files.update(m1.iterkeys())
1134
1134
1135 for fn in t1._files:
1135 for fn in t1._files:
1136 if fn not in t2._files:
1136 if fn not in t2._files:
1137 files.add(t1._subpath(fn))
1137 files.add(t1._subpath(fn))
1138
1138
1139 _filesnotin(self, m2)
1139 _filesnotin(self, m2)
1140 return files
1140 return files
1141
1141
1142 @propertycache
1142 @propertycache
1143 def _alldirs(self):
1143 def _alldirs(self):
1144 return pathutil.dirs(self)
1144 return pathutil.dirs(self)
1145
1145
1146 def dirs(self):
1146 def dirs(self):
1147 return self._alldirs
1147 return self._alldirs
1148
1148
1149 def hasdir(self, dir):
1149 def hasdir(self, dir):
1150 self._load()
1150 self._load()
1151 topdir, subdir = _splittopdir(dir)
1151 topdir, subdir = _splittopdir(dir)
1152 if topdir:
1152 if topdir:
1153 self._loadlazy(topdir)
1153 self._loadlazy(topdir)
1154 if topdir in self._dirs:
1154 if topdir in self._dirs:
1155 return self._dirs[topdir].hasdir(subdir)
1155 return self._dirs[topdir].hasdir(subdir)
1156 return False
1156 return False
1157 dirslash = dir + b'/'
1157 dirslash = dir + b'/'
1158 return dirslash in self._dirs or dirslash in self._lazydirs
1158 return dirslash in self._dirs or dirslash in self._lazydirs
1159
1159
1160 def walk(self, match):
1160 def walk(self, match):
1161 """Generates matching file names.
1161 """Generates matching file names.
1162
1162
1163 It also reports nonexistent files by marking them bad with match.bad().
1163 It also reports nonexistent files by marking them bad with match.bad().
1164 """
1164 """
1165 if match.always():
1165 if match.always():
1166 for f in iter(self):
1166 for f in iter(self):
1167 yield f
1167 yield f
1168 return
1168 return
1169
1169
1170 fset = set(match.files())
1170 fset = set(match.files())
1171
1171
1172 for fn in self._walk(match):
1172 for fn in self._walk(match):
1173 if fn in fset:
1173 if fn in fset:
1174 # specified pattern is the exact name
1174 # specified pattern is the exact name
1175 fset.remove(fn)
1175 fset.remove(fn)
1176 yield fn
1176 yield fn
1177
1177
1178 # for dirstate.walk, files=[''] means "walk the whole tree".
1178 # for dirstate.walk, files=[''] means "walk the whole tree".
1179 # follow that here, too
1179 # follow that here, too
1180 fset.discard(b'')
1180 fset.discard(b'')
1181
1181
1182 for fn in sorted(fset):
1182 for fn in sorted(fset):
1183 if not self.hasdir(fn):
1183 if not self.hasdir(fn):
1184 match.bad(fn, None)
1184 match.bad(fn, None)
1185
1185
1186 def _walk(self, match):
1186 def _walk(self, match):
1187 '''Recursively generates matching file names for walk().'''
1187 '''Recursively generates matching file names for walk().'''
1188 visit = match.visitchildrenset(self._dir[:-1])
1188 visit = match.visitchildrenset(self._dir[:-1])
1189 if not visit:
1189 if not visit:
1190 return
1190 return
1191
1191
1192 # yield this dir's files and walk its submanifests
1192 # yield this dir's files and walk its submanifests
1193 self._load()
1193 self._load()
1194 visit = self._loadchildrensetlazy(visit)
1194 visit = self._loadchildrensetlazy(visit)
1195 for p in sorted(list(self._dirs) + list(self._files)):
1195 for p in sorted(list(self._dirs) + list(self._files)):
1196 if p in self._files:
1196 if p in self._files:
1197 fullp = self._subpath(p)
1197 fullp = self._subpath(p)
1198 if match(fullp):
1198 if match(fullp):
1199 yield fullp
1199 yield fullp
1200 else:
1200 else:
1201 if not visit or p[:-1] in visit:
1201 if not visit or p[:-1] in visit:
1202 for f in self._dirs[p]._walk(match):
1202 for f in self._dirs[p]._walk(match):
1203 yield f
1203 yield f
1204
1204
1205 def _matches(self, match):
1205 def _matches(self, match):
1206 """recursively generate a new manifest filtered by the match argument."""
1206 """recursively generate a new manifest filtered by the match argument."""
1207 if match.always():
1207 if match.always():
1208 return self.copy()
1208 return self.copy()
1209 return self._matches_inner(match)
1209 return self._matches_inner(match)
1210
1210
1211 def _matches_inner(self, match):
1211 def _matches_inner(self, match):
1212 if match.always():
1212 if match.always():
1213 return self.copy()
1213 return self.copy()
1214
1214
1215 visit = match.visitchildrenset(self._dir[:-1])
1215 visit = match.visitchildrenset(self._dir[:-1])
1216 if visit == b'all':
1216 if visit == b'all':
1217 return self.copy()
1217 return self.copy()
1218 ret = treemanifest(self._dir)
1218 ret = treemanifest(self._dir)
1219 if not visit:
1219 if not visit:
1220 return ret
1220 return ret
1221
1221
1222 self._load()
1222 self._load()
1223 for fn in self._files:
1223 for fn in self._files:
1224 # While visitchildrenset *usually* lists only subdirs, this is
1224 # While visitchildrenset *usually* lists only subdirs, this is
1225 # actually up to the matcher and may have some files in the set().
1225 # actually up to the matcher and may have some files in the set().
1226 # If visit == 'this', we should obviously look at the files in this
1226 # If visit == 'this', we should obviously look at the files in this
1227 # directory; if visit is a set, and fn is in it, we should inspect
1227 # directory; if visit is a set, and fn is in it, we should inspect
1228 # fn (but no need to inspect things not in the set).
1228 # fn (but no need to inspect things not in the set).
1229 if visit != b'this' and fn not in visit:
1229 if visit != b'this' and fn not in visit:
1230 continue
1230 continue
1231 fullp = self._subpath(fn)
1231 fullp = self._subpath(fn)
1232 # visitchildrenset isn't perfect, we still need to call the regular
1232 # visitchildrenset isn't perfect, we still need to call the regular
1233 # matcher code to further filter results.
1233 # matcher code to further filter results.
1234 if not match(fullp):
1234 if not match(fullp):
1235 continue
1235 continue
1236 ret._files[fn] = self._files[fn]
1236 ret._files[fn] = self._files[fn]
1237 if fn in self._flags:
1237 if fn in self._flags:
1238 ret._flags[fn] = self._flags[fn]
1238 ret._flags[fn] = self._flags[fn]
1239
1239
1240 visit = self._loadchildrensetlazy(visit)
1240 visit = self._loadchildrensetlazy(visit)
1241 for dir, subm in pycompat.iteritems(self._dirs):
1241 for dir, subm in pycompat.iteritems(self._dirs):
1242 if visit and dir[:-1] not in visit:
1242 if visit and dir[:-1] not in visit:
1243 continue
1243 continue
1244 m = subm._matches_inner(match)
1244 m = subm._matches_inner(match)
1245 if not m._isempty():
1245 if not m._isempty():
1246 ret._dirs[dir] = m
1246 ret._dirs[dir] = m
1247
1247
1248 if not ret._isempty():
1248 if not ret._isempty():
1249 ret._dirty = True
1249 ret._dirty = True
1250 return ret
1250 return ret
1251
1251
1252 def fastdelta(self, base, changes):
1252 def fastdelta(self, base, changes):
1253 raise FastdeltaUnavailable()
1253 raise FastdeltaUnavailable()
1254
1254
1255 def diff(self, m2, match=None, clean=False):
1255 def diff(self, m2, match=None, clean=False):
1256 """Finds changes between the current manifest and m2.
1256 """Finds changes between the current manifest and m2.
1257
1257
1258 Args:
1258 Args:
1259 m2: the manifest to which this manifest should be compared.
1259 m2: the manifest to which this manifest should be compared.
1260 clean: if true, include files unchanged between these manifests
1260 clean: if true, include files unchanged between these manifests
1261 with a None value in the returned dictionary.
1261 with a None value in the returned dictionary.
1262
1262
1263 The result is returned as a dict with filename as key and
1263 The result is returned as a dict with filename as key and
1264 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1264 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1265 nodeid in the current/other manifest and fl1/fl2 is the flag
1265 nodeid in the current/other manifest and fl1/fl2 is the flag
1266 in the current/other manifest. Where the file does not exist,
1266 in the current/other manifest. Where the file does not exist,
1267 the nodeid will be None and the flags will be the empty
1267 the nodeid will be None and the flags will be the empty
1268 string.
1268 string.
1269 """
1269 """
1270 if match and not match.always():
1270 if match and not match.always():
1271 m1 = self._matches(match)
1271 m1 = self._matches(match)
1272 m2 = m2._matches(match)
1272 m2 = m2._matches(match)
1273 return m1.diff(m2, clean=clean)
1273 return m1.diff(m2, clean=clean)
1274 result = {}
1274 result = {}
1275 emptytree = treemanifest()
1275 emptytree = treemanifest()
1276
1276
1277 def _iterativediff(t1, t2, stack):
1277 def _iterativediff(t1, t2, stack):
1278 """compares two tree manifests and append new tree-manifests which
1278 """compares two tree manifests and append new tree-manifests which
1279 needs to be compared to stack"""
1279 needs to be compared to stack"""
1280 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1280 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1281 return
1281 return
1282 t1._load()
1282 t1._load()
1283 t2._load()
1283 t2._load()
1284 self._loaddifflazy(t1, t2)
1284 self._loaddifflazy(t1, t2)
1285
1285
1286 for d, m1 in pycompat.iteritems(t1._dirs):
1286 for d, m1 in pycompat.iteritems(t1._dirs):
1287 m2 = t2._dirs.get(d, emptytree)
1287 m2 = t2._dirs.get(d, emptytree)
1288 stack.append((m1, m2))
1288 stack.append((m1, m2))
1289
1289
1290 for d, m2 in pycompat.iteritems(t2._dirs):
1290 for d, m2 in pycompat.iteritems(t2._dirs):
1291 if d not in t1._dirs:
1291 if d not in t1._dirs:
1292 stack.append((emptytree, m2))
1292 stack.append((emptytree, m2))
1293
1293
1294 for fn, n1 in pycompat.iteritems(t1._files):
1294 for fn, n1 in pycompat.iteritems(t1._files):
1295 fl1 = t1._flags.get(fn, b'')
1295 fl1 = t1._flags.get(fn, b'')
1296 n2 = t2._files.get(fn, None)
1296 n2 = t2._files.get(fn, None)
1297 fl2 = t2._flags.get(fn, b'')
1297 fl2 = t2._flags.get(fn, b'')
1298 if n1 != n2 or fl1 != fl2:
1298 if n1 != n2 or fl1 != fl2:
1299 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1299 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1300 elif clean:
1300 elif clean:
1301 result[t1._subpath(fn)] = None
1301 result[t1._subpath(fn)] = None
1302
1302
1303 for fn, n2 in pycompat.iteritems(t2._files):
1303 for fn, n2 in pycompat.iteritems(t2._files):
1304 if fn not in t1._files:
1304 if fn not in t1._files:
1305 fl2 = t2._flags.get(fn, b'')
1305 fl2 = t2._flags.get(fn, b'')
1306 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1306 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1307
1307
1308 stackls = []
1308 stackls = []
1309 _iterativediff(self, m2, stackls)
1309 _iterativediff(self, m2, stackls)
1310 while stackls:
1310 while stackls:
1311 t1, t2 = stackls.pop()
1311 t1, t2 = stackls.pop()
1312 # stackls is populated in the function call
1312 # stackls is populated in the function call
1313 _iterativediff(t1, t2, stackls)
1313 _iterativediff(t1, t2, stackls)
1314 return result
1314 return result
1315
1315
1316 def unmodifiedsince(self, m2):
1316 def unmodifiedsince(self, m2):
1317 return not self._dirty and not m2._dirty and self._node == m2._node
1317 return not self._dirty and not m2._dirty and self._node == m2._node
1318
1318
1319 def parse(self, text, readsubtree):
1319 def parse(self, text, readsubtree):
1320 selflazy = self._lazydirs
1320 selflazy = self._lazydirs
1321 for f, n, fl in _parse(text):
1321 for f, n, fl in _parse(text):
1322 if fl == b't':
1322 if fl == b't':
1323 f = f + b'/'
1323 f = f + b'/'
1324 # False below means "doesn't need to be copied" and can use the
1324 # False below means "doesn't need to be copied" and can use the
1325 # cached value from readsubtree directly.
1325 # cached value from readsubtree directly.
1326 selflazy[f] = (n, readsubtree, False)
1326 selflazy[f] = (n, readsubtree, False)
1327 elif b'/' in f:
1327 elif b'/' in f:
1328 # This is a flat manifest, so use __setitem__ and setflag rather
1328 # This is a flat manifest, so use __setitem__ and setflag rather
1329 # than assigning directly to _files and _flags, so we can
1329 # than assigning directly to _files and _flags, so we can
1330 # assign a path in a subdirectory, and to mark dirty (compared
1330 # assign a path in a subdirectory, and to mark dirty (compared
1331 # to nullid).
1331 # to nullid).
1332 self[f] = n
1332 self[f] = n
1333 if fl:
1333 if fl:
1334 self.setflag(f, fl)
1334 self.setflag(f, fl)
1335 else:
1335 else:
1336 # Assigning to _files and _flags avoids marking as dirty,
1336 # Assigning to _files and _flags avoids marking as dirty,
1337 # and should be a little faster.
1337 # and should be a little faster.
1338 self._files[f] = n
1338 self._files[f] = n
1339 if fl:
1339 if fl:
1340 self._flags[f] = fl
1340 self._flags[f] = fl
1341
1341
1342 def text(self):
1342 def text(self):
1343 """Get the full data of this manifest as a bytestring."""
1343 """Get the full data of this manifest as a bytestring."""
1344 self._load()
1344 self._load()
1345 return _text(self.iterentries())
1345 return _text(self.iterentries())
1346
1346
1347 def dirtext(self):
1347 def dirtext(self):
1348 """Get the full data of this directory as a bytestring. Make sure that
1348 """Get the full data of this directory as a bytestring. Make sure that
1349 any submanifests have been written first, so their nodeids are correct.
1349 any submanifests have been written first, so their nodeids are correct.
1350 """
1350 """
1351 self._load()
1351 self._load()
1352 flags = self.flags
1352 flags = self.flags
1353 lazydirs = [
1353 lazydirs = [
1354 (d[:-1], v[0], b't') for d, v in pycompat.iteritems(self._lazydirs)
1354 (d[:-1], v[0], b't') for d, v in pycompat.iteritems(self._lazydirs)
1355 ]
1355 ]
1356 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1356 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1357 files = [(f, self._files[f], flags(f)) for f in self._files]
1357 files = [(f, self._files[f], flags(f)) for f in self._files]
1358 return _text(sorted(dirs + files + lazydirs))
1358 return _text(sorted(dirs + files + lazydirs))
1359
1359
1360 def read(self, gettext, readsubtree):
1360 def read(self, gettext, readsubtree):
1361 def _load_for_read(s):
1361 def _load_for_read(s):
1362 s.parse(gettext(), readsubtree)
1362 s.parse(gettext(), readsubtree)
1363 s._dirty = False
1363 s._dirty = False
1364
1364
1365 self._loadfunc = _load_for_read
1365 self._loadfunc = _load_for_read
1366
1366
1367 def writesubtrees(self, m1, m2, writesubtree, match):
1367 def writesubtrees(self, m1, m2, writesubtree, match):
1368 self._load() # for consistency; should never have any effect here
1368 self._load() # for consistency; should never have any effect here
1369 m1._load()
1369 m1._load()
1370 m2._load()
1370 m2._load()
1371 emptytree = treemanifest()
1371 emptytree = treemanifest()
1372
1372
1373 def getnode(m, d):
1373 def getnode(m, d):
1374 ld = m._lazydirs.get(d)
1374 ld = m._lazydirs.get(d)
1375 if ld:
1375 if ld:
1376 return ld[0]
1376 return ld[0]
1377 return m._dirs.get(d, emptytree)._node
1377 return m._dirs.get(d, emptytree)._node
1378
1378
1379 # let's skip investigating things that `match` says we do not need.
1379 # let's skip investigating things that `match` says we do not need.
1380 visit = match.visitchildrenset(self._dir[:-1])
1380 visit = match.visitchildrenset(self._dir[:-1])
1381 visit = self._loadchildrensetlazy(visit)
1381 visit = self._loadchildrensetlazy(visit)
1382 if visit == b'this' or visit == b'all':
1382 if visit == b'this' or visit == b'all':
1383 visit = None
1383 visit = None
1384 for d, subm in pycompat.iteritems(self._dirs):
1384 for d, subm in pycompat.iteritems(self._dirs):
1385 if visit and d[:-1] not in visit:
1385 if visit and d[:-1] not in visit:
1386 continue
1386 continue
1387 subp1 = getnode(m1, d)
1387 subp1 = getnode(m1, d)
1388 subp2 = getnode(m2, d)
1388 subp2 = getnode(m2, d)
1389 if subp1 == nullid:
1389 if subp1 == nullid:
1390 subp1, subp2 = subp2, subp1
1390 subp1, subp2 = subp2, subp1
1391 writesubtree(subm, subp1, subp2, match)
1391 writesubtree(subm, subp1, subp2, match)
1392
1392
1393 def walksubtrees(self, matcher=None):
1393 def walksubtrees(self, matcher=None):
1394 """Returns an iterator of the subtrees of this manifest, including this
1394 """Returns an iterator of the subtrees of this manifest, including this
1395 manifest itself.
1395 manifest itself.
1396
1396
1397 If `matcher` is provided, it only returns subtrees that match.
1397 If `matcher` is provided, it only returns subtrees that match.
1398 """
1398 """
1399 if matcher and not matcher.visitdir(self._dir[:-1]):
1399 if matcher and not matcher.visitdir(self._dir[:-1]):
1400 return
1400 return
1401 if not matcher or matcher(self._dir[:-1]):
1401 if not matcher or matcher(self._dir[:-1]):
1402 yield self
1402 yield self
1403
1403
1404 self._load()
1404 self._load()
1405 # OPT: use visitchildrenset to avoid loading everything.
1405 # OPT: use visitchildrenset to avoid loading everything.
1406 self._loadalllazy()
1406 self._loadalllazy()
1407 for d, subm in pycompat.iteritems(self._dirs):
1407 for d, subm in pycompat.iteritems(self._dirs):
1408 for subtree in subm.walksubtrees(matcher=matcher):
1408 for subtree in subm.walksubtrees(matcher=matcher):
1409 yield subtree
1409 yield subtree
1410
1410
1411
1411
1412 class manifestfulltextcache(util.lrucachedict):
1412 class manifestfulltextcache(util.lrucachedict):
1413 """File-backed LRU cache for the manifest cache
1413 """File-backed LRU cache for the manifest cache
1414
1414
1415 File consists of entries, up to EOF:
1415 File consists of entries, up to EOF:
1416
1416
1417 - 20 bytes node, 4 bytes length, <length> manifest data
1417 - 20 bytes node, 4 bytes length, <length> manifest data
1418
1418
1419 These are written in reverse cache order (oldest to newest).
1419 These are written in reverse cache order (oldest to newest).
1420
1420
1421 """
1421 """
1422
1422
1423 _file = b'manifestfulltextcache'
1423 _file = b'manifestfulltextcache'
1424
1424
1425 def __init__(self, max):
1425 def __init__(self, max):
1426 super(manifestfulltextcache, self).__init__(max)
1426 super(manifestfulltextcache, self).__init__(max)
1427 self._dirty = False
1427 self._dirty = False
1428 self._read = False
1428 self._read = False
1429 self._opener = None
1429 self._opener = None
1430
1430
1431 def read(self):
1431 def read(self):
1432 if self._read or self._opener is None:
1432 if self._read or self._opener is None:
1433 return
1433 return
1434
1434
1435 try:
1435 try:
1436 with self._opener(self._file) as fp:
1436 with self._opener(self._file) as fp:
1437 set = super(manifestfulltextcache, self).__setitem__
1437 set = super(manifestfulltextcache, self).__setitem__
1438 # ignore trailing data, this is a cache, corruption is skipped
1438 # ignore trailing data, this is a cache, corruption is skipped
1439 while True:
1439 while True:
1440 # TODO do we need to do work here for sha1 portability?
1440 # TODO do we need to do work here for sha1 portability?
1441 node = fp.read(20)
1441 node = fp.read(20)
1442 if len(node) < 20:
1442 if len(node) < 20:
1443 break
1443 break
1444 try:
1444 try:
1445 size = struct.unpack(b'>L', fp.read(4))[0]
1445 size = struct.unpack(b'>L', fp.read(4))[0]
1446 except struct.error:
1446 except struct.error:
1447 break
1447 break
1448 value = bytearray(fp.read(size))
1448 value = bytearray(fp.read(size))
1449 if len(value) != size:
1449 if len(value) != size:
1450 break
1450 break
1451 set(node, value)
1451 set(node, value)
1452 except IOError:
1452 except IOError:
1453 # the file is allowed to be missing
1453 # the file is allowed to be missing
1454 pass
1454 pass
1455
1455
1456 self._read = True
1456 self._read = True
1457 self._dirty = False
1457 self._dirty = False
1458
1458
1459 def write(self):
1459 def write(self):
1460 if not self._dirty or self._opener is None:
1460 if not self._dirty or self._opener is None:
1461 return
1461 return
1462 # rotate backwards to the first used node
1462 # rotate backwards to the first used node
1463 try:
1463 try:
1464 with self._opener(
1464 with self._opener(
1465 self._file, b'w', atomictemp=True, checkambig=True
1465 self._file, b'w', atomictemp=True, checkambig=True
1466 ) as fp:
1466 ) as fp:
1467 node = self._head.prev
1467 node = self._head.prev
1468 while True:
1468 while True:
1469 if node.key in self._cache:
1469 if node.key in self._cache:
1470 fp.write(node.key)
1470 fp.write(node.key)
1471 fp.write(struct.pack(b'>L', len(node.value)))
1471 fp.write(struct.pack(b'>L', len(node.value)))
1472 fp.write(node.value)
1472 fp.write(node.value)
1473 if node is self._head:
1473 if node is self._head:
1474 break
1474 break
1475 node = node.prev
1475 node = node.prev
1476 except IOError:
1476 except IOError:
1477 # We could not write the cache (eg: permission error)
1477 # We could not write the cache (eg: permission error)
1478 # the content can be missing.
1478 # the content can be missing.
1479 #
1479 #
1480 # We could try harder and see if we could recreate a wcache
1480 # We could try harder and see if we could recreate a wcache
1481 # directory were we coudl write too.
1481 # directory were we coudl write too.
1482 #
1482 #
1483 # XXX the error pass silently, having some way to issue an error
1483 # XXX the error pass silently, having some way to issue an error
1484 # log `ui.log` would be nice.
1484 # log `ui.log` would be nice.
1485 pass
1485 pass
1486
1486
1487 def __len__(self):
1487 def __len__(self):
1488 if not self._read:
1488 if not self._read:
1489 self.read()
1489 self.read()
1490 return super(manifestfulltextcache, self).__len__()
1490 return super(manifestfulltextcache, self).__len__()
1491
1491
1492 def __contains__(self, k):
1492 def __contains__(self, k):
1493 if not self._read:
1493 if not self._read:
1494 self.read()
1494 self.read()
1495 return super(manifestfulltextcache, self).__contains__(k)
1495 return super(manifestfulltextcache, self).__contains__(k)
1496
1496
1497 def __iter__(self):
1497 def __iter__(self):
1498 if not self._read:
1498 if not self._read:
1499 self.read()
1499 self.read()
1500 return super(manifestfulltextcache, self).__iter__()
1500 return super(manifestfulltextcache, self).__iter__()
1501
1501
1502 def __getitem__(self, k):
1502 def __getitem__(self, k):
1503 if not self._read:
1503 if not self._read:
1504 self.read()
1504 self.read()
1505 # the cache lru order can change on read
1505 # the cache lru order can change on read
1506 setdirty = self._cache.get(k) is not self._head
1506 setdirty = self._cache.get(k) is not self._head
1507 value = super(manifestfulltextcache, self).__getitem__(k)
1507 value = super(manifestfulltextcache, self).__getitem__(k)
1508 if setdirty:
1508 if setdirty:
1509 self._dirty = True
1509 self._dirty = True
1510 return value
1510 return value
1511
1511
1512 def __setitem__(self, k, v):
1512 def __setitem__(self, k, v):
1513 if not self._read:
1513 if not self._read:
1514 self.read()
1514 self.read()
1515 super(manifestfulltextcache, self).__setitem__(k, v)
1515 super(manifestfulltextcache, self).__setitem__(k, v)
1516 self._dirty = True
1516 self._dirty = True
1517
1517
1518 def __delitem__(self, k):
1518 def __delitem__(self, k):
1519 if not self._read:
1519 if not self._read:
1520 self.read()
1520 self.read()
1521 super(manifestfulltextcache, self).__delitem__(k)
1521 super(manifestfulltextcache, self).__delitem__(k)
1522 self._dirty = True
1522 self._dirty = True
1523
1523
1524 def get(self, k, default=None):
1524 def get(self, k, default=None):
1525 if not self._read:
1525 if not self._read:
1526 self.read()
1526 self.read()
1527 return super(manifestfulltextcache, self).get(k, default=default)
1527 return super(manifestfulltextcache, self).get(k, default=default)
1528
1528
1529 def clear(self, clear_persisted_data=False):
1529 def clear(self, clear_persisted_data=False):
1530 super(manifestfulltextcache, self).clear()
1530 super(manifestfulltextcache, self).clear()
1531 if clear_persisted_data:
1531 if clear_persisted_data:
1532 self._dirty = True
1532 self._dirty = True
1533 self.write()
1533 self.write()
1534 self._read = False
1534 self._read = False
1535
1535
1536
1536
1537 # and upper bound of what we expect from compression
1537 # and upper bound of what we expect from compression
1538 # (real live value seems to be "3")
1538 # (real live value seems to be "3")
1539 MAXCOMPRESSION = 3
1539 MAXCOMPRESSION = 3
1540
1540
1541
1541
1542 class FastdeltaUnavailable(Exception):
1542 class FastdeltaUnavailable(Exception):
1543 """Exception raised when fastdelta isn't usable on a manifest."""
1543 """Exception raised when fastdelta isn't usable on a manifest."""
1544
1544
1545
1545
1546 @interfaceutil.implementer(repository.imanifeststorage)
1546 @interfaceutil.implementer(repository.imanifeststorage)
1547 class manifestrevlog(object):
1547 class manifestrevlog(object):
1548 """A revlog that stores manifest texts. This is responsible for caching the
1548 """A revlog that stores manifest texts. This is responsible for caching the
1549 full-text manifest contents.
1549 full-text manifest contents.
1550 """
1550 """
1551
1551
1552 def __init__(
1552 def __init__(
1553 self,
1553 self,
1554 opener,
1554 opener,
1555 tree=b'',
1555 tree=b'',
1556 dirlogcache=None,
1556 dirlogcache=None,
1557 indexfile=None,
1557 indexfile=None,
1558 treemanifest=False,
1558 treemanifest=False,
1559 ):
1559 ):
1560 """Constructs a new manifest revlog
1560 """Constructs a new manifest revlog
1561
1561
1562 `indexfile` - used by extensions to have two manifests at once, like
1562 `indexfile` - used by extensions to have two manifests at once, like
1563 when transitioning between flatmanifeset and treemanifests.
1563 when transitioning between flatmanifeset and treemanifests.
1564
1564
1565 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1565 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1566 options can also be used to make this a tree manifest revlog. The opener
1566 options can also be used to make this a tree manifest revlog. The opener
1567 option takes precedence, so if it is set to True, we ignore whatever
1567 option takes precedence, so if it is set to True, we ignore whatever
1568 value is passed in to the constructor.
1568 value is passed in to the constructor.
1569 """
1569 """
1570 # During normal operations, we expect to deal with not more than four
1570 # During normal operations, we expect to deal with not more than four
1571 # revs at a time (such as during commit --amend). When rebasing large
1571 # revs at a time (such as during commit --amend). When rebasing large
1572 # stacks of commits, the number can go up, hence the config knob below.
1572 # stacks of commits, the number can go up, hence the config knob below.
1573 cachesize = 4
1573 cachesize = 4
1574 optiontreemanifest = False
1574 optiontreemanifest = False
1575 opts = getattr(opener, 'options', None)
1575 opts = getattr(opener, 'options', None)
1576 if opts is not None:
1576 if opts is not None:
1577 cachesize = opts.get(b'manifestcachesize', cachesize)
1577 cachesize = opts.get(b'manifestcachesize', cachesize)
1578 optiontreemanifest = opts.get(b'treemanifest', False)
1578 optiontreemanifest = opts.get(b'treemanifest', False)
1579
1579
1580 self._treeondisk = optiontreemanifest or treemanifest
1580 self._treeondisk = optiontreemanifest or treemanifest
1581
1581
1582 self._fulltextcache = manifestfulltextcache(cachesize)
1582 self._fulltextcache = manifestfulltextcache(cachesize)
1583
1583
1584 if tree:
1584 if tree:
1585 assert self._treeondisk, b'opts is %r' % opts
1585 assert self._treeondisk, b'opts is %r' % opts
1586
1586
1587 if indexfile is None:
1587 if indexfile is None:
1588 indexfile = b'00manifest.i'
1588 indexfile = b'00manifest.i'
1589 if tree:
1589 if tree:
1590 indexfile = b"meta/" + tree + indexfile
1590 indexfile = b"meta/" + tree + indexfile
1591
1591
1592 self.tree = tree
1592 self.tree = tree
1593
1593
1594 # The dirlogcache is kept on the root manifest log
1594 # The dirlogcache is kept on the root manifest log
1595 if tree:
1595 if tree:
1596 self._dirlogcache = dirlogcache
1596 self._dirlogcache = dirlogcache
1597 else:
1597 else:
1598 self._dirlogcache = {b'': self}
1598 self._dirlogcache = {b'': self}
1599
1599
1600 self._revlog = revlog.revlog(
1600 self._revlog = revlog.revlog(
1601 opener,
1601 opener,
1602 indexfile,
1602 indexfile,
1603 # only root indexfile is cached
1603 # only root indexfile is cached
1604 checkambig=not bool(tree),
1604 checkambig=not bool(tree),
1605 mmaplargeindex=True,
1605 mmaplargeindex=True,
1606 upperboundcomp=MAXCOMPRESSION,
1606 upperboundcomp=MAXCOMPRESSION,
1607 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
1607 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
1608 )
1608 )
1609
1609
1610 self.index = self._revlog.index
1610 self.index = self._revlog.index
1611 self.version = self._revlog.version
1611 self.version = self._revlog.version
1612 self._generaldelta = self._revlog._generaldelta
1612 self._generaldelta = self._revlog._generaldelta
1613
1613
1614 def _setupmanifestcachehooks(self, repo):
1614 def _setupmanifestcachehooks(self, repo):
1615 """Persist the manifestfulltextcache on lock release"""
1615 """Persist the manifestfulltextcache on lock release"""
1616 if not util.safehasattr(repo, b'_wlockref'):
1616 if not util.safehasattr(repo, b'_wlockref'):
1617 return
1617 return
1618
1618
1619 self._fulltextcache._opener = repo.wcachevfs
1619 self._fulltextcache._opener = repo.wcachevfs
1620 if repo._currentlock(repo._wlockref) is None:
1620 if repo._currentlock(repo._wlockref) is None:
1621 return
1621 return
1622
1622
1623 reporef = weakref.ref(repo)
1623 reporef = weakref.ref(repo)
1624 manifestrevlogref = weakref.ref(self)
1624 manifestrevlogref = weakref.ref(self)
1625
1625
1626 def persistmanifestcache(success):
1626 def persistmanifestcache(success):
1627 # Repo is in an unknown state, do not persist.
1627 # Repo is in an unknown state, do not persist.
1628 if not success:
1628 if not success:
1629 return
1629 return
1630
1630
1631 repo = reporef()
1631 repo = reporef()
1632 self = manifestrevlogref()
1632 self = manifestrevlogref()
1633 if repo is None or self is None:
1633 if repo is None or self is None:
1634 return
1634 return
1635 if repo.manifestlog.getstorage(b'') is not self:
1635 if repo.manifestlog.getstorage(b'') is not self:
1636 # there's a different manifest in play now, abort
1636 # there's a different manifest in play now, abort
1637 return
1637 return
1638 self._fulltextcache.write()
1638 self._fulltextcache.write()
1639
1639
1640 repo._afterlock(persistmanifestcache)
1640 repo._afterlock(persistmanifestcache)
1641
1641
1642 @property
1642 @property
1643 def fulltextcache(self):
1643 def fulltextcache(self):
1644 return self._fulltextcache
1644 return self._fulltextcache
1645
1645
1646 def clearcaches(self, clear_persisted_data=False):
1646 def clearcaches(self, clear_persisted_data=False):
1647 self._revlog.clearcaches()
1647 self._revlog.clearcaches()
1648 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1648 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1649 self._dirlogcache = {self.tree: self}
1649 self._dirlogcache = {self.tree: self}
1650
1650
1651 def dirlog(self, d):
1651 def dirlog(self, d):
1652 if d:
1652 if d:
1653 assert self._treeondisk
1653 assert self._treeondisk
1654 if d not in self._dirlogcache:
1654 if d not in self._dirlogcache:
1655 mfrevlog = manifestrevlog(
1655 mfrevlog = manifestrevlog(
1656 self.opener, d, self._dirlogcache, treemanifest=self._treeondisk
1656 self.opener, d, self._dirlogcache, treemanifest=self._treeondisk
1657 )
1657 )
1658 self._dirlogcache[d] = mfrevlog
1658 self._dirlogcache[d] = mfrevlog
1659 return self._dirlogcache[d]
1659 return self._dirlogcache[d]
1660
1660
1661 def add(
1661 def add(
1662 self,
1662 self,
1663 m,
1663 m,
1664 transaction,
1664 transaction,
1665 link,
1665 link,
1666 p1,
1666 p1,
1667 p2,
1667 p2,
1668 added,
1668 added,
1669 removed,
1669 removed,
1670 readtree=None,
1670 readtree=None,
1671 match=None,
1671 match=None,
1672 ):
1672 ):
1673 """add some manifest entry in to the manifest log
1673 """add some manifest entry in to the manifest log
1674
1674
1675 input:
1675 input:
1676
1676
1677 m: the manifest dict we want to store
1677 m: the manifest dict we want to store
1678 transaction: the open transaction
1678 transaction: the open transaction
1679 p1: manifest-node of p1
1679 p1: manifest-node of p1
1680 p2: manifest-node of p2
1680 p2: manifest-node of p2
1681 added: file added/changed compared to parent
1681 added: file added/changed compared to parent
1682 removed: file removed compared to parent
1682 removed: file removed compared to parent
1683
1683
1684 tree manifest input:
1684 tree manifest input:
1685
1685
1686 readtree: a function to read a subtree
1686 readtree: a function to read a subtree
1687 match: a filematcher for the subpart of the tree manifest
1687 match: a filematcher for the subpart of the tree manifest
1688 """
1688 """
1689 try:
1689 try:
1690 if p1 not in self.fulltextcache:
1690 if p1 not in self.fulltextcache:
1691 raise FastdeltaUnavailable()
1691 raise FastdeltaUnavailable()
1692 # If our first parent is in the manifest cache, we can
1692 # If our first parent is in the manifest cache, we can
1693 # compute a delta here using properties we know about the
1693 # compute a delta here using properties we know about the
1694 # manifest up-front, which may save time later for the
1694 # manifest up-front, which may save time later for the
1695 # revlog layer.
1695 # revlog layer.
1696
1696
1697 _checkforbidden(added)
1697 _checkforbidden(added)
1698 # combine the changed lists into one sorted iterator
1698 # combine the changed lists into one sorted iterator
1699 work = heapq.merge(
1699 work = heapq.merge(
1700 [(x, False) for x in sorted(added)],
1700 [(x, False) for x in sorted(added)],
1701 [(x, True) for x in sorted(removed)],
1701 [(x, True) for x in sorted(removed)],
1702 )
1702 )
1703
1703
1704 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1704 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1705 cachedelta = self._revlog.rev(p1), deltatext
1705 cachedelta = self._revlog.rev(p1), deltatext
1706 text = util.buffer(arraytext)
1706 text = util.buffer(arraytext)
1707 n = self._revlog.addrevision(
1707 n = self._revlog.addrevision(
1708 text, transaction, link, p1, p2, cachedelta
1708 text, transaction, link, p1, p2, cachedelta
1709 )
1709 )
1710 except FastdeltaUnavailable:
1710 except FastdeltaUnavailable:
1711 # The first parent manifest isn't already loaded or the
1711 # The first parent manifest isn't already loaded or the
1712 # manifest implementation doesn't support fastdelta, so
1712 # manifest implementation doesn't support fastdelta, so
1713 # we'll just encode a fulltext of the manifest and pass
1713 # we'll just encode a fulltext of the manifest and pass
1714 # that through to the revlog layer, and let it handle the
1714 # that through to the revlog layer, and let it handle the
1715 # delta process.
1715 # delta process.
1716 if self._treeondisk:
1716 if self._treeondisk:
1717 assert readtree, b"readtree must be set for treemanifest writes"
1717 assert readtree, b"readtree must be set for treemanifest writes"
1718 assert match, b"match must be specified for treemanifest writes"
1718 assert match, b"match must be specified for treemanifest writes"
1719 m1 = readtree(self.tree, p1)
1719 m1 = readtree(self.tree, p1)
1720 m2 = readtree(self.tree, p2)
1720 m2 = readtree(self.tree, p2)
1721 n = self._addtree(
1721 n = self._addtree(
1722 m, transaction, link, m1, m2, readtree, match=match
1722 m, transaction, link, m1, m2, readtree, match=match
1723 )
1723 )
1724 arraytext = None
1724 arraytext = None
1725 else:
1725 else:
1726 text = m.text()
1726 text = m.text()
1727 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1727 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1728 arraytext = bytearray(text)
1728 arraytext = bytearray(text)
1729
1729
1730 if arraytext is not None:
1730 if arraytext is not None:
1731 self.fulltextcache[n] = arraytext
1731 self.fulltextcache[n] = arraytext
1732
1732
1733 return n
1733 return n
1734
1734
1735 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1735 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1736 # If the manifest is unchanged compared to one parent,
1736 # If the manifest is unchanged compared to one parent,
1737 # don't write a new revision
1737 # don't write a new revision
1738 if self.tree != b'' and (
1738 if self.tree != b'' and (
1739 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1739 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1740 ):
1740 ):
1741 return m.node()
1741 return m.node()
1742
1742
1743 def writesubtree(subm, subp1, subp2, match):
1743 def writesubtree(subm, subp1, subp2, match):
1744 sublog = self.dirlog(subm.dir())
1744 sublog = self.dirlog(subm.dir())
1745 sublog.add(
1745 sublog.add(
1746 subm,
1746 subm,
1747 transaction,
1747 transaction,
1748 link,
1748 link,
1749 subp1,
1749 subp1,
1750 subp2,
1750 subp2,
1751 None,
1751 None,
1752 None,
1752 None,
1753 readtree=readtree,
1753 readtree=readtree,
1754 match=match,
1754 match=match,
1755 )
1755 )
1756
1756
1757 m.writesubtrees(m1, m2, writesubtree, match)
1757 m.writesubtrees(m1, m2, writesubtree, match)
1758 text = m.dirtext()
1758 text = m.dirtext()
1759 n = None
1759 n = None
1760 if self.tree != b'':
1760 if self.tree != b'':
1761 # Double-check whether contents are unchanged to one parent
1761 # Double-check whether contents are unchanged to one parent
1762 if text == m1.dirtext():
1762 if text == m1.dirtext():
1763 n = m1.node()
1763 n = m1.node()
1764 elif text == m2.dirtext():
1764 elif text == m2.dirtext():
1765 n = m2.node()
1765 n = m2.node()
1766
1766
1767 if not n:
1767 if not n:
1768 n = self._revlog.addrevision(
1768 n = self._revlog.addrevision(
1769 text, transaction, link, m1.node(), m2.node()
1769 text, transaction, link, m1.node(), m2.node()
1770 )
1770 )
1771
1771
1772 # Save nodeid so parent manifest can calculate its nodeid
1772 # Save nodeid so parent manifest can calculate its nodeid
1773 m.setnode(n)
1773 m.setnode(n)
1774 return n
1774 return n
1775
1775
1776 def __len__(self):
1776 def __len__(self):
1777 return len(self._revlog)
1777 return len(self._revlog)
1778
1778
1779 def __iter__(self):
1779 def __iter__(self):
1780 return self._revlog.__iter__()
1780 return self._revlog.__iter__()
1781
1781
1782 def rev(self, node):
1782 def rev(self, node):
1783 return self._revlog.rev(node)
1783 return self._revlog.rev(node)
1784
1784
1785 def node(self, rev):
1785 def node(self, rev):
1786 return self._revlog.node(rev)
1786 return self._revlog.node(rev)
1787
1787
1788 def lookup(self, value):
1788 def lookup(self, value):
1789 return self._revlog.lookup(value)
1789 return self._revlog.lookup(value)
1790
1790
1791 def parentrevs(self, rev):
1791 def parentrevs(self, rev):
1792 return self._revlog.parentrevs(rev)
1792 return self._revlog.parentrevs(rev)
1793
1793
1794 def parents(self, node):
1794 def parents(self, node):
1795 return self._revlog.parents(node)
1795 return self._revlog.parents(node)
1796
1796
1797 def linkrev(self, rev):
1797 def linkrev(self, rev):
1798 return self._revlog.linkrev(rev)
1798 return self._revlog.linkrev(rev)
1799
1799
1800 def checksize(self):
1800 def checksize(self):
1801 return self._revlog.checksize()
1801 return self._revlog.checksize()
1802
1802
1803 def revision(self, node, _df=None, raw=False):
1803 def revision(self, node, _df=None, raw=False):
1804 return self._revlog.revision(node, _df=_df, raw=raw)
1804 return self._revlog.revision(node, _df=_df, raw=raw)
1805
1805
1806 def rawdata(self, node, _df=None):
1806 def rawdata(self, node, _df=None):
1807 return self._revlog.rawdata(node, _df=_df)
1807 return self._revlog.rawdata(node, _df=_df)
1808
1808
1809 def revdiff(self, rev1, rev2):
1809 def revdiff(self, rev1, rev2):
1810 return self._revlog.revdiff(rev1, rev2)
1810 return self._revlog.revdiff(rev1, rev2)
1811
1811
1812 def cmp(self, node, text):
1812 def cmp(self, node, text):
1813 return self._revlog.cmp(node, text)
1813 return self._revlog.cmp(node, text)
1814
1814
1815 def deltaparent(self, rev):
1815 def deltaparent(self, rev):
1816 return self._revlog.deltaparent(rev)
1816 return self._revlog.deltaparent(rev)
1817
1817
1818 def emitrevisions(
1818 def emitrevisions(
1819 self,
1819 self,
1820 nodes,
1820 nodes,
1821 nodesorder=None,
1821 nodesorder=None,
1822 revisiondata=False,
1822 revisiondata=False,
1823 assumehaveparentrevisions=False,
1823 assumehaveparentrevisions=False,
1824 deltamode=repository.CG_DELTAMODE_STD,
1824 deltamode=repository.CG_DELTAMODE_STD,
1825 ):
1825 ):
1826 return self._revlog.emitrevisions(
1826 return self._revlog.emitrevisions(
1827 nodes,
1827 nodes,
1828 nodesorder=nodesorder,
1828 nodesorder=nodesorder,
1829 revisiondata=revisiondata,
1829 revisiondata=revisiondata,
1830 assumehaveparentrevisions=assumehaveparentrevisions,
1830 assumehaveparentrevisions=assumehaveparentrevisions,
1831 deltamode=deltamode,
1831 deltamode=deltamode,
1832 )
1832 )
1833
1833
1834 def addgroup(
1834 def addgroup(
1835 self,
1835 self,
1836 deltas,
1836 deltas,
1837 linkmapper,
1837 linkmapper,
1838 transaction,
1838 transaction,
1839 alwayscache=False,
1839 addrevisioncb=None,
1840 addrevisioncb=None,
1840 duplicaterevisioncb=None,
1841 duplicaterevisioncb=None,
1841 ):
1842 ):
1842 return self._revlog.addgroup(
1843 return self._revlog.addgroup(
1843 deltas,
1844 deltas,
1844 linkmapper,
1845 linkmapper,
1845 transaction,
1846 transaction,
1847 alwayscache=alwayscache,
1846 addrevisioncb=addrevisioncb,
1848 addrevisioncb=addrevisioncb,
1847 duplicaterevisioncb=duplicaterevisioncb,
1849 duplicaterevisioncb=duplicaterevisioncb,
1848 )
1850 )
1849
1851
1850 def rawsize(self, rev):
1852 def rawsize(self, rev):
1851 return self._revlog.rawsize(rev)
1853 return self._revlog.rawsize(rev)
1852
1854
1853 def getstrippoint(self, minlink):
1855 def getstrippoint(self, minlink):
1854 return self._revlog.getstrippoint(minlink)
1856 return self._revlog.getstrippoint(minlink)
1855
1857
1856 def strip(self, minlink, transaction):
1858 def strip(self, minlink, transaction):
1857 return self._revlog.strip(minlink, transaction)
1859 return self._revlog.strip(minlink, transaction)
1858
1860
1859 def files(self):
1861 def files(self):
1860 return self._revlog.files()
1862 return self._revlog.files()
1861
1863
1862 def clone(self, tr, destrevlog, **kwargs):
1864 def clone(self, tr, destrevlog, **kwargs):
1863 if not isinstance(destrevlog, manifestrevlog):
1865 if not isinstance(destrevlog, manifestrevlog):
1864 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1866 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1865
1867
1866 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1868 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1867
1869
1868 def storageinfo(
1870 def storageinfo(
1869 self,
1871 self,
1870 exclusivefiles=False,
1872 exclusivefiles=False,
1871 sharedfiles=False,
1873 sharedfiles=False,
1872 revisionscount=False,
1874 revisionscount=False,
1873 trackedsize=False,
1875 trackedsize=False,
1874 storedsize=False,
1876 storedsize=False,
1875 ):
1877 ):
1876 return self._revlog.storageinfo(
1878 return self._revlog.storageinfo(
1877 exclusivefiles=exclusivefiles,
1879 exclusivefiles=exclusivefiles,
1878 sharedfiles=sharedfiles,
1880 sharedfiles=sharedfiles,
1879 revisionscount=revisionscount,
1881 revisionscount=revisionscount,
1880 trackedsize=trackedsize,
1882 trackedsize=trackedsize,
1881 storedsize=storedsize,
1883 storedsize=storedsize,
1882 )
1884 )
1883
1885
1884 @property
1886 @property
1885 def indexfile(self):
1887 def indexfile(self):
1886 return self._revlog.indexfile
1888 return self._revlog.indexfile
1887
1889
1888 @indexfile.setter
1890 @indexfile.setter
1889 def indexfile(self, value):
1891 def indexfile(self, value):
1890 self._revlog.indexfile = value
1892 self._revlog.indexfile = value
1891
1893
1892 @property
1894 @property
1893 def opener(self):
1895 def opener(self):
1894 return self._revlog.opener
1896 return self._revlog.opener
1895
1897
1896 @opener.setter
1898 @opener.setter
1897 def opener(self, value):
1899 def opener(self, value):
1898 self._revlog.opener = value
1900 self._revlog.opener = value
1899
1901
1900
1902
1901 @interfaceutil.implementer(repository.imanifestlog)
1903 @interfaceutil.implementer(repository.imanifestlog)
1902 class manifestlog(object):
1904 class manifestlog(object):
1903 """A collection class representing the collection of manifest snapshots
1905 """A collection class representing the collection of manifest snapshots
1904 referenced by commits in the repository.
1906 referenced by commits in the repository.
1905
1907
1906 In this situation, 'manifest' refers to the abstract concept of a snapshot
1908 In this situation, 'manifest' refers to the abstract concept of a snapshot
1907 of the list of files in the given commit. Consumers of the output of this
1909 of the list of files in the given commit. Consumers of the output of this
1908 class do not care about the implementation details of the actual manifests
1910 class do not care about the implementation details of the actual manifests
1909 they receive (i.e. tree or flat or lazily loaded, etc)."""
1911 they receive (i.e. tree or flat or lazily loaded, etc)."""
1910
1912
1911 def __init__(self, opener, repo, rootstore, narrowmatch):
1913 def __init__(self, opener, repo, rootstore, narrowmatch):
1912 usetreemanifest = False
1914 usetreemanifest = False
1913 cachesize = 4
1915 cachesize = 4
1914
1916
1915 opts = getattr(opener, 'options', None)
1917 opts = getattr(opener, 'options', None)
1916 if opts is not None:
1918 if opts is not None:
1917 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1919 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1918 cachesize = opts.get(b'manifestcachesize', cachesize)
1920 cachesize = opts.get(b'manifestcachesize', cachesize)
1919
1921
1920 self._treemanifests = usetreemanifest
1922 self._treemanifests = usetreemanifest
1921
1923
1922 self._rootstore = rootstore
1924 self._rootstore = rootstore
1923 self._rootstore._setupmanifestcachehooks(repo)
1925 self._rootstore._setupmanifestcachehooks(repo)
1924 self._narrowmatch = narrowmatch
1926 self._narrowmatch = narrowmatch
1925
1927
1926 # A cache of the manifestctx or treemanifestctx for each directory
1928 # A cache of the manifestctx or treemanifestctx for each directory
1927 self._dirmancache = {}
1929 self._dirmancache = {}
1928 self._dirmancache[b''] = util.lrucachedict(cachesize)
1930 self._dirmancache[b''] = util.lrucachedict(cachesize)
1929
1931
1930 self._cachesize = cachesize
1932 self._cachesize = cachesize
1931
1933
1932 def __getitem__(self, node):
1934 def __getitem__(self, node):
1933 """Retrieves the manifest instance for the given node. Throws a
1935 """Retrieves the manifest instance for the given node. Throws a
1934 LookupError if not found.
1936 LookupError if not found.
1935 """
1937 """
1936 return self.get(b'', node)
1938 return self.get(b'', node)
1937
1939
1938 def get(self, tree, node, verify=True):
1940 def get(self, tree, node, verify=True):
1939 """Retrieves the manifest instance for the given node. Throws a
1941 """Retrieves the manifest instance for the given node. Throws a
1940 LookupError if not found.
1942 LookupError if not found.
1941
1943
1942 `verify` - if True an exception will be thrown if the node is not in
1944 `verify` - if True an exception will be thrown if the node is not in
1943 the revlog
1945 the revlog
1944 """
1946 """
1945 if node in self._dirmancache.get(tree, ()):
1947 if node in self._dirmancache.get(tree, ()):
1946 return self._dirmancache[tree][node]
1948 return self._dirmancache[tree][node]
1947
1949
1948 if not self._narrowmatch.always():
1950 if not self._narrowmatch.always():
1949 if not self._narrowmatch.visitdir(tree[:-1]):
1951 if not self._narrowmatch.visitdir(tree[:-1]):
1950 return excludeddirmanifestctx(tree, node)
1952 return excludeddirmanifestctx(tree, node)
1951 if tree:
1953 if tree:
1952 if self._rootstore._treeondisk:
1954 if self._rootstore._treeondisk:
1953 if verify:
1955 if verify:
1954 # Side-effect is LookupError is raised if node doesn't
1956 # Side-effect is LookupError is raised if node doesn't
1955 # exist.
1957 # exist.
1956 self.getstorage(tree).rev(node)
1958 self.getstorage(tree).rev(node)
1957
1959
1958 m = treemanifestctx(self, tree, node)
1960 m = treemanifestctx(self, tree, node)
1959 else:
1961 else:
1960 raise error.Abort(
1962 raise error.Abort(
1961 _(
1963 _(
1962 b"cannot ask for manifest directory '%s' in a flat "
1964 b"cannot ask for manifest directory '%s' in a flat "
1963 b"manifest"
1965 b"manifest"
1964 )
1966 )
1965 % tree
1967 % tree
1966 )
1968 )
1967 else:
1969 else:
1968 if verify:
1970 if verify:
1969 # Side-effect is LookupError is raised if node doesn't exist.
1971 # Side-effect is LookupError is raised if node doesn't exist.
1970 self._rootstore.rev(node)
1972 self._rootstore.rev(node)
1971
1973
1972 if self._treemanifests:
1974 if self._treemanifests:
1973 m = treemanifestctx(self, b'', node)
1975 m = treemanifestctx(self, b'', node)
1974 else:
1976 else:
1975 m = manifestctx(self, node)
1977 m = manifestctx(self, node)
1976
1978
1977 if node != nullid:
1979 if node != nullid:
1978 mancache = self._dirmancache.get(tree)
1980 mancache = self._dirmancache.get(tree)
1979 if not mancache:
1981 if not mancache:
1980 mancache = util.lrucachedict(self._cachesize)
1982 mancache = util.lrucachedict(self._cachesize)
1981 self._dirmancache[tree] = mancache
1983 self._dirmancache[tree] = mancache
1982 mancache[node] = m
1984 mancache[node] = m
1983 return m
1985 return m
1984
1986
1985 def getstorage(self, tree):
1987 def getstorage(self, tree):
1986 return self._rootstore.dirlog(tree)
1988 return self._rootstore.dirlog(tree)
1987
1989
1988 def clearcaches(self, clear_persisted_data=False):
1990 def clearcaches(self, clear_persisted_data=False):
1989 self._dirmancache.clear()
1991 self._dirmancache.clear()
1990 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1992 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1991
1993
1992 def rev(self, node):
1994 def rev(self, node):
1993 return self._rootstore.rev(node)
1995 return self._rootstore.rev(node)
1994
1996
1995 def update_caches(self, transaction):
1997 def update_caches(self, transaction):
1996 return self._rootstore._revlog.update_caches(transaction=transaction)
1998 return self._rootstore._revlog.update_caches(transaction=transaction)
1997
1999
1998
2000
1999 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2001 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2000 class memmanifestctx(object):
2002 class memmanifestctx(object):
2001 def __init__(self, manifestlog):
2003 def __init__(self, manifestlog):
2002 self._manifestlog = manifestlog
2004 self._manifestlog = manifestlog
2003 self._manifestdict = manifestdict()
2005 self._manifestdict = manifestdict()
2004
2006
2005 def _storage(self):
2007 def _storage(self):
2006 return self._manifestlog.getstorage(b'')
2008 return self._manifestlog.getstorage(b'')
2007
2009
2008 def copy(self):
2010 def copy(self):
2009 memmf = memmanifestctx(self._manifestlog)
2011 memmf = memmanifestctx(self._manifestlog)
2010 memmf._manifestdict = self.read().copy()
2012 memmf._manifestdict = self.read().copy()
2011 return memmf
2013 return memmf
2012
2014
2013 def read(self):
2015 def read(self):
2014 return self._manifestdict
2016 return self._manifestdict
2015
2017
2016 def write(self, transaction, link, p1, p2, added, removed, match=None):
2018 def write(self, transaction, link, p1, p2, added, removed, match=None):
2017 return self._storage().add(
2019 return self._storage().add(
2018 self._manifestdict,
2020 self._manifestdict,
2019 transaction,
2021 transaction,
2020 link,
2022 link,
2021 p1,
2023 p1,
2022 p2,
2024 p2,
2023 added,
2025 added,
2024 removed,
2026 removed,
2025 match=match,
2027 match=match,
2026 )
2028 )
2027
2029
2028
2030
2029 @interfaceutil.implementer(repository.imanifestrevisionstored)
2031 @interfaceutil.implementer(repository.imanifestrevisionstored)
2030 class manifestctx(object):
2032 class manifestctx(object):
2031 """A class representing a single revision of a manifest, including its
2033 """A class representing a single revision of a manifest, including its
2032 contents, its parent revs, and its linkrev.
2034 contents, its parent revs, and its linkrev.
2033 """
2035 """
2034
2036
2035 def __init__(self, manifestlog, node):
2037 def __init__(self, manifestlog, node):
2036 self._manifestlog = manifestlog
2038 self._manifestlog = manifestlog
2037 self._data = None
2039 self._data = None
2038
2040
2039 self._node = node
2041 self._node = node
2040
2042
2041 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2043 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2042 # but let's add it later when something needs it and we can load it
2044 # but let's add it later when something needs it and we can load it
2043 # lazily.
2045 # lazily.
2044 # self.p1, self.p2 = store.parents(node)
2046 # self.p1, self.p2 = store.parents(node)
2045 # rev = store.rev(node)
2047 # rev = store.rev(node)
2046 # self.linkrev = store.linkrev(rev)
2048 # self.linkrev = store.linkrev(rev)
2047
2049
2048 def _storage(self):
2050 def _storage(self):
2049 return self._manifestlog.getstorage(b'')
2051 return self._manifestlog.getstorage(b'')
2050
2052
2051 def node(self):
2053 def node(self):
2052 return self._node
2054 return self._node
2053
2055
2054 def copy(self):
2056 def copy(self):
2055 memmf = memmanifestctx(self._manifestlog)
2057 memmf = memmanifestctx(self._manifestlog)
2056 memmf._manifestdict = self.read().copy()
2058 memmf._manifestdict = self.read().copy()
2057 return memmf
2059 return memmf
2058
2060
2059 @propertycache
2061 @propertycache
2060 def parents(self):
2062 def parents(self):
2061 return self._storage().parents(self._node)
2063 return self._storage().parents(self._node)
2062
2064
2063 def read(self):
2065 def read(self):
2064 if self._data is None:
2066 if self._data is None:
2065 if self._node == nullid:
2067 if self._node == nullid:
2066 self._data = manifestdict()
2068 self._data = manifestdict()
2067 else:
2069 else:
2068 store = self._storage()
2070 store = self._storage()
2069 if self._node in store.fulltextcache:
2071 if self._node in store.fulltextcache:
2070 text = pycompat.bytestr(store.fulltextcache[self._node])
2072 text = pycompat.bytestr(store.fulltextcache[self._node])
2071 else:
2073 else:
2072 text = store.revision(self._node)
2074 text = store.revision(self._node)
2073 arraytext = bytearray(text)
2075 arraytext = bytearray(text)
2074 store.fulltextcache[self._node] = arraytext
2076 store.fulltextcache[self._node] = arraytext
2075 self._data = manifestdict(text)
2077 self._data = manifestdict(text)
2076 return self._data
2078 return self._data
2077
2079
2078 def readfast(self, shallow=False):
2080 def readfast(self, shallow=False):
2079 """Calls either readdelta or read, based on which would be less work.
2081 """Calls either readdelta or read, based on which would be less work.
2080 readdelta is called if the delta is against the p1, and therefore can be
2082 readdelta is called if the delta is against the p1, and therefore can be
2081 read quickly.
2083 read quickly.
2082
2084
2083 If `shallow` is True, nothing changes since this is a flat manifest.
2085 If `shallow` is True, nothing changes since this is a flat manifest.
2084 """
2086 """
2085 store = self._storage()
2087 store = self._storage()
2086 r = store.rev(self._node)
2088 r = store.rev(self._node)
2087 deltaparent = store.deltaparent(r)
2089 deltaparent = store.deltaparent(r)
2088 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2090 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2089 return self.readdelta()
2091 return self.readdelta()
2090 return self.read()
2092 return self.read()
2091
2093
2092 def readdelta(self, shallow=False):
2094 def readdelta(self, shallow=False):
2093 """Returns a manifest containing just the entries that are present
2095 """Returns a manifest containing just the entries that are present
2094 in this manifest, but not in its p1 manifest. This is efficient to read
2096 in this manifest, but not in its p1 manifest. This is efficient to read
2095 if the revlog delta is already p1.
2097 if the revlog delta is already p1.
2096
2098
2097 Changing the value of `shallow` has no effect on flat manifests.
2099 Changing the value of `shallow` has no effect on flat manifests.
2098 """
2100 """
2099 store = self._storage()
2101 store = self._storage()
2100 r = store.rev(self._node)
2102 r = store.rev(self._node)
2101 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2103 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2102 return manifestdict(d)
2104 return manifestdict(d)
2103
2105
2104 def find(self, key):
2106 def find(self, key):
2105 return self.read().find(key)
2107 return self.read().find(key)
2106
2108
2107
2109
2108 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2110 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2109 class memtreemanifestctx(object):
2111 class memtreemanifestctx(object):
2110 def __init__(self, manifestlog, dir=b''):
2112 def __init__(self, manifestlog, dir=b''):
2111 self._manifestlog = manifestlog
2113 self._manifestlog = manifestlog
2112 self._dir = dir
2114 self._dir = dir
2113 self._treemanifest = treemanifest()
2115 self._treemanifest = treemanifest()
2114
2116
2115 def _storage(self):
2117 def _storage(self):
2116 return self._manifestlog.getstorage(b'')
2118 return self._manifestlog.getstorage(b'')
2117
2119
2118 def copy(self):
2120 def copy(self):
2119 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2121 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2120 memmf._treemanifest = self._treemanifest.copy()
2122 memmf._treemanifest = self._treemanifest.copy()
2121 return memmf
2123 return memmf
2122
2124
2123 def read(self):
2125 def read(self):
2124 return self._treemanifest
2126 return self._treemanifest
2125
2127
2126 def write(self, transaction, link, p1, p2, added, removed, match=None):
2128 def write(self, transaction, link, p1, p2, added, removed, match=None):
2127 def readtree(dir, node):
2129 def readtree(dir, node):
2128 return self._manifestlog.get(dir, node).read()
2130 return self._manifestlog.get(dir, node).read()
2129
2131
2130 return self._storage().add(
2132 return self._storage().add(
2131 self._treemanifest,
2133 self._treemanifest,
2132 transaction,
2134 transaction,
2133 link,
2135 link,
2134 p1,
2136 p1,
2135 p2,
2137 p2,
2136 added,
2138 added,
2137 removed,
2139 removed,
2138 readtree=readtree,
2140 readtree=readtree,
2139 match=match,
2141 match=match,
2140 )
2142 )
2141
2143
2142
2144
2143 @interfaceutil.implementer(repository.imanifestrevisionstored)
2145 @interfaceutil.implementer(repository.imanifestrevisionstored)
2144 class treemanifestctx(object):
2146 class treemanifestctx(object):
2145 def __init__(self, manifestlog, dir, node):
2147 def __init__(self, manifestlog, dir, node):
2146 self._manifestlog = manifestlog
2148 self._manifestlog = manifestlog
2147 self._dir = dir
2149 self._dir = dir
2148 self._data = None
2150 self._data = None
2149
2151
2150 self._node = node
2152 self._node = node
2151
2153
2152 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2154 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2153 # we can instantiate treemanifestctx objects for directories we don't
2155 # we can instantiate treemanifestctx objects for directories we don't
2154 # have on disk.
2156 # have on disk.
2155 # self.p1, self.p2 = store.parents(node)
2157 # self.p1, self.p2 = store.parents(node)
2156 # rev = store.rev(node)
2158 # rev = store.rev(node)
2157 # self.linkrev = store.linkrev(rev)
2159 # self.linkrev = store.linkrev(rev)
2158
2160
2159 def _storage(self):
2161 def _storage(self):
2160 narrowmatch = self._manifestlog._narrowmatch
2162 narrowmatch = self._manifestlog._narrowmatch
2161 if not narrowmatch.always():
2163 if not narrowmatch.always():
2162 if not narrowmatch.visitdir(self._dir[:-1]):
2164 if not narrowmatch.visitdir(self._dir[:-1]):
2163 return excludedmanifestrevlog(self._dir)
2165 return excludedmanifestrevlog(self._dir)
2164 return self._manifestlog.getstorage(self._dir)
2166 return self._manifestlog.getstorage(self._dir)
2165
2167
2166 def read(self):
2168 def read(self):
2167 if self._data is None:
2169 if self._data is None:
2168 store = self._storage()
2170 store = self._storage()
2169 if self._node == nullid:
2171 if self._node == nullid:
2170 self._data = treemanifest()
2172 self._data = treemanifest()
2171 # TODO accessing non-public API
2173 # TODO accessing non-public API
2172 elif store._treeondisk:
2174 elif store._treeondisk:
2173 m = treemanifest(dir=self._dir)
2175 m = treemanifest(dir=self._dir)
2174
2176
2175 def gettext():
2177 def gettext():
2176 return store.revision(self._node)
2178 return store.revision(self._node)
2177
2179
2178 def readsubtree(dir, subm):
2180 def readsubtree(dir, subm):
2179 # Set verify to False since we need to be able to create
2181 # Set verify to False since we need to be able to create
2180 # subtrees for trees that don't exist on disk.
2182 # subtrees for trees that don't exist on disk.
2181 return self._manifestlog.get(dir, subm, verify=False).read()
2183 return self._manifestlog.get(dir, subm, verify=False).read()
2182
2184
2183 m.read(gettext, readsubtree)
2185 m.read(gettext, readsubtree)
2184 m.setnode(self._node)
2186 m.setnode(self._node)
2185 self._data = m
2187 self._data = m
2186 else:
2188 else:
2187 if self._node in store.fulltextcache:
2189 if self._node in store.fulltextcache:
2188 text = pycompat.bytestr(store.fulltextcache[self._node])
2190 text = pycompat.bytestr(store.fulltextcache[self._node])
2189 else:
2191 else:
2190 text = store.revision(self._node)
2192 text = store.revision(self._node)
2191 arraytext = bytearray(text)
2193 arraytext = bytearray(text)
2192 store.fulltextcache[self._node] = arraytext
2194 store.fulltextcache[self._node] = arraytext
2193 self._data = treemanifest(dir=self._dir, text=text)
2195 self._data = treemanifest(dir=self._dir, text=text)
2194
2196
2195 return self._data
2197 return self._data
2196
2198
2197 def node(self):
2199 def node(self):
2198 return self._node
2200 return self._node
2199
2201
2200 def copy(self):
2202 def copy(self):
2201 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2203 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2202 memmf._treemanifest = self.read().copy()
2204 memmf._treemanifest = self.read().copy()
2203 return memmf
2205 return memmf
2204
2206
2205 @propertycache
2207 @propertycache
2206 def parents(self):
2208 def parents(self):
2207 return self._storage().parents(self._node)
2209 return self._storage().parents(self._node)
2208
2210
2209 def readdelta(self, shallow=False):
2211 def readdelta(self, shallow=False):
2210 """Returns a manifest containing just the entries that are present
2212 """Returns a manifest containing just the entries that are present
2211 in this manifest, but not in its p1 manifest. This is efficient to read
2213 in this manifest, but not in its p1 manifest. This is efficient to read
2212 if the revlog delta is already p1.
2214 if the revlog delta is already p1.
2213
2215
2214 If `shallow` is True, this will read the delta for this directory,
2216 If `shallow` is True, this will read the delta for this directory,
2215 without recursively reading subdirectory manifests. Instead, any
2217 without recursively reading subdirectory manifests. Instead, any
2216 subdirectory entry will be reported as it appears in the manifest, i.e.
2218 subdirectory entry will be reported as it appears in the manifest, i.e.
2217 the subdirectory will be reported among files and distinguished only by
2219 the subdirectory will be reported among files and distinguished only by
2218 its 't' flag.
2220 its 't' flag.
2219 """
2221 """
2220 store = self._storage()
2222 store = self._storage()
2221 if shallow:
2223 if shallow:
2222 r = store.rev(self._node)
2224 r = store.rev(self._node)
2223 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2225 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2224 return manifestdict(d)
2226 return manifestdict(d)
2225 else:
2227 else:
2226 # Need to perform a slow delta
2228 # Need to perform a slow delta
2227 r0 = store.deltaparent(store.rev(self._node))
2229 r0 = store.deltaparent(store.rev(self._node))
2228 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2230 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2229 m1 = self.read()
2231 m1 = self.read()
2230 md = treemanifest(dir=self._dir)
2232 md = treemanifest(dir=self._dir)
2231 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2233 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2232 if n1:
2234 if n1:
2233 md[f] = n1
2235 md[f] = n1
2234 if fl1:
2236 if fl1:
2235 md.setflag(f, fl1)
2237 md.setflag(f, fl1)
2236 return md
2238 return md
2237
2239
2238 def readfast(self, shallow=False):
2240 def readfast(self, shallow=False):
2239 """Calls either readdelta or read, based on which would be less work.
2241 """Calls either readdelta or read, based on which would be less work.
2240 readdelta is called if the delta is against the p1, and therefore can be
2242 readdelta is called if the delta is against the p1, and therefore can be
2241 read quickly.
2243 read quickly.
2242
2244
2243 If `shallow` is True, it only returns the entries from this manifest,
2245 If `shallow` is True, it only returns the entries from this manifest,
2244 and not any submanifests.
2246 and not any submanifests.
2245 """
2247 """
2246 store = self._storage()
2248 store = self._storage()
2247 r = store.rev(self._node)
2249 r = store.rev(self._node)
2248 deltaparent = store.deltaparent(r)
2250 deltaparent = store.deltaparent(r)
2249 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2251 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2250 return self.readdelta(shallow=shallow)
2252 return self.readdelta(shallow=shallow)
2251
2253
2252 if shallow:
2254 if shallow:
2253 return manifestdict(store.revision(self._node))
2255 return manifestdict(store.revision(self._node))
2254 else:
2256 else:
2255 return self.read()
2257 return self.read()
2256
2258
2257 def find(self, key):
2259 def find(self, key):
2258 return self.read().find(key)
2260 return self.read().find(key)
2259
2261
2260
2262
2261 class excludeddir(treemanifest):
2263 class excludeddir(treemanifest):
2262 """Stand-in for a directory that is excluded from the repository.
2264 """Stand-in for a directory that is excluded from the repository.
2263
2265
2264 With narrowing active on a repository that uses treemanifests,
2266 With narrowing active on a repository that uses treemanifests,
2265 some of the directory revlogs will be excluded from the resulting
2267 some of the directory revlogs will be excluded from the resulting
2266 clone. This is a huge storage win for clients, but means we need
2268 clone. This is a huge storage win for clients, but means we need
2267 some sort of pseudo-manifest to surface to internals so we can
2269 some sort of pseudo-manifest to surface to internals so we can
2268 detect a merge conflict outside the narrowspec. That's what this
2270 detect a merge conflict outside the narrowspec. That's what this
2269 class is: it stands in for a directory whose node is known, but
2271 class is: it stands in for a directory whose node is known, but
2270 whose contents are unknown.
2272 whose contents are unknown.
2271 """
2273 """
2272
2274
2273 def __init__(self, dir, node):
2275 def __init__(self, dir, node):
2274 super(excludeddir, self).__init__(dir)
2276 super(excludeddir, self).__init__(dir)
2275 self._node = node
2277 self._node = node
2276 # Add an empty file, which will be included by iterators and such,
2278 # Add an empty file, which will be included by iterators and such,
2277 # appearing as the directory itself (i.e. something like "dir/")
2279 # appearing as the directory itself (i.e. something like "dir/")
2278 self._files[b''] = node
2280 self._files[b''] = node
2279 self._flags[b''] = b't'
2281 self._flags[b''] = b't'
2280
2282
2281 # Manifests outside the narrowspec should never be modified, so avoid
2283 # Manifests outside the narrowspec should never be modified, so avoid
2282 # copying. This makes a noticeable difference when there are very many
2284 # copying. This makes a noticeable difference when there are very many
2283 # directories outside the narrowspec. Also, it makes sense for the copy to
2285 # directories outside the narrowspec. Also, it makes sense for the copy to
2284 # be of the same type as the original, which would not happen with the
2286 # be of the same type as the original, which would not happen with the
2285 # super type's copy().
2287 # super type's copy().
2286 def copy(self):
2288 def copy(self):
2287 return self
2289 return self
2288
2290
2289
2291
2290 class excludeddirmanifestctx(treemanifestctx):
2292 class excludeddirmanifestctx(treemanifestctx):
2291 """context wrapper for excludeddir - see that docstring for rationale"""
2293 """context wrapper for excludeddir - see that docstring for rationale"""
2292
2294
2293 def __init__(self, dir, node):
2295 def __init__(self, dir, node):
2294 self._dir = dir
2296 self._dir = dir
2295 self._node = node
2297 self._node = node
2296
2298
2297 def read(self):
2299 def read(self):
2298 return excludeddir(self._dir, self._node)
2300 return excludeddir(self._dir, self._node)
2299
2301
2300 def readfast(self, shallow=False):
2302 def readfast(self, shallow=False):
2301 # special version of readfast since we don't have underlying storage
2303 # special version of readfast since we don't have underlying storage
2302 return self.read()
2304 return self.read()
2303
2305
2304 def write(self, *args):
2306 def write(self, *args):
2305 raise error.ProgrammingError(
2307 raise error.ProgrammingError(
2306 b'attempt to write manifest from excluded dir %s' % self._dir
2308 b'attempt to write manifest from excluded dir %s' % self._dir
2307 )
2309 )
2308
2310
2309
2311
2310 class excludedmanifestrevlog(manifestrevlog):
2312 class excludedmanifestrevlog(manifestrevlog):
2311 """Stand-in for excluded treemanifest revlogs.
2313 """Stand-in for excluded treemanifest revlogs.
2312
2314
2313 When narrowing is active on a treemanifest repository, we'll have
2315 When narrowing is active on a treemanifest repository, we'll have
2314 references to directories we can't see due to the revlog being
2316 references to directories we can't see due to the revlog being
2315 skipped. This class exists to conform to the manifestrevlog
2317 skipped. This class exists to conform to the manifestrevlog
2316 interface for those directories and proactively prevent writes to
2318 interface for those directories and proactively prevent writes to
2317 outside the narrowspec.
2319 outside the narrowspec.
2318 """
2320 """
2319
2321
2320 def __init__(self, dir):
2322 def __init__(self, dir):
2321 self._dir = dir
2323 self._dir = dir
2322
2324
2323 def __len__(self):
2325 def __len__(self):
2324 raise error.ProgrammingError(
2326 raise error.ProgrammingError(
2325 b'attempt to get length of excluded dir %s' % self._dir
2327 b'attempt to get length of excluded dir %s' % self._dir
2326 )
2328 )
2327
2329
2328 def rev(self, node):
2330 def rev(self, node):
2329 raise error.ProgrammingError(
2331 raise error.ProgrammingError(
2330 b'attempt to get rev from excluded dir %s' % self._dir
2332 b'attempt to get rev from excluded dir %s' % self._dir
2331 )
2333 )
2332
2334
2333 def linkrev(self, node):
2335 def linkrev(self, node):
2334 raise error.ProgrammingError(
2336 raise error.ProgrammingError(
2335 b'attempt to get linkrev from excluded dir %s' % self._dir
2337 b'attempt to get linkrev from excluded dir %s' % self._dir
2336 )
2338 )
2337
2339
2338 def node(self, rev):
2340 def node(self, rev):
2339 raise error.ProgrammingError(
2341 raise error.ProgrammingError(
2340 b'attempt to get node from excluded dir %s' % self._dir
2342 b'attempt to get node from excluded dir %s' % self._dir
2341 )
2343 )
2342
2344
2343 def add(self, *args, **kwargs):
2345 def add(self, *args, **kwargs):
2344 # We should never write entries in dirlogs outside the narrow clone.
2346 # We should never write entries in dirlogs outside the narrow clone.
2345 # However, the method still gets called from writesubtree() in
2347 # However, the method still gets called from writesubtree() in
2346 # _addtree(), so we need to handle it. We should possibly make that
2348 # _addtree(), so we need to handle it. We should possibly make that
2347 # avoid calling add() with a clean manifest (_dirty is always False
2349 # avoid calling add() with a clean manifest (_dirty is always False
2348 # in excludeddir instances).
2350 # in excludeddir instances).
2349 pass
2351 pass
@@ -1,3089 +1,3090 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 io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import zlib
22 import zlib
23
23
24 # import stuff from node for others to import from revlog
24 # import stuff from node for others to import from revlog
25 from .node import (
25 from .node import (
26 bin,
26 bin,
27 hex,
27 hex,
28 nullhex,
28 nullhex,
29 nullid,
29 nullid,
30 nullrev,
30 nullrev,
31 short,
31 short,
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 .pycompat import getattr
38 from .pycompat import getattr
39 from .revlogutils.constants import (
39 from .revlogutils.constants import (
40 FLAG_GENERALDELTA,
40 FLAG_GENERALDELTA,
41 FLAG_INLINE_DATA,
41 FLAG_INLINE_DATA,
42 REVLOGV0,
42 REVLOGV0,
43 REVLOGV1,
43 REVLOGV1,
44 REVLOGV1_FLAGS,
44 REVLOGV1_FLAGS,
45 REVLOGV2,
45 REVLOGV2,
46 REVLOGV2_FLAGS,
46 REVLOGV2_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
48 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_FORMAT,
49 REVLOG_DEFAULT_VERSION,
49 REVLOG_DEFAULT_VERSION,
50 )
50 )
51 from .revlogutils.flagutil import (
51 from .revlogutils.flagutil import (
52 REVIDX_DEFAULT_FLAGS,
52 REVIDX_DEFAULT_FLAGS,
53 REVIDX_ELLIPSIS,
53 REVIDX_ELLIPSIS,
54 REVIDX_EXTSTORED,
54 REVIDX_EXTSTORED,
55 REVIDX_FLAGS_ORDER,
55 REVIDX_FLAGS_ORDER,
56 REVIDX_HASCOPIESINFO,
56 REVIDX_HASCOPIESINFO,
57 REVIDX_ISCENSORED,
57 REVIDX_ISCENSORED,
58 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 REVIDX_RAWTEXT_CHANGING_FLAGS,
59 REVIDX_SIDEDATA,
59 REVIDX_SIDEDATA,
60 )
60 )
61 from .thirdparty import attr
61 from .thirdparty import attr
62 from . import (
62 from . import (
63 ancestor,
63 ancestor,
64 dagop,
64 dagop,
65 error,
65 error,
66 mdiff,
66 mdiff,
67 policy,
67 policy,
68 pycompat,
68 pycompat,
69 templatefilters,
69 templatefilters,
70 util,
70 util,
71 )
71 )
72 from .interfaces import (
72 from .interfaces import (
73 repository,
73 repository,
74 util as interfaceutil,
74 util as interfaceutil,
75 )
75 )
76 from .revlogutils import (
76 from .revlogutils import (
77 deltas as deltautil,
77 deltas as deltautil,
78 flagutil,
78 flagutil,
79 nodemap as nodemaputil,
79 nodemap as nodemaputil,
80 sidedata as sidedatautil,
80 sidedata as sidedatautil,
81 )
81 )
82 from .utils import (
82 from .utils import (
83 storageutil,
83 storageutil,
84 stringutil,
84 stringutil,
85 )
85 )
86
86
87 # blanked usage of all the name to prevent pyflakes constraints
87 # blanked usage of all the name to prevent pyflakes constraints
88 # We need these name available in the module for extensions.
88 # We need these name available in the module for extensions.
89 REVLOGV0
89 REVLOGV0
90 REVLOGV1
90 REVLOGV1
91 REVLOGV2
91 REVLOGV2
92 FLAG_INLINE_DATA
92 FLAG_INLINE_DATA
93 FLAG_GENERALDELTA
93 FLAG_GENERALDELTA
94 REVLOG_DEFAULT_FLAGS
94 REVLOG_DEFAULT_FLAGS
95 REVLOG_DEFAULT_FORMAT
95 REVLOG_DEFAULT_FORMAT
96 REVLOG_DEFAULT_VERSION
96 REVLOG_DEFAULT_VERSION
97 REVLOGV1_FLAGS
97 REVLOGV1_FLAGS
98 REVLOGV2_FLAGS
98 REVLOGV2_FLAGS
99 REVIDX_ISCENSORED
99 REVIDX_ISCENSORED
100 REVIDX_ELLIPSIS
100 REVIDX_ELLIPSIS
101 REVIDX_SIDEDATA
101 REVIDX_SIDEDATA
102 REVIDX_HASCOPIESINFO
102 REVIDX_HASCOPIESINFO
103 REVIDX_EXTSTORED
103 REVIDX_EXTSTORED
104 REVIDX_DEFAULT_FLAGS
104 REVIDX_DEFAULT_FLAGS
105 REVIDX_FLAGS_ORDER
105 REVIDX_FLAGS_ORDER
106 REVIDX_RAWTEXT_CHANGING_FLAGS
106 REVIDX_RAWTEXT_CHANGING_FLAGS
107
107
108 parsers = policy.importmod('parsers')
108 parsers = policy.importmod('parsers')
109 rustancestor = policy.importrust('ancestor')
109 rustancestor = policy.importrust('ancestor')
110 rustdagop = policy.importrust('dagop')
110 rustdagop = policy.importrust('dagop')
111 rustrevlog = policy.importrust('revlog')
111 rustrevlog = policy.importrust('revlog')
112
112
113 # Aliased for performance.
113 # Aliased for performance.
114 _zlibdecompress = zlib.decompress
114 _zlibdecompress = zlib.decompress
115
115
116 # max size of revlog with inline data
116 # max size of revlog with inline data
117 _maxinline = 131072
117 _maxinline = 131072
118 _chunksize = 1048576
118 _chunksize = 1048576
119
119
120 # Flag processors for REVIDX_ELLIPSIS.
120 # Flag processors for REVIDX_ELLIPSIS.
121 def ellipsisreadprocessor(rl, text):
121 def ellipsisreadprocessor(rl, text):
122 return text, False, {}
122 return text, False, {}
123
123
124
124
125 def ellipsiswriteprocessor(rl, text, sidedata):
125 def ellipsiswriteprocessor(rl, text, sidedata):
126 return text, False
126 return text, False
127
127
128
128
129 def ellipsisrawprocessor(rl, text):
129 def ellipsisrawprocessor(rl, text):
130 return False
130 return False
131
131
132
132
133 ellipsisprocessor = (
133 ellipsisprocessor = (
134 ellipsisreadprocessor,
134 ellipsisreadprocessor,
135 ellipsiswriteprocessor,
135 ellipsiswriteprocessor,
136 ellipsisrawprocessor,
136 ellipsisrawprocessor,
137 )
137 )
138
138
139
139
140 def getoffset(q):
140 def getoffset(q):
141 return int(q >> 16)
141 return int(q >> 16)
142
142
143
143
144 def gettype(q):
144 def gettype(q):
145 return int(q & 0xFFFF)
145 return int(q & 0xFFFF)
146
146
147
147
148 def offset_type(offset, type):
148 def offset_type(offset, type):
149 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
149 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
150 raise ValueError(b'unknown revlog index flags')
150 raise ValueError(b'unknown revlog index flags')
151 return int(int(offset) << 16 | type)
151 return int(int(offset) << 16 | type)
152
152
153
153
154 def _verify_revision(rl, skipflags, state, node):
154 def _verify_revision(rl, skipflags, state, node):
155 """Verify the integrity of the given revlog ``node`` while providing a hook
155 """Verify the integrity of the given revlog ``node`` while providing a hook
156 point for extensions to influence the operation."""
156 point for extensions to influence the operation."""
157 if skipflags:
157 if skipflags:
158 state[b'skipread'].add(node)
158 state[b'skipread'].add(node)
159 else:
159 else:
160 # Side-effect: read content and verify hash.
160 # Side-effect: read content and verify hash.
161 rl.revision(node)
161 rl.revision(node)
162
162
163
163
164 # True if a fast implementation for persistent-nodemap is available
164 # True if a fast implementation for persistent-nodemap is available
165 #
165 #
166 # We also consider we have a "fast" implementation in "pure" python because
166 # We also consider we have a "fast" implementation in "pure" python because
167 # people using pure don't really have performance consideration (and a
167 # people using pure don't really have performance consideration (and a
168 # wheelbarrow of other slowness source)
168 # wheelbarrow of other slowness source)
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
170 parsers, 'BaseIndexObject'
170 parsers, 'BaseIndexObject'
171 )
171 )
172
172
173
173
174 @attr.s(slots=True, frozen=True)
174 @attr.s(slots=True, frozen=True)
175 class _revisioninfo(object):
175 class _revisioninfo(object):
176 """Information about a revision that allows building its fulltext
176 """Information about a revision that allows building its fulltext
177 node: expected hash of the revision
177 node: expected hash of the revision
178 p1, p2: parent revs of the revision
178 p1, p2: parent revs of the revision
179 btext: built text cache consisting of a one-element list
179 btext: built text cache consisting of a one-element list
180 cachedelta: (baserev, uncompressed_delta) or None
180 cachedelta: (baserev, uncompressed_delta) or None
181 flags: flags associated to the revision storage
181 flags: flags associated to the revision storage
182
182
183 One of btext[0] or cachedelta must be set.
183 One of btext[0] or cachedelta must be set.
184 """
184 """
185
185
186 node = attr.ib()
186 node = attr.ib()
187 p1 = attr.ib()
187 p1 = attr.ib()
188 p2 = attr.ib()
188 p2 = attr.ib()
189 btext = attr.ib()
189 btext = attr.ib()
190 textlen = attr.ib()
190 textlen = attr.ib()
191 cachedelta = attr.ib()
191 cachedelta = attr.ib()
192 flags = attr.ib()
192 flags = attr.ib()
193
193
194
194
195 @interfaceutil.implementer(repository.irevisiondelta)
195 @interfaceutil.implementer(repository.irevisiondelta)
196 @attr.s(slots=True)
196 @attr.s(slots=True)
197 class revlogrevisiondelta(object):
197 class revlogrevisiondelta(object):
198 node = attr.ib()
198 node = attr.ib()
199 p1node = attr.ib()
199 p1node = attr.ib()
200 p2node = attr.ib()
200 p2node = attr.ib()
201 basenode = attr.ib()
201 basenode = attr.ib()
202 flags = attr.ib()
202 flags = attr.ib()
203 baserevisionsize = attr.ib()
203 baserevisionsize = attr.ib()
204 revision = attr.ib()
204 revision = attr.ib()
205 delta = attr.ib()
205 delta = attr.ib()
206 linknode = attr.ib(default=None)
206 linknode = attr.ib(default=None)
207
207
208
208
209 @interfaceutil.implementer(repository.iverifyproblem)
209 @interfaceutil.implementer(repository.iverifyproblem)
210 @attr.s(frozen=True)
210 @attr.s(frozen=True)
211 class revlogproblem(object):
211 class revlogproblem(object):
212 warning = attr.ib(default=None)
212 warning = attr.ib(default=None)
213 error = attr.ib(default=None)
213 error = attr.ib(default=None)
214 node = attr.ib(default=None)
214 node = attr.ib(default=None)
215
215
216
216
217 # index v0:
217 # index v0:
218 # 4 bytes: offset
218 # 4 bytes: offset
219 # 4 bytes: compressed length
219 # 4 bytes: compressed length
220 # 4 bytes: base rev
220 # 4 bytes: base rev
221 # 4 bytes: link rev
221 # 4 bytes: link rev
222 # 20 bytes: parent 1 nodeid
222 # 20 bytes: parent 1 nodeid
223 # 20 bytes: parent 2 nodeid
223 # 20 bytes: parent 2 nodeid
224 # 20 bytes: nodeid
224 # 20 bytes: nodeid
225 indexformatv0 = struct.Struct(b">4l20s20s20s")
225 indexformatv0 = struct.Struct(b">4l20s20s20s")
226 indexformatv0_pack = indexformatv0.pack
226 indexformatv0_pack = indexformatv0.pack
227 indexformatv0_unpack = indexformatv0.unpack
227 indexformatv0_unpack = indexformatv0.unpack
228
228
229
229
230 class revlogoldindex(list):
230 class revlogoldindex(list):
231 @property
231 @property
232 def nodemap(self):
232 def nodemap(self):
233 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
233 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
234 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
234 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
235 return self._nodemap
235 return self._nodemap
236
236
237 @util.propertycache
237 @util.propertycache
238 def _nodemap(self):
238 def _nodemap(self):
239 nodemap = nodemaputil.NodeMap({nullid: nullrev})
239 nodemap = nodemaputil.NodeMap({nullid: nullrev})
240 for r in range(0, len(self)):
240 for r in range(0, len(self)):
241 n = self[r][7]
241 n = self[r][7]
242 nodemap[n] = r
242 nodemap[n] = r
243 return nodemap
243 return nodemap
244
244
245 def has_node(self, node):
245 def has_node(self, node):
246 """return True if the node exist in the index"""
246 """return True if the node exist in the index"""
247 return node in self._nodemap
247 return node in self._nodemap
248
248
249 def rev(self, node):
249 def rev(self, node):
250 """return a revision for a node
250 """return a revision for a node
251
251
252 If the node is unknown, raise a RevlogError"""
252 If the node is unknown, raise a RevlogError"""
253 return self._nodemap[node]
253 return self._nodemap[node]
254
254
255 def get_rev(self, node):
255 def get_rev(self, node):
256 """return a revision for a node
256 """return a revision for a node
257
257
258 If the node is unknown, return None"""
258 If the node is unknown, return None"""
259 return self._nodemap.get(node)
259 return self._nodemap.get(node)
260
260
261 def append(self, tup):
261 def append(self, tup):
262 self._nodemap[tup[7]] = len(self)
262 self._nodemap[tup[7]] = len(self)
263 super(revlogoldindex, self).append(tup)
263 super(revlogoldindex, self).append(tup)
264
264
265 def __delitem__(self, i):
265 def __delitem__(self, i):
266 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
266 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
267 raise ValueError(b"deleting slices only supports a:-1 with step 1")
267 raise ValueError(b"deleting slices only supports a:-1 with step 1")
268 for r in pycompat.xrange(i.start, len(self)):
268 for r in pycompat.xrange(i.start, len(self)):
269 del self._nodemap[self[r][7]]
269 del self._nodemap[self[r][7]]
270 super(revlogoldindex, self).__delitem__(i)
270 super(revlogoldindex, self).__delitem__(i)
271
271
272 def clearcaches(self):
272 def clearcaches(self):
273 self.__dict__.pop('_nodemap', None)
273 self.__dict__.pop('_nodemap', None)
274
274
275 def __getitem__(self, i):
275 def __getitem__(self, i):
276 if i == -1:
276 if i == -1:
277 return (0, 0, 0, -1, -1, -1, -1, nullid)
277 return (0, 0, 0, -1, -1, -1, -1, nullid)
278 return list.__getitem__(self, i)
278 return list.__getitem__(self, i)
279
279
280
280
281 class revlogoldio(object):
281 class revlogoldio(object):
282 def __init__(self):
282 def __init__(self):
283 self.size = indexformatv0.size
283 self.size = indexformatv0.size
284
284
285 def parseindex(self, data, inline):
285 def parseindex(self, data, inline):
286 s = self.size
286 s = self.size
287 index = []
287 index = []
288 nodemap = nodemaputil.NodeMap({nullid: nullrev})
288 nodemap = nodemaputil.NodeMap({nullid: nullrev})
289 n = off = 0
289 n = off = 0
290 l = len(data)
290 l = len(data)
291 while off + s <= l:
291 while off + s <= l:
292 cur = data[off : off + s]
292 cur = data[off : off + s]
293 off += s
293 off += s
294 e = indexformatv0_unpack(cur)
294 e = indexformatv0_unpack(cur)
295 # transform to revlogv1 format
295 # transform to revlogv1 format
296 e2 = (
296 e2 = (
297 offset_type(e[0], 0),
297 offset_type(e[0], 0),
298 e[1],
298 e[1],
299 -1,
299 -1,
300 e[2],
300 e[2],
301 e[3],
301 e[3],
302 nodemap.get(e[4], nullrev),
302 nodemap.get(e[4], nullrev),
303 nodemap.get(e[5], nullrev),
303 nodemap.get(e[5], nullrev),
304 e[6],
304 e[6],
305 )
305 )
306 index.append(e2)
306 index.append(e2)
307 nodemap[e[6]] = n
307 nodemap[e[6]] = n
308 n += 1
308 n += 1
309
309
310 index = revlogoldindex(index)
310 index = revlogoldindex(index)
311 return index, None
311 return index, None
312
312
313 def packentry(self, entry, node, version, rev):
313 def packentry(self, entry, node, version, rev):
314 if gettype(entry[0]):
314 if gettype(entry[0]):
315 raise error.RevlogError(
315 raise error.RevlogError(
316 _(b'index entry flags need revlog version 1')
316 _(b'index entry flags need revlog version 1')
317 )
317 )
318 e2 = (
318 e2 = (
319 getoffset(entry[0]),
319 getoffset(entry[0]),
320 entry[1],
320 entry[1],
321 entry[3],
321 entry[3],
322 entry[4],
322 entry[4],
323 node(entry[5]),
323 node(entry[5]),
324 node(entry[6]),
324 node(entry[6]),
325 entry[7],
325 entry[7],
326 )
326 )
327 return indexformatv0_pack(*e2)
327 return indexformatv0_pack(*e2)
328
328
329
329
330 # index ng:
330 # index ng:
331 # 6 bytes: offset
331 # 6 bytes: offset
332 # 2 bytes: flags
332 # 2 bytes: flags
333 # 4 bytes: compressed length
333 # 4 bytes: compressed length
334 # 4 bytes: uncompressed length
334 # 4 bytes: uncompressed length
335 # 4 bytes: base rev
335 # 4 bytes: base rev
336 # 4 bytes: link rev
336 # 4 bytes: link rev
337 # 4 bytes: parent 1 rev
337 # 4 bytes: parent 1 rev
338 # 4 bytes: parent 2 rev
338 # 4 bytes: parent 2 rev
339 # 32 bytes: nodeid
339 # 32 bytes: nodeid
340 indexformatng = struct.Struct(b">Qiiiiii20s12x")
340 indexformatng = struct.Struct(b">Qiiiiii20s12x")
341 indexformatng_pack = indexformatng.pack
341 indexformatng_pack = indexformatng.pack
342 versionformat = struct.Struct(b">I")
342 versionformat = struct.Struct(b">I")
343 versionformat_pack = versionformat.pack
343 versionformat_pack = versionformat.pack
344 versionformat_unpack = versionformat.unpack
344 versionformat_unpack = versionformat.unpack
345
345
346 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
346 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
347 # signed integer)
347 # signed integer)
348 _maxentrysize = 0x7FFFFFFF
348 _maxentrysize = 0x7FFFFFFF
349
349
350
350
351 class revlogio(object):
351 class revlogio(object):
352 def __init__(self):
352 def __init__(self):
353 self.size = indexformatng.size
353 self.size = indexformatng.size
354
354
355 def parseindex(self, data, inline):
355 def parseindex(self, data, inline):
356 # call the C implementation to parse the index data
356 # call the C implementation to parse the index data
357 index, cache = parsers.parse_index2(data, inline)
357 index, cache = parsers.parse_index2(data, inline)
358 return index, cache
358 return index, cache
359
359
360 def packentry(self, entry, node, version, rev):
360 def packentry(self, entry, node, version, rev):
361 p = indexformatng_pack(*entry)
361 p = indexformatng_pack(*entry)
362 if rev == 0:
362 if rev == 0:
363 p = versionformat_pack(version) + p[4:]
363 p = versionformat_pack(version) + p[4:]
364 return p
364 return p
365
365
366
366
367 NodemapRevlogIO = None
367 NodemapRevlogIO = None
368
368
369 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
369 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
370
370
371 class NodemapRevlogIO(revlogio):
371 class NodemapRevlogIO(revlogio):
372 """A debug oriented IO class that return a PersistentNodeMapIndexObject
372 """A debug oriented IO class that return a PersistentNodeMapIndexObject
373
373
374 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
374 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
375 """
375 """
376
376
377 def parseindex(self, data, inline):
377 def parseindex(self, data, inline):
378 index, cache = parsers.parse_index_devel_nodemap(data, inline)
378 index, cache = parsers.parse_index_devel_nodemap(data, inline)
379 return index, cache
379 return index, cache
380
380
381
381
382 class rustrevlogio(revlogio):
382 class rustrevlogio(revlogio):
383 def parseindex(self, data, inline):
383 def parseindex(self, data, inline):
384 index, cache = super(rustrevlogio, self).parseindex(data, inline)
384 index, cache = super(rustrevlogio, self).parseindex(data, inline)
385 return rustrevlog.MixedIndex(index), cache
385 return rustrevlog.MixedIndex(index), cache
386
386
387
387
388 class revlog(object):
388 class revlog(object):
389 """
389 """
390 the underlying revision storage object
390 the underlying revision storage object
391
391
392 A revlog consists of two parts, an index and the revision data.
392 A revlog consists of two parts, an index and the revision data.
393
393
394 The index is a file with a fixed record size containing
394 The index is a file with a fixed record size containing
395 information on each revision, including its nodeid (hash), the
395 information on each revision, including its nodeid (hash), the
396 nodeids of its parents, the position and offset of its data within
396 nodeids of its parents, the position and offset of its data within
397 the data file, and the revision it's based on. Finally, each entry
397 the data file, and the revision it's based on. Finally, each entry
398 contains a linkrev entry that can serve as a pointer to external
398 contains a linkrev entry that can serve as a pointer to external
399 data.
399 data.
400
400
401 The revision data itself is a linear collection of data chunks.
401 The revision data itself is a linear collection of data chunks.
402 Each chunk represents a revision and is usually represented as a
402 Each chunk represents a revision and is usually represented as a
403 delta against the previous chunk. To bound lookup time, runs of
403 delta against the previous chunk. To bound lookup time, runs of
404 deltas are limited to about 2 times the length of the original
404 deltas are limited to about 2 times the length of the original
405 version data. This makes retrieval of a version proportional to
405 version data. This makes retrieval of a version proportional to
406 its size, or O(1) relative to the number of revisions.
406 its size, or O(1) relative to the number of revisions.
407
407
408 Both pieces of the revlog are written to in an append-only
408 Both pieces of the revlog are written to in an append-only
409 fashion, which means we never need to rewrite a file to insert or
409 fashion, which means we never need to rewrite a file to insert or
410 remove data, and can use some simple techniques to avoid the need
410 remove data, and can use some simple techniques to avoid the need
411 for locking while reading.
411 for locking while reading.
412
412
413 If checkambig, indexfile is opened with checkambig=True at
413 If checkambig, indexfile is opened with checkambig=True at
414 writing, to avoid file stat ambiguity.
414 writing, to avoid file stat ambiguity.
415
415
416 If mmaplargeindex is True, and an mmapindexthreshold is set, the
416 If mmaplargeindex is True, and an mmapindexthreshold is set, the
417 index will be mmapped rather than read if it is larger than the
417 index will be mmapped rather than read if it is larger than the
418 configured threshold.
418 configured threshold.
419
419
420 If censorable is True, the revlog can have censored revisions.
420 If censorable is True, the revlog can have censored revisions.
421
421
422 If `upperboundcomp` is not None, this is the expected maximal gain from
422 If `upperboundcomp` is not None, this is the expected maximal gain from
423 compression for the data content.
423 compression for the data content.
424 """
424 """
425
425
426 _flagserrorclass = error.RevlogError
426 _flagserrorclass = error.RevlogError
427
427
428 def __init__(
428 def __init__(
429 self,
429 self,
430 opener,
430 opener,
431 indexfile,
431 indexfile,
432 datafile=None,
432 datafile=None,
433 checkambig=False,
433 checkambig=False,
434 mmaplargeindex=False,
434 mmaplargeindex=False,
435 censorable=False,
435 censorable=False,
436 upperboundcomp=None,
436 upperboundcomp=None,
437 persistentnodemap=False,
437 persistentnodemap=False,
438 ):
438 ):
439 """
439 """
440 create a revlog object
440 create a revlog object
441
441
442 opener is a function that abstracts the file opening operation
442 opener is a function that abstracts the file opening operation
443 and can be used to implement COW semantics or the like.
443 and can be used to implement COW semantics or the like.
444
444
445 """
445 """
446 self.upperboundcomp = upperboundcomp
446 self.upperboundcomp = upperboundcomp
447 self.indexfile = indexfile
447 self.indexfile = indexfile
448 self.datafile = datafile or (indexfile[:-2] + b".d")
448 self.datafile = datafile or (indexfile[:-2] + b".d")
449 self.nodemap_file = None
449 self.nodemap_file = None
450 if persistentnodemap:
450 if persistentnodemap:
451 if indexfile.endswith(b'.a'):
451 if indexfile.endswith(b'.a'):
452 pending_path = indexfile[:-4] + b".n.a"
452 pending_path = indexfile[:-4] + b".n.a"
453 if opener.exists(pending_path):
453 if opener.exists(pending_path):
454 self.nodemap_file = pending_path
454 self.nodemap_file = pending_path
455 else:
455 else:
456 self.nodemap_file = indexfile[:-4] + b".n"
456 self.nodemap_file = indexfile[:-4] + b".n"
457 else:
457 else:
458 self.nodemap_file = indexfile[:-2] + b".n"
458 self.nodemap_file = indexfile[:-2] + b".n"
459
459
460 self.opener = opener
460 self.opener = opener
461 # When True, indexfile is opened with checkambig=True at writing, to
461 # When True, indexfile is opened with checkambig=True at writing, to
462 # avoid file stat ambiguity.
462 # avoid file stat ambiguity.
463 self._checkambig = checkambig
463 self._checkambig = checkambig
464 self._mmaplargeindex = mmaplargeindex
464 self._mmaplargeindex = mmaplargeindex
465 self._censorable = censorable
465 self._censorable = censorable
466 # 3-tuple of (node, rev, text) for a raw revision.
466 # 3-tuple of (node, rev, text) for a raw revision.
467 self._revisioncache = None
467 self._revisioncache = None
468 # Maps rev to chain base rev.
468 # Maps rev to chain base rev.
469 self._chainbasecache = util.lrucachedict(100)
469 self._chainbasecache = util.lrucachedict(100)
470 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
470 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
471 self._chunkcache = (0, b'')
471 self._chunkcache = (0, b'')
472 # How much data to read and cache into the raw revlog data cache.
472 # How much data to read and cache into the raw revlog data cache.
473 self._chunkcachesize = 65536
473 self._chunkcachesize = 65536
474 self._maxchainlen = None
474 self._maxchainlen = None
475 self._deltabothparents = True
475 self._deltabothparents = True
476 self.index = None
476 self.index = None
477 self._nodemap_docket = None
477 self._nodemap_docket = None
478 # Mapping of partial identifiers to full nodes.
478 # Mapping of partial identifiers to full nodes.
479 self._pcache = {}
479 self._pcache = {}
480 # Mapping of revision integer to full node.
480 # Mapping of revision integer to full node.
481 self._compengine = b'zlib'
481 self._compengine = b'zlib'
482 self._compengineopts = {}
482 self._compengineopts = {}
483 self._maxdeltachainspan = -1
483 self._maxdeltachainspan = -1
484 self._withsparseread = False
484 self._withsparseread = False
485 self._sparserevlog = False
485 self._sparserevlog = False
486 self._srdensitythreshold = 0.50
486 self._srdensitythreshold = 0.50
487 self._srmingapsize = 262144
487 self._srmingapsize = 262144
488
488
489 # Make copy of flag processors so each revlog instance can support
489 # Make copy of flag processors so each revlog instance can support
490 # custom flags.
490 # custom flags.
491 self._flagprocessors = dict(flagutil.flagprocessors)
491 self._flagprocessors = dict(flagutil.flagprocessors)
492
492
493 # 2-tuple of file handles being used for active writing.
493 # 2-tuple of file handles being used for active writing.
494 self._writinghandles = None
494 self._writinghandles = None
495
495
496 self._loadindex()
496 self._loadindex()
497
497
498 def _loadindex(self):
498 def _loadindex(self):
499 mmapindexthreshold = None
499 mmapindexthreshold = None
500 opts = self.opener.options
500 opts = self.opener.options
501
501
502 if b'revlogv2' in opts:
502 if b'revlogv2' in opts:
503 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
503 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
504 elif b'revlogv1' in opts:
504 elif b'revlogv1' in opts:
505 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
505 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
506 if b'generaldelta' in opts:
506 if b'generaldelta' in opts:
507 newversionflags |= FLAG_GENERALDELTA
507 newversionflags |= FLAG_GENERALDELTA
508 elif b'revlogv0' in self.opener.options:
508 elif b'revlogv0' in self.opener.options:
509 newversionflags = REVLOGV0
509 newversionflags = REVLOGV0
510 else:
510 else:
511 newversionflags = REVLOG_DEFAULT_VERSION
511 newversionflags = REVLOG_DEFAULT_VERSION
512
512
513 if b'chunkcachesize' in opts:
513 if b'chunkcachesize' in opts:
514 self._chunkcachesize = opts[b'chunkcachesize']
514 self._chunkcachesize = opts[b'chunkcachesize']
515 if b'maxchainlen' in opts:
515 if b'maxchainlen' in opts:
516 self._maxchainlen = opts[b'maxchainlen']
516 self._maxchainlen = opts[b'maxchainlen']
517 if b'deltabothparents' in opts:
517 if b'deltabothparents' in opts:
518 self._deltabothparents = opts[b'deltabothparents']
518 self._deltabothparents = opts[b'deltabothparents']
519 self._lazydelta = bool(opts.get(b'lazydelta', True))
519 self._lazydelta = bool(opts.get(b'lazydelta', True))
520 self._lazydeltabase = False
520 self._lazydeltabase = False
521 if self._lazydelta:
521 if self._lazydelta:
522 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
522 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
523 if b'compengine' in opts:
523 if b'compengine' in opts:
524 self._compengine = opts[b'compengine']
524 self._compengine = opts[b'compengine']
525 if b'zlib.level' in opts:
525 if b'zlib.level' in opts:
526 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
526 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
527 if b'zstd.level' in opts:
527 if b'zstd.level' in opts:
528 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
528 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
529 if b'maxdeltachainspan' in opts:
529 if b'maxdeltachainspan' in opts:
530 self._maxdeltachainspan = opts[b'maxdeltachainspan']
530 self._maxdeltachainspan = opts[b'maxdeltachainspan']
531 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
531 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
532 mmapindexthreshold = opts[b'mmapindexthreshold']
532 mmapindexthreshold = opts[b'mmapindexthreshold']
533 self.hassidedata = bool(opts.get(b'side-data', False))
533 self.hassidedata = bool(opts.get(b'side-data', False))
534 if self.hassidedata:
534 if self.hassidedata:
535 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
535 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
536 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
536 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
537 withsparseread = bool(opts.get(b'with-sparse-read', False))
537 withsparseread = bool(opts.get(b'with-sparse-read', False))
538 # sparse-revlog forces sparse-read
538 # sparse-revlog forces sparse-read
539 self._withsparseread = self._sparserevlog or withsparseread
539 self._withsparseread = self._sparserevlog or withsparseread
540 if b'sparse-read-density-threshold' in opts:
540 if b'sparse-read-density-threshold' in opts:
541 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
541 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
542 if b'sparse-read-min-gap-size' in opts:
542 if b'sparse-read-min-gap-size' in opts:
543 self._srmingapsize = opts[b'sparse-read-min-gap-size']
543 self._srmingapsize = opts[b'sparse-read-min-gap-size']
544 if opts.get(b'enableellipsis'):
544 if opts.get(b'enableellipsis'):
545 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
545 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
546
546
547 # revlog v0 doesn't have flag processors
547 # revlog v0 doesn't have flag processors
548 for flag, processor in pycompat.iteritems(
548 for flag, processor in pycompat.iteritems(
549 opts.get(b'flagprocessors', {})
549 opts.get(b'flagprocessors', {})
550 ):
550 ):
551 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
551 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
552
552
553 if self._chunkcachesize <= 0:
553 if self._chunkcachesize <= 0:
554 raise error.RevlogError(
554 raise error.RevlogError(
555 _(b'revlog chunk cache size %r is not greater than 0')
555 _(b'revlog chunk cache size %r is not greater than 0')
556 % self._chunkcachesize
556 % self._chunkcachesize
557 )
557 )
558 elif self._chunkcachesize & (self._chunkcachesize - 1):
558 elif self._chunkcachesize & (self._chunkcachesize - 1):
559 raise error.RevlogError(
559 raise error.RevlogError(
560 _(b'revlog chunk cache size %r is not a power of 2')
560 _(b'revlog chunk cache size %r is not a power of 2')
561 % self._chunkcachesize
561 % self._chunkcachesize
562 )
562 )
563
563
564 indexdata = b''
564 indexdata = b''
565 self._initempty = True
565 self._initempty = True
566 try:
566 try:
567 with self._indexfp() as f:
567 with self._indexfp() as f:
568 if (
568 if (
569 mmapindexthreshold is not None
569 mmapindexthreshold is not None
570 and self.opener.fstat(f).st_size >= mmapindexthreshold
570 and self.opener.fstat(f).st_size >= mmapindexthreshold
571 ):
571 ):
572 # TODO: should .close() to release resources without
572 # TODO: should .close() to release resources without
573 # relying on Python GC
573 # relying on Python GC
574 indexdata = util.buffer(util.mmapread(f))
574 indexdata = util.buffer(util.mmapread(f))
575 else:
575 else:
576 indexdata = f.read()
576 indexdata = f.read()
577 if len(indexdata) > 0:
577 if len(indexdata) > 0:
578 versionflags = versionformat_unpack(indexdata[:4])[0]
578 versionflags = versionformat_unpack(indexdata[:4])[0]
579 self._initempty = False
579 self._initempty = False
580 else:
580 else:
581 versionflags = newversionflags
581 versionflags = newversionflags
582 except IOError as inst:
582 except IOError as inst:
583 if inst.errno != errno.ENOENT:
583 if inst.errno != errno.ENOENT:
584 raise
584 raise
585
585
586 versionflags = newversionflags
586 versionflags = newversionflags
587
587
588 self.version = versionflags
588 self.version = versionflags
589
589
590 flags = versionflags & ~0xFFFF
590 flags = versionflags & ~0xFFFF
591 fmt = versionflags & 0xFFFF
591 fmt = versionflags & 0xFFFF
592
592
593 if fmt == REVLOGV0:
593 if fmt == REVLOGV0:
594 if flags:
594 if flags:
595 raise error.RevlogError(
595 raise error.RevlogError(
596 _(b'unknown flags (%#04x) in version %d revlog %s')
596 _(b'unknown flags (%#04x) in version %d revlog %s')
597 % (flags >> 16, fmt, self.indexfile)
597 % (flags >> 16, fmt, self.indexfile)
598 )
598 )
599
599
600 self._inline = False
600 self._inline = False
601 self._generaldelta = False
601 self._generaldelta = False
602
602
603 elif fmt == REVLOGV1:
603 elif fmt == REVLOGV1:
604 if flags & ~REVLOGV1_FLAGS:
604 if flags & ~REVLOGV1_FLAGS:
605 raise error.RevlogError(
605 raise error.RevlogError(
606 _(b'unknown flags (%#04x) in version %d revlog %s')
606 _(b'unknown flags (%#04x) in version %d revlog %s')
607 % (flags >> 16, fmt, self.indexfile)
607 % (flags >> 16, fmt, self.indexfile)
608 )
608 )
609
609
610 self._inline = versionflags & FLAG_INLINE_DATA
610 self._inline = versionflags & FLAG_INLINE_DATA
611 self._generaldelta = versionflags & FLAG_GENERALDELTA
611 self._generaldelta = versionflags & FLAG_GENERALDELTA
612
612
613 elif fmt == REVLOGV2:
613 elif fmt == REVLOGV2:
614 if flags & ~REVLOGV2_FLAGS:
614 if flags & ~REVLOGV2_FLAGS:
615 raise error.RevlogError(
615 raise error.RevlogError(
616 _(b'unknown flags (%#04x) in version %d revlog %s')
616 _(b'unknown flags (%#04x) in version %d revlog %s')
617 % (flags >> 16, fmt, self.indexfile)
617 % (flags >> 16, fmt, self.indexfile)
618 )
618 )
619
619
620 self._inline = versionflags & FLAG_INLINE_DATA
620 self._inline = versionflags & FLAG_INLINE_DATA
621 # generaldelta implied by version 2 revlogs.
621 # generaldelta implied by version 2 revlogs.
622 self._generaldelta = True
622 self._generaldelta = True
623
623
624 else:
624 else:
625 raise error.RevlogError(
625 raise error.RevlogError(
626 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
626 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
627 )
627 )
628 # sparse-revlog can't be on without general-delta (issue6056)
628 # sparse-revlog can't be on without general-delta (issue6056)
629 if not self._generaldelta:
629 if not self._generaldelta:
630 self._sparserevlog = False
630 self._sparserevlog = False
631
631
632 self._storedeltachains = True
632 self._storedeltachains = True
633
633
634 devel_nodemap = (
634 devel_nodemap = (
635 self.nodemap_file
635 self.nodemap_file
636 and opts.get(b'devel-force-nodemap', False)
636 and opts.get(b'devel-force-nodemap', False)
637 and NodemapRevlogIO is not None
637 and NodemapRevlogIO is not None
638 )
638 )
639
639
640 use_rust_index = False
640 use_rust_index = False
641 if rustrevlog is not None:
641 if rustrevlog is not None:
642 if self.nodemap_file is not None:
642 if self.nodemap_file is not None:
643 use_rust_index = True
643 use_rust_index = True
644 else:
644 else:
645 use_rust_index = self.opener.options.get(b'rust.index')
645 use_rust_index = self.opener.options.get(b'rust.index')
646
646
647 self._io = revlogio()
647 self._io = revlogio()
648 if self.version == REVLOGV0:
648 if self.version == REVLOGV0:
649 self._io = revlogoldio()
649 self._io = revlogoldio()
650 elif devel_nodemap:
650 elif devel_nodemap:
651 self._io = NodemapRevlogIO()
651 self._io = NodemapRevlogIO()
652 elif use_rust_index:
652 elif use_rust_index:
653 self._io = rustrevlogio()
653 self._io = rustrevlogio()
654 try:
654 try:
655 d = self._io.parseindex(indexdata, self._inline)
655 d = self._io.parseindex(indexdata, self._inline)
656 index, _chunkcache = d
656 index, _chunkcache = d
657 use_nodemap = (
657 use_nodemap = (
658 not self._inline
658 not self._inline
659 and self.nodemap_file is not None
659 and self.nodemap_file is not None
660 and util.safehasattr(index, 'update_nodemap_data')
660 and util.safehasattr(index, 'update_nodemap_data')
661 )
661 )
662 if use_nodemap:
662 if use_nodemap:
663 nodemap_data = nodemaputil.persisted_data(self)
663 nodemap_data = nodemaputil.persisted_data(self)
664 if nodemap_data is not None:
664 if nodemap_data is not None:
665 docket = nodemap_data[0]
665 docket = nodemap_data[0]
666 if (
666 if (
667 len(d[0]) > docket.tip_rev
667 len(d[0]) > docket.tip_rev
668 and d[0][docket.tip_rev][7] == docket.tip_node
668 and d[0][docket.tip_rev][7] == docket.tip_node
669 ):
669 ):
670 # no changelog tampering
670 # no changelog tampering
671 self._nodemap_docket = docket
671 self._nodemap_docket = docket
672 index.update_nodemap_data(*nodemap_data)
672 index.update_nodemap_data(*nodemap_data)
673 except (ValueError, IndexError):
673 except (ValueError, IndexError):
674 raise error.RevlogError(
674 raise error.RevlogError(
675 _(b"index %s is corrupted") % self.indexfile
675 _(b"index %s is corrupted") % self.indexfile
676 )
676 )
677 self.index, self._chunkcache = d
677 self.index, self._chunkcache = d
678 if not self._chunkcache:
678 if not self._chunkcache:
679 self._chunkclear()
679 self._chunkclear()
680 # revnum -> (chain-length, sum-delta-length)
680 # revnum -> (chain-length, sum-delta-length)
681 self._chaininfocache = util.lrucachedict(500)
681 self._chaininfocache = util.lrucachedict(500)
682 # revlog header -> revlog compressor
682 # revlog header -> revlog compressor
683 self._decompressors = {}
683 self._decompressors = {}
684
684
685 @util.propertycache
685 @util.propertycache
686 def _compressor(self):
686 def _compressor(self):
687 engine = util.compengines[self._compengine]
687 engine = util.compengines[self._compengine]
688 return engine.revlogcompressor(self._compengineopts)
688 return engine.revlogcompressor(self._compengineopts)
689
689
690 def _indexfp(self, mode=b'r'):
690 def _indexfp(self, mode=b'r'):
691 """file object for the revlog's index file"""
691 """file object for the revlog's index file"""
692 args = {'mode': mode}
692 args = {'mode': mode}
693 if mode != b'r':
693 if mode != b'r':
694 args['checkambig'] = self._checkambig
694 args['checkambig'] = self._checkambig
695 if mode == b'w':
695 if mode == b'w':
696 args['atomictemp'] = True
696 args['atomictemp'] = True
697 return self.opener(self.indexfile, **args)
697 return self.opener(self.indexfile, **args)
698
698
699 def _datafp(self, mode=b'r'):
699 def _datafp(self, mode=b'r'):
700 """file object for the revlog's data file"""
700 """file object for the revlog's data file"""
701 return self.opener(self.datafile, mode=mode)
701 return self.opener(self.datafile, mode=mode)
702
702
703 @contextlib.contextmanager
703 @contextlib.contextmanager
704 def _datareadfp(self, existingfp=None):
704 def _datareadfp(self, existingfp=None):
705 """file object suitable to read data"""
705 """file object suitable to read data"""
706 # Use explicit file handle, if given.
706 # Use explicit file handle, if given.
707 if existingfp is not None:
707 if existingfp is not None:
708 yield existingfp
708 yield existingfp
709
709
710 # Use a file handle being actively used for writes, if available.
710 # Use a file handle being actively used for writes, if available.
711 # There is some danger to doing this because reads will seek the
711 # There is some danger to doing this because reads will seek the
712 # file. However, _writeentry() performs a SEEK_END before all writes,
712 # file. However, _writeentry() performs a SEEK_END before all writes,
713 # so we should be safe.
713 # so we should be safe.
714 elif self._writinghandles:
714 elif self._writinghandles:
715 if self._inline:
715 if self._inline:
716 yield self._writinghandles[0]
716 yield self._writinghandles[0]
717 else:
717 else:
718 yield self._writinghandles[1]
718 yield self._writinghandles[1]
719
719
720 # Otherwise open a new file handle.
720 # Otherwise open a new file handle.
721 else:
721 else:
722 if self._inline:
722 if self._inline:
723 func = self._indexfp
723 func = self._indexfp
724 else:
724 else:
725 func = self._datafp
725 func = self._datafp
726 with func() as fp:
726 with func() as fp:
727 yield fp
727 yield fp
728
728
729 def tiprev(self):
729 def tiprev(self):
730 return len(self.index) - 1
730 return len(self.index) - 1
731
731
732 def tip(self):
732 def tip(self):
733 return self.node(self.tiprev())
733 return self.node(self.tiprev())
734
734
735 def __contains__(self, rev):
735 def __contains__(self, rev):
736 return 0 <= rev < len(self)
736 return 0 <= rev < len(self)
737
737
738 def __len__(self):
738 def __len__(self):
739 return len(self.index)
739 return len(self.index)
740
740
741 def __iter__(self):
741 def __iter__(self):
742 return iter(pycompat.xrange(len(self)))
742 return iter(pycompat.xrange(len(self)))
743
743
744 def revs(self, start=0, stop=None):
744 def revs(self, start=0, stop=None):
745 """iterate over all rev in this revlog (from start to stop)"""
745 """iterate over all rev in this revlog (from start to stop)"""
746 return storageutil.iterrevs(len(self), start=start, stop=stop)
746 return storageutil.iterrevs(len(self), start=start, stop=stop)
747
747
748 @property
748 @property
749 def nodemap(self):
749 def nodemap(self):
750 msg = (
750 msg = (
751 b"revlog.nodemap is deprecated, "
751 b"revlog.nodemap is deprecated, "
752 b"use revlog.index.[has_node|rev|get_rev]"
752 b"use revlog.index.[has_node|rev|get_rev]"
753 )
753 )
754 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
754 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
755 return self.index.nodemap
755 return self.index.nodemap
756
756
757 @property
757 @property
758 def _nodecache(self):
758 def _nodecache(self):
759 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
759 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
760 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
760 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
761 return self.index.nodemap
761 return self.index.nodemap
762
762
763 def hasnode(self, node):
763 def hasnode(self, node):
764 try:
764 try:
765 self.rev(node)
765 self.rev(node)
766 return True
766 return True
767 except KeyError:
767 except KeyError:
768 return False
768 return False
769
769
770 def candelta(self, baserev, rev):
770 def candelta(self, baserev, rev):
771 """whether two revisions (baserev, rev) can be delta-ed or not"""
771 """whether two revisions (baserev, rev) can be delta-ed or not"""
772 # Disable delta if either rev requires a content-changing flag
772 # Disable delta if either rev requires a content-changing flag
773 # processor (ex. LFS). This is because such flag processor can alter
773 # processor (ex. LFS). This is because such flag processor can alter
774 # the rawtext content that the delta will be based on, and two clients
774 # the rawtext content that the delta will be based on, and two clients
775 # could have a same revlog node with different flags (i.e. different
775 # could have a same revlog node with different flags (i.e. different
776 # rawtext contents) and the delta could be incompatible.
776 # rawtext contents) and the delta could be incompatible.
777 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
777 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
778 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
778 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
779 ):
779 ):
780 return False
780 return False
781 return True
781 return True
782
782
783 def update_caches(self, transaction):
783 def update_caches(self, transaction):
784 if self.nodemap_file is not None:
784 if self.nodemap_file is not None:
785 if transaction is None:
785 if transaction is None:
786 nodemaputil.update_persistent_nodemap(self)
786 nodemaputil.update_persistent_nodemap(self)
787 else:
787 else:
788 nodemaputil.setup_persistent_nodemap(transaction, self)
788 nodemaputil.setup_persistent_nodemap(transaction, self)
789
789
790 def clearcaches(self):
790 def clearcaches(self):
791 self._revisioncache = None
791 self._revisioncache = None
792 self._chainbasecache.clear()
792 self._chainbasecache.clear()
793 self._chunkcache = (0, b'')
793 self._chunkcache = (0, b'')
794 self._pcache = {}
794 self._pcache = {}
795 self._nodemap_docket = None
795 self._nodemap_docket = None
796 self.index.clearcaches()
796 self.index.clearcaches()
797 # The python code is the one responsible for validating the docket, we
797 # The python code is the one responsible for validating the docket, we
798 # end up having to refresh it here.
798 # end up having to refresh it here.
799 use_nodemap = (
799 use_nodemap = (
800 not self._inline
800 not self._inline
801 and self.nodemap_file is not None
801 and self.nodemap_file is not None
802 and util.safehasattr(self.index, 'update_nodemap_data')
802 and util.safehasattr(self.index, 'update_nodemap_data')
803 )
803 )
804 if use_nodemap:
804 if use_nodemap:
805 nodemap_data = nodemaputil.persisted_data(self)
805 nodemap_data = nodemaputil.persisted_data(self)
806 if nodemap_data is not None:
806 if nodemap_data is not None:
807 self._nodemap_docket = nodemap_data[0]
807 self._nodemap_docket = nodemap_data[0]
808 self.index.update_nodemap_data(*nodemap_data)
808 self.index.update_nodemap_data(*nodemap_data)
809
809
810 def rev(self, node):
810 def rev(self, node):
811 try:
811 try:
812 return self.index.rev(node)
812 return self.index.rev(node)
813 except TypeError:
813 except TypeError:
814 raise
814 raise
815 except error.RevlogError:
815 except error.RevlogError:
816 # parsers.c radix tree lookup failed
816 # parsers.c radix tree lookup failed
817 if node == wdirid or node in wdirfilenodeids:
817 if node == wdirid or node in wdirfilenodeids:
818 raise error.WdirUnsupported
818 raise error.WdirUnsupported
819 raise error.LookupError(node, self.indexfile, _(b'no node'))
819 raise error.LookupError(node, self.indexfile, _(b'no node'))
820
820
821 # Accessors for index entries.
821 # Accessors for index entries.
822
822
823 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
823 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
824 # are flags.
824 # are flags.
825 def start(self, rev):
825 def start(self, rev):
826 return int(self.index[rev][0] >> 16)
826 return int(self.index[rev][0] >> 16)
827
827
828 def flags(self, rev):
828 def flags(self, rev):
829 return self.index[rev][0] & 0xFFFF
829 return self.index[rev][0] & 0xFFFF
830
830
831 def length(self, rev):
831 def length(self, rev):
832 return self.index[rev][1]
832 return self.index[rev][1]
833
833
834 def rawsize(self, rev):
834 def rawsize(self, rev):
835 """return the length of the uncompressed text for a given revision"""
835 """return the length of the uncompressed text for a given revision"""
836 l = self.index[rev][2]
836 l = self.index[rev][2]
837 if l >= 0:
837 if l >= 0:
838 return l
838 return l
839
839
840 t = self.rawdata(rev)
840 t = self.rawdata(rev)
841 return len(t)
841 return len(t)
842
842
843 def size(self, rev):
843 def size(self, rev):
844 """length of non-raw text (processed by a "read" flag processor)"""
844 """length of non-raw text (processed by a "read" flag processor)"""
845 # fast path: if no "read" flag processor could change the content,
845 # fast path: if no "read" flag processor could change the content,
846 # size is rawsize. note: ELLIPSIS is known to not change the content.
846 # size is rawsize. note: ELLIPSIS is known to not change the content.
847 flags = self.flags(rev)
847 flags = self.flags(rev)
848 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
848 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
849 return self.rawsize(rev)
849 return self.rawsize(rev)
850
850
851 return len(self.revision(rev, raw=False))
851 return len(self.revision(rev, raw=False))
852
852
853 def chainbase(self, rev):
853 def chainbase(self, rev):
854 base = self._chainbasecache.get(rev)
854 base = self._chainbasecache.get(rev)
855 if base is not None:
855 if base is not None:
856 return base
856 return base
857
857
858 index = self.index
858 index = self.index
859 iterrev = rev
859 iterrev = rev
860 base = index[iterrev][3]
860 base = index[iterrev][3]
861 while base != iterrev:
861 while base != iterrev:
862 iterrev = base
862 iterrev = base
863 base = index[iterrev][3]
863 base = index[iterrev][3]
864
864
865 self._chainbasecache[rev] = base
865 self._chainbasecache[rev] = base
866 return base
866 return base
867
867
868 def linkrev(self, rev):
868 def linkrev(self, rev):
869 return self.index[rev][4]
869 return self.index[rev][4]
870
870
871 def parentrevs(self, rev):
871 def parentrevs(self, rev):
872 try:
872 try:
873 entry = self.index[rev]
873 entry = self.index[rev]
874 except IndexError:
874 except IndexError:
875 if rev == wdirrev:
875 if rev == wdirrev:
876 raise error.WdirUnsupported
876 raise error.WdirUnsupported
877 raise
877 raise
878
878
879 return entry[5], entry[6]
879 return entry[5], entry[6]
880
880
881 # fast parentrevs(rev) where rev isn't filtered
881 # fast parentrevs(rev) where rev isn't filtered
882 _uncheckedparentrevs = parentrevs
882 _uncheckedparentrevs = parentrevs
883
883
884 def node(self, rev):
884 def node(self, rev):
885 try:
885 try:
886 return self.index[rev][7]
886 return self.index[rev][7]
887 except IndexError:
887 except IndexError:
888 if rev == wdirrev:
888 if rev == wdirrev:
889 raise error.WdirUnsupported
889 raise error.WdirUnsupported
890 raise
890 raise
891
891
892 # Derived from index values.
892 # Derived from index values.
893
893
894 def end(self, rev):
894 def end(self, rev):
895 return self.start(rev) + self.length(rev)
895 return self.start(rev) + self.length(rev)
896
896
897 def parents(self, node):
897 def parents(self, node):
898 i = self.index
898 i = self.index
899 d = i[self.rev(node)]
899 d = i[self.rev(node)]
900 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
900 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
901
901
902 def chainlen(self, rev):
902 def chainlen(self, rev):
903 return self._chaininfo(rev)[0]
903 return self._chaininfo(rev)[0]
904
904
905 def _chaininfo(self, rev):
905 def _chaininfo(self, rev):
906 chaininfocache = self._chaininfocache
906 chaininfocache = self._chaininfocache
907 if rev in chaininfocache:
907 if rev in chaininfocache:
908 return chaininfocache[rev]
908 return chaininfocache[rev]
909 index = self.index
909 index = self.index
910 generaldelta = self._generaldelta
910 generaldelta = self._generaldelta
911 iterrev = rev
911 iterrev = rev
912 e = index[iterrev]
912 e = index[iterrev]
913 clen = 0
913 clen = 0
914 compresseddeltalen = 0
914 compresseddeltalen = 0
915 while iterrev != e[3]:
915 while iterrev != e[3]:
916 clen += 1
916 clen += 1
917 compresseddeltalen += e[1]
917 compresseddeltalen += e[1]
918 if generaldelta:
918 if generaldelta:
919 iterrev = e[3]
919 iterrev = e[3]
920 else:
920 else:
921 iterrev -= 1
921 iterrev -= 1
922 if iterrev in chaininfocache:
922 if iterrev in chaininfocache:
923 t = chaininfocache[iterrev]
923 t = chaininfocache[iterrev]
924 clen += t[0]
924 clen += t[0]
925 compresseddeltalen += t[1]
925 compresseddeltalen += t[1]
926 break
926 break
927 e = index[iterrev]
927 e = index[iterrev]
928 else:
928 else:
929 # Add text length of base since decompressing that also takes
929 # Add text length of base since decompressing that also takes
930 # work. For cache hits the length is already included.
930 # work. For cache hits the length is already included.
931 compresseddeltalen += e[1]
931 compresseddeltalen += e[1]
932 r = (clen, compresseddeltalen)
932 r = (clen, compresseddeltalen)
933 chaininfocache[rev] = r
933 chaininfocache[rev] = r
934 return r
934 return r
935
935
936 def _deltachain(self, rev, stoprev=None):
936 def _deltachain(self, rev, stoprev=None):
937 """Obtain the delta chain for a revision.
937 """Obtain the delta chain for a revision.
938
938
939 ``stoprev`` specifies a revision to stop at. If not specified, we
939 ``stoprev`` specifies a revision to stop at. If not specified, we
940 stop at the base of the chain.
940 stop at the base of the chain.
941
941
942 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
942 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
943 revs in ascending order and ``stopped`` is a bool indicating whether
943 revs in ascending order and ``stopped`` is a bool indicating whether
944 ``stoprev`` was hit.
944 ``stoprev`` was hit.
945 """
945 """
946 # Try C implementation.
946 # Try C implementation.
947 try:
947 try:
948 return self.index.deltachain(rev, stoprev, self._generaldelta)
948 return self.index.deltachain(rev, stoprev, self._generaldelta)
949 except AttributeError:
949 except AttributeError:
950 pass
950 pass
951
951
952 chain = []
952 chain = []
953
953
954 # Alias to prevent attribute lookup in tight loop.
954 # Alias to prevent attribute lookup in tight loop.
955 index = self.index
955 index = self.index
956 generaldelta = self._generaldelta
956 generaldelta = self._generaldelta
957
957
958 iterrev = rev
958 iterrev = rev
959 e = index[iterrev]
959 e = index[iterrev]
960 while iterrev != e[3] and iterrev != stoprev:
960 while iterrev != e[3] and iterrev != stoprev:
961 chain.append(iterrev)
961 chain.append(iterrev)
962 if generaldelta:
962 if generaldelta:
963 iterrev = e[3]
963 iterrev = e[3]
964 else:
964 else:
965 iterrev -= 1
965 iterrev -= 1
966 e = index[iterrev]
966 e = index[iterrev]
967
967
968 if iterrev == stoprev:
968 if iterrev == stoprev:
969 stopped = True
969 stopped = True
970 else:
970 else:
971 chain.append(iterrev)
971 chain.append(iterrev)
972 stopped = False
972 stopped = False
973
973
974 chain.reverse()
974 chain.reverse()
975 return chain, stopped
975 return chain, stopped
976
976
977 def ancestors(self, revs, stoprev=0, inclusive=False):
977 def ancestors(self, revs, stoprev=0, inclusive=False):
978 """Generate the ancestors of 'revs' in reverse revision order.
978 """Generate the ancestors of 'revs' in reverse revision order.
979 Does not generate revs lower than stoprev.
979 Does not generate revs lower than stoprev.
980
980
981 See the documentation for ancestor.lazyancestors for more details."""
981 See the documentation for ancestor.lazyancestors for more details."""
982
982
983 # first, make sure start revisions aren't filtered
983 # first, make sure start revisions aren't filtered
984 revs = list(revs)
984 revs = list(revs)
985 checkrev = self.node
985 checkrev = self.node
986 for r in revs:
986 for r in revs:
987 checkrev(r)
987 checkrev(r)
988 # and we're sure ancestors aren't filtered as well
988 # and we're sure ancestors aren't filtered as well
989
989
990 if rustancestor is not None:
990 if rustancestor is not None:
991 lazyancestors = rustancestor.LazyAncestors
991 lazyancestors = rustancestor.LazyAncestors
992 arg = self.index
992 arg = self.index
993 else:
993 else:
994 lazyancestors = ancestor.lazyancestors
994 lazyancestors = ancestor.lazyancestors
995 arg = self._uncheckedparentrevs
995 arg = self._uncheckedparentrevs
996 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
996 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
997
997
998 def descendants(self, revs):
998 def descendants(self, revs):
999 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
999 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1000
1000
1001 def findcommonmissing(self, common=None, heads=None):
1001 def findcommonmissing(self, common=None, heads=None):
1002 """Return a tuple of the ancestors of common and the ancestors of heads
1002 """Return a tuple of the ancestors of common and the ancestors of heads
1003 that are not ancestors of common. In revset terminology, we return the
1003 that are not ancestors of common. In revset terminology, we return the
1004 tuple:
1004 tuple:
1005
1005
1006 ::common, (::heads) - (::common)
1006 ::common, (::heads) - (::common)
1007
1007
1008 The list is sorted by revision number, meaning it is
1008 The list is sorted by revision number, meaning it is
1009 topologically sorted.
1009 topologically sorted.
1010
1010
1011 'heads' and 'common' are both lists of node IDs. If heads is
1011 'heads' and 'common' are both lists of node IDs. If heads is
1012 not supplied, uses all of the revlog's heads. If common is not
1012 not supplied, uses all of the revlog's heads. If common is not
1013 supplied, uses nullid."""
1013 supplied, uses nullid."""
1014 if common is None:
1014 if common is None:
1015 common = [nullid]
1015 common = [nullid]
1016 if heads is None:
1016 if heads is None:
1017 heads = self.heads()
1017 heads = self.heads()
1018
1018
1019 common = [self.rev(n) for n in common]
1019 common = [self.rev(n) for n in common]
1020 heads = [self.rev(n) for n in heads]
1020 heads = [self.rev(n) for n in heads]
1021
1021
1022 # we want the ancestors, but inclusive
1022 # we want the ancestors, but inclusive
1023 class lazyset(object):
1023 class lazyset(object):
1024 def __init__(self, lazyvalues):
1024 def __init__(self, lazyvalues):
1025 self.addedvalues = set()
1025 self.addedvalues = set()
1026 self.lazyvalues = lazyvalues
1026 self.lazyvalues = lazyvalues
1027
1027
1028 def __contains__(self, value):
1028 def __contains__(self, value):
1029 return value in self.addedvalues or value in self.lazyvalues
1029 return value in self.addedvalues or value in self.lazyvalues
1030
1030
1031 def __iter__(self):
1031 def __iter__(self):
1032 added = self.addedvalues
1032 added = self.addedvalues
1033 for r in added:
1033 for r in added:
1034 yield r
1034 yield r
1035 for r in self.lazyvalues:
1035 for r in self.lazyvalues:
1036 if not r in added:
1036 if not r in added:
1037 yield r
1037 yield r
1038
1038
1039 def add(self, value):
1039 def add(self, value):
1040 self.addedvalues.add(value)
1040 self.addedvalues.add(value)
1041
1041
1042 def update(self, values):
1042 def update(self, values):
1043 self.addedvalues.update(values)
1043 self.addedvalues.update(values)
1044
1044
1045 has = lazyset(self.ancestors(common))
1045 has = lazyset(self.ancestors(common))
1046 has.add(nullrev)
1046 has.add(nullrev)
1047 has.update(common)
1047 has.update(common)
1048
1048
1049 # take all ancestors from heads that aren't in has
1049 # take all ancestors from heads that aren't in has
1050 missing = set()
1050 missing = set()
1051 visit = collections.deque(r for r in heads if r not in has)
1051 visit = collections.deque(r for r in heads if r not in has)
1052 while visit:
1052 while visit:
1053 r = visit.popleft()
1053 r = visit.popleft()
1054 if r in missing:
1054 if r in missing:
1055 continue
1055 continue
1056 else:
1056 else:
1057 missing.add(r)
1057 missing.add(r)
1058 for p in self.parentrevs(r):
1058 for p in self.parentrevs(r):
1059 if p not in has:
1059 if p not in has:
1060 visit.append(p)
1060 visit.append(p)
1061 missing = list(missing)
1061 missing = list(missing)
1062 missing.sort()
1062 missing.sort()
1063 return has, [self.node(miss) for miss in missing]
1063 return has, [self.node(miss) for miss in missing]
1064
1064
1065 def incrementalmissingrevs(self, common=None):
1065 def incrementalmissingrevs(self, common=None):
1066 """Return an object that can be used to incrementally compute the
1066 """Return an object that can be used to incrementally compute the
1067 revision numbers of the ancestors of arbitrary sets that are not
1067 revision numbers of the ancestors of arbitrary sets that are not
1068 ancestors of common. This is an ancestor.incrementalmissingancestors
1068 ancestors of common. This is an ancestor.incrementalmissingancestors
1069 object.
1069 object.
1070
1070
1071 'common' is a list of revision numbers. If common is not supplied, uses
1071 'common' is a list of revision numbers. If common is not supplied, uses
1072 nullrev.
1072 nullrev.
1073 """
1073 """
1074 if common is None:
1074 if common is None:
1075 common = [nullrev]
1075 common = [nullrev]
1076
1076
1077 if rustancestor is not None:
1077 if rustancestor is not None:
1078 return rustancestor.MissingAncestors(self.index, common)
1078 return rustancestor.MissingAncestors(self.index, common)
1079 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1079 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1080
1080
1081 def findmissingrevs(self, common=None, heads=None):
1081 def findmissingrevs(self, common=None, heads=None):
1082 """Return the revision numbers of the ancestors of heads that
1082 """Return the revision numbers of the ancestors of heads that
1083 are not ancestors of common.
1083 are not ancestors of common.
1084
1084
1085 More specifically, return a list of revision numbers corresponding to
1085 More specifically, return a list of revision numbers corresponding to
1086 nodes N such that every N satisfies the following constraints:
1086 nodes N such that every N satisfies the following constraints:
1087
1087
1088 1. N is an ancestor of some node in 'heads'
1088 1. N is an ancestor of some node in 'heads'
1089 2. N is not an ancestor of any node in 'common'
1089 2. N is not an ancestor of any node in 'common'
1090
1090
1091 The list is sorted by revision number, meaning it is
1091 The list is sorted by revision number, meaning it is
1092 topologically sorted.
1092 topologically sorted.
1093
1093
1094 'heads' and 'common' are both lists of revision numbers. If heads is
1094 'heads' and 'common' are both lists of revision numbers. If heads is
1095 not supplied, uses all of the revlog's heads. If common is not
1095 not supplied, uses all of the revlog's heads. If common is not
1096 supplied, uses nullid."""
1096 supplied, uses nullid."""
1097 if common is None:
1097 if common is None:
1098 common = [nullrev]
1098 common = [nullrev]
1099 if heads is None:
1099 if heads is None:
1100 heads = self.headrevs()
1100 heads = self.headrevs()
1101
1101
1102 inc = self.incrementalmissingrevs(common=common)
1102 inc = self.incrementalmissingrevs(common=common)
1103 return inc.missingancestors(heads)
1103 return inc.missingancestors(heads)
1104
1104
1105 def findmissing(self, common=None, heads=None):
1105 def findmissing(self, common=None, heads=None):
1106 """Return the ancestors of heads that are not ancestors of common.
1106 """Return the ancestors of heads that are not ancestors of common.
1107
1107
1108 More specifically, return a list of nodes N such that every N
1108 More specifically, return a list of nodes N such that every N
1109 satisfies the following constraints:
1109 satisfies the following constraints:
1110
1110
1111 1. N is an ancestor of some node in 'heads'
1111 1. N is an ancestor of some node in 'heads'
1112 2. N is not an ancestor of any node in 'common'
1112 2. N is not an ancestor of any node in 'common'
1113
1113
1114 The list is sorted by revision number, meaning it is
1114 The list is sorted by revision number, meaning it is
1115 topologically sorted.
1115 topologically sorted.
1116
1116
1117 'heads' and 'common' are both lists of node IDs. If heads is
1117 'heads' and 'common' are both lists of node IDs. If heads is
1118 not supplied, uses all of the revlog's heads. If common is not
1118 not supplied, uses all of the revlog's heads. If common is not
1119 supplied, uses nullid."""
1119 supplied, uses nullid."""
1120 if common is None:
1120 if common is None:
1121 common = [nullid]
1121 common = [nullid]
1122 if heads is None:
1122 if heads is None:
1123 heads = self.heads()
1123 heads = self.heads()
1124
1124
1125 common = [self.rev(n) for n in common]
1125 common = [self.rev(n) for n in common]
1126 heads = [self.rev(n) for n in heads]
1126 heads = [self.rev(n) for n in heads]
1127
1127
1128 inc = self.incrementalmissingrevs(common=common)
1128 inc = self.incrementalmissingrevs(common=common)
1129 return [self.node(r) for r in inc.missingancestors(heads)]
1129 return [self.node(r) for r in inc.missingancestors(heads)]
1130
1130
1131 def nodesbetween(self, roots=None, heads=None):
1131 def nodesbetween(self, roots=None, heads=None):
1132 """Return a topological path from 'roots' to 'heads'.
1132 """Return a topological path from 'roots' to 'heads'.
1133
1133
1134 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1134 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1135 topologically sorted list of all nodes N that satisfy both of
1135 topologically sorted list of all nodes N that satisfy both of
1136 these constraints:
1136 these constraints:
1137
1137
1138 1. N is a descendant of some node in 'roots'
1138 1. N is a descendant of some node in 'roots'
1139 2. N is an ancestor of some node in 'heads'
1139 2. N is an ancestor of some node in 'heads'
1140
1140
1141 Every node is considered to be both a descendant and an ancestor
1141 Every node is considered to be both a descendant and an ancestor
1142 of itself, so every reachable node in 'roots' and 'heads' will be
1142 of itself, so every reachable node in 'roots' and 'heads' will be
1143 included in 'nodes'.
1143 included in 'nodes'.
1144
1144
1145 'outroots' is the list of reachable nodes in 'roots', i.e., the
1145 'outroots' is the list of reachable nodes in 'roots', i.e., the
1146 subset of 'roots' that is returned in 'nodes'. Likewise,
1146 subset of 'roots' that is returned in 'nodes'. Likewise,
1147 'outheads' is the subset of 'heads' that is also in 'nodes'.
1147 'outheads' is the subset of 'heads' that is also in 'nodes'.
1148
1148
1149 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1149 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1150 unspecified, uses nullid as the only root. If 'heads' is
1150 unspecified, uses nullid as the only root. If 'heads' is
1151 unspecified, uses list of all of the revlog's heads."""
1151 unspecified, uses list of all of the revlog's heads."""
1152 nonodes = ([], [], [])
1152 nonodes = ([], [], [])
1153 if roots is not None:
1153 if roots is not None:
1154 roots = list(roots)
1154 roots = list(roots)
1155 if not roots:
1155 if not roots:
1156 return nonodes
1156 return nonodes
1157 lowestrev = min([self.rev(n) for n in roots])
1157 lowestrev = min([self.rev(n) for n in roots])
1158 else:
1158 else:
1159 roots = [nullid] # Everybody's a descendant of nullid
1159 roots = [nullid] # Everybody's a descendant of nullid
1160 lowestrev = nullrev
1160 lowestrev = nullrev
1161 if (lowestrev == nullrev) and (heads is None):
1161 if (lowestrev == nullrev) and (heads is None):
1162 # We want _all_ the nodes!
1162 # We want _all_ the nodes!
1163 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1163 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1164 if heads is None:
1164 if heads is None:
1165 # All nodes are ancestors, so the latest ancestor is the last
1165 # All nodes are ancestors, so the latest ancestor is the last
1166 # node.
1166 # node.
1167 highestrev = len(self) - 1
1167 highestrev = len(self) - 1
1168 # Set ancestors to None to signal that every node is an ancestor.
1168 # Set ancestors to None to signal that every node is an ancestor.
1169 ancestors = None
1169 ancestors = None
1170 # Set heads to an empty dictionary for later discovery of heads
1170 # Set heads to an empty dictionary for later discovery of heads
1171 heads = {}
1171 heads = {}
1172 else:
1172 else:
1173 heads = list(heads)
1173 heads = list(heads)
1174 if not heads:
1174 if not heads:
1175 return nonodes
1175 return nonodes
1176 ancestors = set()
1176 ancestors = set()
1177 # Turn heads into a dictionary so we can remove 'fake' heads.
1177 # Turn heads into a dictionary so we can remove 'fake' heads.
1178 # Also, later we will be using it to filter out the heads we can't
1178 # Also, later we will be using it to filter out the heads we can't
1179 # find from roots.
1179 # find from roots.
1180 heads = dict.fromkeys(heads, False)
1180 heads = dict.fromkeys(heads, False)
1181 # Start at the top and keep marking parents until we're done.
1181 # Start at the top and keep marking parents until we're done.
1182 nodestotag = set(heads)
1182 nodestotag = set(heads)
1183 # Remember where the top was so we can use it as a limit later.
1183 # Remember where the top was so we can use it as a limit later.
1184 highestrev = max([self.rev(n) for n in nodestotag])
1184 highestrev = max([self.rev(n) for n in nodestotag])
1185 while nodestotag:
1185 while nodestotag:
1186 # grab a node to tag
1186 # grab a node to tag
1187 n = nodestotag.pop()
1187 n = nodestotag.pop()
1188 # Never tag nullid
1188 # Never tag nullid
1189 if n == nullid:
1189 if n == nullid:
1190 continue
1190 continue
1191 # A node's revision number represents its place in a
1191 # A node's revision number represents its place in a
1192 # topologically sorted list of nodes.
1192 # topologically sorted list of nodes.
1193 r = self.rev(n)
1193 r = self.rev(n)
1194 if r >= lowestrev:
1194 if r >= lowestrev:
1195 if n not in ancestors:
1195 if n not in ancestors:
1196 # If we are possibly a descendant of one of the roots
1196 # If we are possibly a descendant of one of the roots
1197 # and we haven't already been marked as an ancestor
1197 # and we haven't already been marked as an ancestor
1198 ancestors.add(n) # Mark as ancestor
1198 ancestors.add(n) # Mark as ancestor
1199 # Add non-nullid parents to list of nodes to tag.
1199 # Add non-nullid parents to list of nodes to tag.
1200 nodestotag.update(
1200 nodestotag.update(
1201 [p for p in self.parents(n) if p != nullid]
1201 [p for p in self.parents(n) if p != nullid]
1202 )
1202 )
1203 elif n in heads: # We've seen it before, is it a fake head?
1203 elif n in heads: # We've seen it before, is it a fake head?
1204 # So it is, real heads should not be the ancestors of
1204 # So it is, real heads should not be the ancestors of
1205 # any other heads.
1205 # any other heads.
1206 heads.pop(n)
1206 heads.pop(n)
1207 if not ancestors:
1207 if not ancestors:
1208 return nonodes
1208 return nonodes
1209 # Now that we have our set of ancestors, we want to remove any
1209 # Now that we have our set of ancestors, we want to remove any
1210 # roots that are not ancestors.
1210 # roots that are not ancestors.
1211
1211
1212 # If one of the roots was nullid, everything is included anyway.
1212 # If one of the roots was nullid, everything is included anyway.
1213 if lowestrev > nullrev:
1213 if lowestrev > nullrev:
1214 # But, since we weren't, let's recompute the lowest rev to not
1214 # But, since we weren't, let's recompute the lowest rev to not
1215 # include roots that aren't ancestors.
1215 # include roots that aren't ancestors.
1216
1216
1217 # Filter out roots that aren't ancestors of heads
1217 # Filter out roots that aren't ancestors of heads
1218 roots = [root for root in roots if root in ancestors]
1218 roots = [root for root in roots if root in ancestors]
1219 # Recompute the lowest revision
1219 # Recompute the lowest revision
1220 if roots:
1220 if roots:
1221 lowestrev = min([self.rev(root) for root in roots])
1221 lowestrev = min([self.rev(root) for root in roots])
1222 else:
1222 else:
1223 # No more roots? Return empty list
1223 # No more roots? Return empty list
1224 return nonodes
1224 return nonodes
1225 else:
1225 else:
1226 # We are descending from nullid, and don't need to care about
1226 # We are descending from nullid, and don't need to care about
1227 # any other roots.
1227 # any other roots.
1228 lowestrev = nullrev
1228 lowestrev = nullrev
1229 roots = [nullid]
1229 roots = [nullid]
1230 # Transform our roots list into a set.
1230 # Transform our roots list into a set.
1231 descendants = set(roots)
1231 descendants = set(roots)
1232 # Also, keep the original roots so we can filter out roots that aren't
1232 # Also, keep the original roots so we can filter out roots that aren't
1233 # 'real' roots (i.e. are descended from other roots).
1233 # 'real' roots (i.e. are descended from other roots).
1234 roots = descendants.copy()
1234 roots = descendants.copy()
1235 # Our topologically sorted list of output nodes.
1235 # Our topologically sorted list of output nodes.
1236 orderedout = []
1236 orderedout = []
1237 # Don't start at nullid since we don't want nullid in our output list,
1237 # Don't start at nullid since we don't want nullid in our output list,
1238 # and if nullid shows up in descendants, empty parents will look like
1238 # and if nullid shows up in descendants, empty parents will look like
1239 # they're descendants.
1239 # they're descendants.
1240 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1240 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1241 n = self.node(r)
1241 n = self.node(r)
1242 isdescendant = False
1242 isdescendant = False
1243 if lowestrev == nullrev: # Everybody is a descendant of nullid
1243 if lowestrev == nullrev: # Everybody is a descendant of nullid
1244 isdescendant = True
1244 isdescendant = True
1245 elif n in descendants:
1245 elif n in descendants:
1246 # n is already a descendant
1246 # n is already a descendant
1247 isdescendant = True
1247 isdescendant = True
1248 # This check only needs to be done here because all the roots
1248 # This check only needs to be done here because all the roots
1249 # will start being marked is descendants before the loop.
1249 # will start being marked is descendants before the loop.
1250 if n in roots:
1250 if n in roots:
1251 # If n was a root, check if it's a 'real' root.
1251 # If n was a root, check if it's a 'real' root.
1252 p = tuple(self.parents(n))
1252 p = tuple(self.parents(n))
1253 # If any of its parents are descendants, it's not a root.
1253 # If any of its parents are descendants, it's not a root.
1254 if (p[0] in descendants) or (p[1] in descendants):
1254 if (p[0] in descendants) or (p[1] in descendants):
1255 roots.remove(n)
1255 roots.remove(n)
1256 else:
1256 else:
1257 p = tuple(self.parents(n))
1257 p = tuple(self.parents(n))
1258 # A node is a descendant if either of its parents are
1258 # A node is a descendant if either of its parents are
1259 # descendants. (We seeded the dependents list with the roots
1259 # descendants. (We seeded the dependents list with the roots
1260 # up there, remember?)
1260 # up there, remember?)
1261 if (p[0] in descendants) or (p[1] in descendants):
1261 if (p[0] in descendants) or (p[1] in descendants):
1262 descendants.add(n)
1262 descendants.add(n)
1263 isdescendant = True
1263 isdescendant = True
1264 if isdescendant and ((ancestors is None) or (n in ancestors)):
1264 if isdescendant and ((ancestors is None) or (n in ancestors)):
1265 # Only include nodes that are both descendants and ancestors.
1265 # Only include nodes that are both descendants and ancestors.
1266 orderedout.append(n)
1266 orderedout.append(n)
1267 if (ancestors is not None) and (n in heads):
1267 if (ancestors is not None) and (n in heads):
1268 # We're trying to figure out which heads are reachable
1268 # We're trying to figure out which heads are reachable
1269 # from roots.
1269 # from roots.
1270 # Mark this head as having been reached
1270 # Mark this head as having been reached
1271 heads[n] = True
1271 heads[n] = True
1272 elif ancestors is None:
1272 elif ancestors is None:
1273 # Otherwise, we're trying to discover the heads.
1273 # Otherwise, we're trying to discover the heads.
1274 # Assume this is a head because if it isn't, the next step
1274 # Assume this is a head because if it isn't, the next step
1275 # will eventually remove it.
1275 # will eventually remove it.
1276 heads[n] = True
1276 heads[n] = True
1277 # But, obviously its parents aren't.
1277 # But, obviously its parents aren't.
1278 for p in self.parents(n):
1278 for p in self.parents(n):
1279 heads.pop(p, None)
1279 heads.pop(p, None)
1280 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1280 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1281 roots = list(roots)
1281 roots = list(roots)
1282 assert orderedout
1282 assert orderedout
1283 assert roots
1283 assert roots
1284 assert heads
1284 assert heads
1285 return (orderedout, roots, heads)
1285 return (orderedout, roots, heads)
1286
1286
1287 def headrevs(self, revs=None):
1287 def headrevs(self, revs=None):
1288 if revs is None:
1288 if revs is None:
1289 try:
1289 try:
1290 return self.index.headrevs()
1290 return self.index.headrevs()
1291 except AttributeError:
1291 except AttributeError:
1292 return self._headrevs()
1292 return self._headrevs()
1293 if rustdagop is not None:
1293 if rustdagop is not None:
1294 return rustdagop.headrevs(self.index, revs)
1294 return rustdagop.headrevs(self.index, revs)
1295 return dagop.headrevs(revs, self._uncheckedparentrevs)
1295 return dagop.headrevs(revs, self._uncheckedparentrevs)
1296
1296
1297 def computephases(self, roots):
1297 def computephases(self, roots):
1298 return self.index.computephasesmapsets(roots)
1298 return self.index.computephasesmapsets(roots)
1299
1299
1300 def _headrevs(self):
1300 def _headrevs(self):
1301 count = len(self)
1301 count = len(self)
1302 if not count:
1302 if not count:
1303 return [nullrev]
1303 return [nullrev]
1304 # we won't iter over filtered rev so nobody is a head at start
1304 # we won't iter over filtered rev so nobody is a head at start
1305 ishead = [0] * (count + 1)
1305 ishead = [0] * (count + 1)
1306 index = self.index
1306 index = self.index
1307 for r in self:
1307 for r in self:
1308 ishead[r] = 1 # I may be an head
1308 ishead[r] = 1 # I may be an head
1309 e = index[r]
1309 e = index[r]
1310 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1310 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1311 return [r for r, val in enumerate(ishead) if val]
1311 return [r for r, val in enumerate(ishead) if val]
1312
1312
1313 def heads(self, start=None, stop=None):
1313 def heads(self, start=None, stop=None):
1314 """return the list of all nodes that have no children
1314 """return the list of all nodes that have no children
1315
1315
1316 if start is specified, only heads that are descendants of
1316 if start is specified, only heads that are descendants of
1317 start will be returned
1317 start will be returned
1318 if stop is specified, it will consider all the revs from stop
1318 if stop is specified, it will consider all the revs from stop
1319 as if they had no children
1319 as if they had no children
1320 """
1320 """
1321 if start is None and stop is None:
1321 if start is None and stop is None:
1322 if not len(self):
1322 if not len(self):
1323 return [nullid]
1323 return [nullid]
1324 return [self.node(r) for r in self.headrevs()]
1324 return [self.node(r) for r in self.headrevs()]
1325
1325
1326 if start is None:
1326 if start is None:
1327 start = nullrev
1327 start = nullrev
1328 else:
1328 else:
1329 start = self.rev(start)
1329 start = self.rev(start)
1330
1330
1331 stoprevs = {self.rev(n) for n in stop or []}
1331 stoprevs = {self.rev(n) for n in stop or []}
1332
1332
1333 revs = dagop.headrevssubset(
1333 revs = dagop.headrevssubset(
1334 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1334 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1335 )
1335 )
1336
1336
1337 return [self.node(rev) for rev in revs]
1337 return [self.node(rev) for rev in revs]
1338
1338
1339 def children(self, node):
1339 def children(self, node):
1340 """find the children of a given node"""
1340 """find the children of a given node"""
1341 c = []
1341 c = []
1342 p = self.rev(node)
1342 p = self.rev(node)
1343 for r in self.revs(start=p + 1):
1343 for r in self.revs(start=p + 1):
1344 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1344 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1345 if prevs:
1345 if prevs:
1346 for pr in prevs:
1346 for pr in prevs:
1347 if pr == p:
1347 if pr == p:
1348 c.append(self.node(r))
1348 c.append(self.node(r))
1349 elif p == nullrev:
1349 elif p == nullrev:
1350 c.append(self.node(r))
1350 c.append(self.node(r))
1351 return c
1351 return c
1352
1352
1353 def commonancestorsheads(self, a, b):
1353 def commonancestorsheads(self, a, b):
1354 """calculate all the heads of the common ancestors of nodes a and b"""
1354 """calculate all the heads of the common ancestors of nodes a and b"""
1355 a, b = self.rev(a), self.rev(b)
1355 a, b = self.rev(a), self.rev(b)
1356 ancs = self._commonancestorsheads(a, b)
1356 ancs = self._commonancestorsheads(a, b)
1357 return pycompat.maplist(self.node, ancs)
1357 return pycompat.maplist(self.node, ancs)
1358
1358
1359 def _commonancestorsheads(self, *revs):
1359 def _commonancestorsheads(self, *revs):
1360 """calculate all the heads of the common ancestors of revs"""
1360 """calculate all the heads of the common ancestors of revs"""
1361 try:
1361 try:
1362 ancs = self.index.commonancestorsheads(*revs)
1362 ancs = self.index.commonancestorsheads(*revs)
1363 except (AttributeError, OverflowError): # C implementation failed
1363 except (AttributeError, OverflowError): # C implementation failed
1364 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1364 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1365 return ancs
1365 return ancs
1366
1366
1367 def isancestor(self, a, b):
1367 def isancestor(self, a, b):
1368 """return True if node a is an ancestor of node b
1368 """return True if node a is an ancestor of node b
1369
1369
1370 A revision is considered an ancestor of itself."""
1370 A revision is considered an ancestor of itself."""
1371 a, b = self.rev(a), self.rev(b)
1371 a, b = self.rev(a), self.rev(b)
1372 return self.isancestorrev(a, b)
1372 return self.isancestorrev(a, b)
1373
1373
1374 def isancestorrev(self, a, b):
1374 def isancestorrev(self, a, b):
1375 """return True if revision a is an ancestor of revision b
1375 """return True if revision a is an ancestor of revision b
1376
1376
1377 A revision is considered an ancestor of itself.
1377 A revision is considered an ancestor of itself.
1378
1378
1379 The implementation of this is trivial but the use of
1379 The implementation of this is trivial but the use of
1380 reachableroots is not."""
1380 reachableroots is not."""
1381 if a == nullrev:
1381 if a == nullrev:
1382 return True
1382 return True
1383 elif a == b:
1383 elif a == b:
1384 return True
1384 return True
1385 elif a > b:
1385 elif a > b:
1386 return False
1386 return False
1387 return bool(self.reachableroots(a, [b], [a], includepath=False))
1387 return bool(self.reachableroots(a, [b], [a], includepath=False))
1388
1388
1389 def reachableroots(self, minroot, heads, roots, includepath=False):
1389 def reachableroots(self, minroot, heads, roots, includepath=False):
1390 """return (heads(::(<roots> and <roots>::<heads>)))
1390 """return (heads(::(<roots> and <roots>::<heads>)))
1391
1391
1392 If includepath is True, return (<roots>::<heads>)."""
1392 If includepath is True, return (<roots>::<heads>)."""
1393 try:
1393 try:
1394 return self.index.reachableroots2(
1394 return self.index.reachableroots2(
1395 minroot, heads, roots, includepath
1395 minroot, heads, roots, includepath
1396 )
1396 )
1397 except AttributeError:
1397 except AttributeError:
1398 return dagop._reachablerootspure(
1398 return dagop._reachablerootspure(
1399 self.parentrevs, minroot, roots, heads, includepath
1399 self.parentrevs, minroot, roots, heads, includepath
1400 )
1400 )
1401
1401
1402 def ancestor(self, a, b):
1402 def ancestor(self, a, b):
1403 """calculate the "best" common ancestor of nodes a and b"""
1403 """calculate the "best" common ancestor of nodes a and b"""
1404
1404
1405 a, b = self.rev(a), self.rev(b)
1405 a, b = self.rev(a), self.rev(b)
1406 try:
1406 try:
1407 ancs = self.index.ancestors(a, b)
1407 ancs = self.index.ancestors(a, b)
1408 except (AttributeError, OverflowError):
1408 except (AttributeError, OverflowError):
1409 ancs = ancestor.ancestors(self.parentrevs, a, b)
1409 ancs = ancestor.ancestors(self.parentrevs, a, b)
1410 if ancs:
1410 if ancs:
1411 # choose a consistent winner when there's a tie
1411 # choose a consistent winner when there's a tie
1412 return min(map(self.node, ancs))
1412 return min(map(self.node, ancs))
1413 return nullid
1413 return nullid
1414
1414
1415 def _match(self, id):
1415 def _match(self, id):
1416 if isinstance(id, int):
1416 if isinstance(id, int):
1417 # rev
1417 # rev
1418 return self.node(id)
1418 return self.node(id)
1419 if len(id) == 20:
1419 if len(id) == 20:
1420 # possibly a binary node
1420 # possibly a binary node
1421 # odds of a binary node being all hex in ASCII are 1 in 10**25
1421 # odds of a binary node being all hex in ASCII are 1 in 10**25
1422 try:
1422 try:
1423 node = id
1423 node = id
1424 self.rev(node) # quick search the index
1424 self.rev(node) # quick search the index
1425 return node
1425 return node
1426 except error.LookupError:
1426 except error.LookupError:
1427 pass # may be partial hex id
1427 pass # may be partial hex id
1428 try:
1428 try:
1429 # str(rev)
1429 # str(rev)
1430 rev = int(id)
1430 rev = int(id)
1431 if b"%d" % rev != id:
1431 if b"%d" % rev != id:
1432 raise ValueError
1432 raise ValueError
1433 if rev < 0:
1433 if rev < 0:
1434 rev = len(self) + rev
1434 rev = len(self) + rev
1435 if rev < 0 or rev >= len(self):
1435 if rev < 0 or rev >= len(self):
1436 raise ValueError
1436 raise ValueError
1437 return self.node(rev)
1437 return self.node(rev)
1438 except (ValueError, OverflowError):
1438 except (ValueError, OverflowError):
1439 pass
1439 pass
1440 if len(id) == 40:
1440 if len(id) == 40:
1441 try:
1441 try:
1442 # a full hex nodeid?
1442 # a full hex nodeid?
1443 node = bin(id)
1443 node = bin(id)
1444 self.rev(node)
1444 self.rev(node)
1445 return node
1445 return node
1446 except (TypeError, error.LookupError):
1446 except (TypeError, error.LookupError):
1447 pass
1447 pass
1448
1448
1449 def _partialmatch(self, id):
1449 def _partialmatch(self, id):
1450 # we don't care wdirfilenodeids as they should be always full hash
1450 # we don't care wdirfilenodeids as they should be always full hash
1451 maybewdir = wdirhex.startswith(id)
1451 maybewdir = wdirhex.startswith(id)
1452 try:
1452 try:
1453 partial = self.index.partialmatch(id)
1453 partial = self.index.partialmatch(id)
1454 if partial and self.hasnode(partial):
1454 if partial and self.hasnode(partial):
1455 if maybewdir:
1455 if maybewdir:
1456 # single 'ff...' match in radix tree, ambiguous with wdir
1456 # single 'ff...' match in radix tree, ambiguous with wdir
1457 raise error.RevlogError
1457 raise error.RevlogError
1458 return partial
1458 return partial
1459 if maybewdir:
1459 if maybewdir:
1460 # no 'ff...' match in radix tree, wdir identified
1460 # no 'ff...' match in radix tree, wdir identified
1461 raise error.WdirUnsupported
1461 raise error.WdirUnsupported
1462 return None
1462 return None
1463 except error.RevlogError:
1463 except error.RevlogError:
1464 # parsers.c radix tree lookup gave multiple matches
1464 # parsers.c radix tree lookup gave multiple matches
1465 # fast path: for unfiltered changelog, radix tree is accurate
1465 # fast path: for unfiltered changelog, radix tree is accurate
1466 if not getattr(self, 'filteredrevs', None):
1466 if not getattr(self, 'filteredrevs', None):
1467 raise error.AmbiguousPrefixLookupError(
1467 raise error.AmbiguousPrefixLookupError(
1468 id, self.indexfile, _(b'ambiguous identifier')
1468 id, self.indexfile, _(b'ambiguous identifier')
1469 )
1469 )
1470 # fall through to slow path that filters hidden revisions
1470 # fall through to slow path that filters hidden revisions
1471 except (AttributeError, ValueError):
1471 except (AttributeError, ValueError):
1472 # we are pure python, or key was too short to search radix tree
1472 # we are pure python, or key was too short to search radix tree
1473 pass
1473 pass
1474
1474
1475 if id in self._pcache:
1475 if id in self._pcache:
1476 return self._pcache[id]
1476 return self._pcache[id]
1477
1477
1478 if len(id) <= 40:
1478 if len(id) <= 40:
1479 try:
1479 try:
1480 # hex(node)[:...]
1480 # hex(node)[:...]
1481 l = len(id) // 2 # grab an even number of digits
1481 l = len(id) // 2 # grab an even number of digits
1482 prefix = bin(id[: l * 2])
1482 prefix = bin(id[: l * 2])
1483 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1483 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1484 nl = [
1484 nl = [
1485 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1485 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1486 ]
1486 ]
1487 if nullhex.startswith(id):
1487 if nullhex.startswith(id):
1488 nl.append(nullid)
1488 nl.append(nullid)
1489 if len(nl) > 0:
1489 if len(nl) > 0:
1490 if len(nl) == 1 and not maybewdir:
1490 if len(nl) == 1 and not maybewdir:
1491 self._pcache[id] = nl[0]
1491 self._pcache[id] = nl[0]
1492 return nl[0]
1492 return nl[0]
1493 raise error.AmbiguousPrefixLookupError(
1493 raise error.AmbiguousPrefixLookupError(
1494 id, self.indexfile, _(b'ambiguous identifier')
1494 id, self.indexfile, _(b'ambiguous identifier')
1495 )
1495 )
1496 if maybewdir:
1496 if maybewdir:
1497 raise error.WdirUnsupported
1497 raise error.WdirUnsupported
1498 return None
1498 return None
1499 except TypeError:
1499 except TypeError:
1500 pass
1500 pass
1501
1501
1502 def lookup(self, id):
1502 def lookup(self, id):
1503 """locate a node based on:
1503 """locate a node based on:
1504 - revision number or str(revision number)
1504 - revision number or str(revision number)
1505 - nodeid or subset of hex nodeid
1505 - nodeid or subset of hex nodeid
1506 """
1506 """
1507 n = self._match(id)
1507 n = self._match(id)
1508 if n is not None:
1508 if n is not None:
1509 return n
1509 return n
1510 n = self._partialmatch(id)
1510 n = self._partialmatch(id)
1511 if n:
1511 if n:
1512 return n
1512 return n
1513
1513
1514 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1514 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1515
1515
1516 def shortest(self, node, minlength=1):
1516 def shortest(self, node, minlength=1):
1517 """Find the shortest unambiguous prefix that matches node."""
1517 """Find the shortest unambiguous prefix that matches node."""
1518
1518
1519 def isvalid(prefix):
1519 def isvalid(prefix):
1520 try:
1520 try:
1521 matchednode = self._partialmatch(prefix)
1521 matchednode = self._partialmatch(prefix)
1522 except error.AmbiguousPrefixLookupError:
1522 except error.AmbiguousPrefixLookupError:
1523 return False
1523 return False
1524 except error.WdirUnsupported:
1524 except error.WdirUnsupported:
1525 # single 'ff...' match
1525 # single 'ff...' match
1526 return True
1526 return True
1527 if matchednode is None:
1527 if matchednode is None:
1528 raise error.LookupError(node, self.indexfile, _(b'no node'))
1528 raise error.LookupError(node, self.indexfile, _(b'no node'))
1529 return True
1529 return True
1530
1530
1531 def maybewdir(prefix):
1531 def maybewdir(prefix):
1532 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1532 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1533
1533
1534 hexnode = hex(node)
1534 hexnode = hex(node)
1535
1535
1536 def disambiguate(hexnode, minlength):
1536 def disambiguate(hexnode, minlength):
1537 """Disambiguate against wdirid."""
1537 """Disambiguate against wdirid."""
1538 for length in range(minlength, len(hexnode) + 1):
1538 for length in range(minlength, len(hexnode) + 1):
1539 prefix = hexnode[:length]
1539 prefix = hexnode[:length]
1540 if not maybewdir(prefix):
1540 if not maybewdir(prefix):
1541 return prefix
1541 return prefix
1542
1542
1543 if not getattr(self, 'filteredrevs', None):
1543 if not getattr(self, 'filteredrevs', None):
1544 try:
1544 try:
1545 length = max(self.index.shortest(node), minlength)
1545 length = max(self.index.shortest(node), minlength)
1546 return disambiguate(hexnode, length)
1546 return disambiguate(hexnode, length)
1547 except error.RevlogError:
1547 except error.RevlogError:
1548 if node != wdirid:
1548 if node != wdirid:
1549 raise error.LookupError(node, self.indexfile, _(b'no node'))
1549 raise error.LookupError(node, self.indexfile, _(b'no node'))
1550 except AttributeError:
1550 except AttributeError:
1551 # Fall through to pure code
1551 # Fall through to pure code
1552 pass
1552 pass
1553
1553
1554 if node == wdirid:
1554 if node == wdirid:
1555 for length in range(minlength, len(hexnode) + 1):
1555 for length in range(minlength, len(hexnode) + 1):
1556 prefix = hexnode[:length]
1556 prefix = hexnode[:length]
1557 if isvalid(prefix):
1557 if isvalid(prefix):
1558 return prefix
1558 return prefix
1559
1559
1560 for length in range(minlength, len(hexnode) + 1):
1560 for length in range(minlength, len(hexnode) + 1):
1561 prefix = hexnode[:length]
1561 prefix = hexnode[:length]
1562 if isvalid(prefix):
1562 if isvalid(prefix):
1563 return disambiguate(hexnode, length)
1563 return disambiguate(hexnode, length)
1564
1564
1565 def cmp(self, node, text):
1565 def cmp(self, node, text):
1566 """compare text with a given file revision
1566 """compare text with a given file revision
1567
1567
1568 returns True if text is different than what is stored.
1568 returns True if text is different than what is stored.
1569 """
1569 """
1570 p1, p2 = self.parents(node)
1570 p1, p2 = self.parents(node)
1571 return storageutil.hashrevisionsha1(text, p1, p2) != node
1571 return storageutil.hashrevisionsha1(text, p1, p2) != node
1572
1572
1573 def _cachesegment(self, offset, data):
1573 def _cachesegment(self, offset, data):
1574 """Add a segment to the revlog cache.
1574 """Add a segment to the revlog cache.
1575
1575
1576 Accepts an absolute offset and the data that is at that location.
1576 Accepts an absolute offset and the data that is at that location.
1577 """
1577 """
1578 o, d = self._chunkcache
1578 o, d = self._chunkcache
1579 # try to add to existing cache
1579 # try to add to existing cache
1580 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1580 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1581 self._chunkcache = o, d + data
1581 self._chunkcache = o, d + data
1582 else:
1582 else:
1583 self._chunkcache = offset, data
1583 self._chunkcache = offset, data
1584
1584
1585 def _readsegment(self, offset, length, df=None):
1585 def _readsegment(self, offset, length, df=None):
1586 """Load a segment of raw data from the revlog.
1586 """Load a segment of raw data from the revlog.
1587
1587
1588 Accepts an absolute offset, length to read, and an optional existing
1588 Accepts an absolute offset, length to read, and an optional existing
1589 file handle to read from.
1589 file handle to read from.
1590
1590
1591 If an existing file handle is passed, it will be seeked and the
1591 If an existing file handle is passed, it will be seeked and the
1592 original seek position will NOT be restored.
1592 original seek position will NOT be restored.
1593
1593
1594 Returns a str or buffer of raw byte data.
1594 Returns a str or buffer of raw byte data.
1595
1595
1596 Raises if the requested number of bytes could not be read.
1596 Raises if the requested number of bytes could not be read.
1597 """
1597 """
1598 # Cache data both forward and backward around the requested
1598 # Cache data both forward and backward around the requested
1599 # data, in a fixed size window. This helps speed up operations
1599 # data, in a fixed size window. This helps speed up operations
1600 # involving reading the revlog backwards.
1600 # involving reading the revlog backwards.
1601 cachesize = self._chunkcachesize
1601 cachesize = self._chunkcachesize
1602 realoffset = offset & ~(cachesize - 1)
1602 realoffset = offset & ~(cachesize - 1)
1603 reallength = (
1603 reallength = (
1604 (offset + length + cachesize) & ~(cachesize - 1)
1604 (offset + length + cachesize) & ~(cachesize - 1)
1605 ) - realoffset
1605 ) - realoffset
1606 with self._datareadfp(df) as df:
1606 with self._datareadfp(df) as df:
1607 df.seek(realoffset)
1607 df.seek(realoffset)
1608 d = df.read(reallength)
1608 d = df.read(reallength)
1609
1609
1610 self._cachesegment(realoffset, d)
1610 self._cachesegment(realoffset, d)
1611 if offset != realoffset or reallength != length:
1611 if offset != realoffset or reallength != length:
1612 startoffset = offset - realoffset
1612 startoffset = offset - realoffset
1613 if len(d) - startoffset < length:
1613 if len(d) - startoffset < length:
1614 raise error.RevlogError(
1614 raise error.RevlogError(
1615 _(
1615 _(
1616 b'partial read of revlog %s; expected %d bytes from '
1616 b'partial read of revlog %s; expected %d bytes from '
1617 b'offset %d, got %d'
1617 b'offset %d, got %d'
1618 )
1618 )
1619 % (
1619 % (
1620 self.indexfile if self._inline else self.datafile,
1620 self.indexfile if self._inline else self.datafile,
1621 length,
1621 length,
1622 realoffset,
1622 realoffset,
1623 len(d) - startoffset,
1623 len(d) - startoffset,
1624 )
1624 )
1625 )
1625 )
1626
1626
1627 return util.buffer(d, startoffset, length)
1627 return util.buffer(d, startoffset, length)
1628
1628
1629 if len(d) < length:
1629 if len(d) < length:
1630 raise error.RevlogError(
1630 raise error.RevlogError(
1631 _(
1631 _(
1632 b'partial read of revlog %s; expected %d bytes from offset '
1632 b'partial read of revlog %s; expected %d bytes from offset '
1633 b'%d, got %d'
1633 b'%d, got %d'
1634 )
1634 )
1635 % (
1635 % (
1636 self.indexfile if self._inline else self.datafile,
1636 self.indexfile if self._inline else self.datafile,
1637 length,
1637 length,
1638 offset,
1638 offset,
1639 len(d),
1639 len(d),
1640 )
1640 )
1641 )
1641 )
1642
1642
1643 return d
1643 return d
1644
1644
1645 def _getsegment(self, offset, length, df=None):
1645 def _getsegment(self, offset, length, df=None):
1646 """Obtain a segment of raw data from the revlog.
1646 """Obtain a segment of raw data from the revlog.
1647
1647
1648 Accepts an absolute offset, length of bytes to obtain, and an
1648 Accepts an absolute offset, length of bytes to obtain, and an
1649 optional file handle to the already-opened revlog. If the file
1649 optional file handle to the already-opened revlog. If the file
1650 handle is used, it's original seek position will not be preserved.
1650 handle is used, it's original seek position will not be preserved.
1651
1651
1652 Requests for data may be returned from a cache.
1652 Requests for data may be returned from a cache.
1653
1653
1654 Returns a str or a buffer instance of raw byte data.
1654 Returns a str or a buffer instance of raw byte data.
1655 """
1655 """
1656 o, d = self._chunkcache
1656 o, d = self._chunkcache
1657 l = len(d)
1657 l = len(d)
1658
1658
1659 # is it in the cache?
1659 # is it in the cache?
1660 cachestart = offset - o
1660 cachestart = offset - o
1661 cacheend = cachestart + length
1661 cacheend = cachestart + length
1662 if cachestart >= 0 and cacheend <= l:
1662 if cachestart >= 0 and cacheend <= l:
1663 if cachestart == 0 and cacheend == l:
1663 if cachestart == 0 and cacheend == l:
1664 return d # avoid a copy
1664 return d # avoid a copy
1665 return util.buffer(d, cachestart, cacheend - cachestart)
1665 return util.buffer(d, cachestart, cacheend - cachestart)
1666
1666
1667 return self._readsegment(offset, length, df=df)
1667 return self._readsegment(offset, length, df=df)
1668
1668
1669 def _getsegmentforrevs(self, startrev, endrev, df=None):
1669 def _getsegmentforrevs(self, startrev, endrev, df=None):
1670 """Obtain a segment of raw data corresponding to a range of revisions.
1670 """Obtain a segment of raw data corresponding to a range of revisions.
1671
1671
1672 Accepts the start and end revisions and an optional already-open
1672 Accepts the start and end revisions and an optional already-open
1673 file handle to be used for reading. If the file handle is read, its
1673 file handle to be used for reading. If the file handle is read, its
1674 seek position will not be preserved.
1674 seek position will not be preserved.
1675
1675
1676 Requests for data may be satisfied by a cache.
1676 Requests for data may be satisfied by a cache.
1677
1677
1678 Returns a 2-tuple of (offset, data) for the requested range of
1678 Returns a 2-tuple of (offset, data) for the requested range of
1679 revisions. Offset is the integer offset from the beginning of the
1679 revisions. Offset is the integer offset from the beginning of the
1680 revlog and data is a str or buffer of the raw byte data.
1680 revlog and data is a str or buffer of the raw byte data.
1681
1681
1682 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1682 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1683 to determine where each revision's data begins and ends.
1683 to determine where each revision's data begins and ends.
1684 """
1684 """
1685 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1685 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1686 # (functions are expensive).
1686 # (functions are expensive).
1687 index = self.index
1687 index = self.index
1688 istart = index[startrev]
1688 istart = index[startrev]
1689 start = int(istart[0] >> 16)
1689 start = int(istart[0] >> 16)
1690 if startrev == endrev:
1690 if startrev == endrev:
1691 end = start + istart[1]
1691 end = start + istart[1]
1692 else:
1692 else:
1693 iend = index[endrev]
1693 iend = index[endrev]
1694 end = int(iend[0] >> 16) + iend[1]
1694 end = int(iend[0] >> 16) + iend[1]
1695
1695
1696 if self._inline:
1696 if self._inline:
1697 start += (startrev + 1) * self._io.size
1697 start += (startrev + 1) * self._io.size
1698 end += (endrev + 1) * self._io.size
1698 end += (endrev + 1) * self._io.size
1699 length = end - start
1699 length = end - start
1700
1700
1701 return start, self._getsegment(start, length, df=df)
1701 return start, self._getsegment(start, length, df=df)
1702
1702
1703 def _chunk(self, rev, df=None):
1703 def _chunk(self, rev, df=None):
1704 """Obtain a single decompressed chunk for a revision.
1704 """Obtain a single decompressed chunk for a revision.
1705
1705
1706 Accepts an integer revision and an optional already-open file handle
1706 Accepts an integer revision and an optional already-open file handle
1707 to be used for reading. If used, the seek position of the file will not
1707 to be used for reading. If used, the seek position of the file will not
1708 be preserved.
1708 be preserved.
1709
1709
1710 Returns a str holding uncompressed data for the requested revision.
1710 Returns a str holding uncompressed data for the requested revision.
1711 """
1711 """
1712 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1712 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1713
1713
1714 def _chunks(self, revs, df=None, targetsize=None):
1714 def _chunks(self, revs, df=None, targetsize=None):
1715 """Obtain decompressed chunks for the specified revisions.
1715 """Obtain decompressed chunks for the specified revisions.
1716
1716
1717 Accepts an iterable of numeric revisions that are assumed to be in
1717 Accepts an iterable of numeric revisions that are assumed to be in
1718 ascending order. Also accepts an optional already-open file handle
1718 ascending order. Also accepts an optional already-open file handle
1719 to be used for reading. If used, the seek position of the file will
1719 to be used for reading. If used, the seek position of the file will
1720 not be preserved.
1720 not be preserved.
1721
1721
1722 This function is similar to calling ``self._chunk()`` multiple times,
1722 This function is similar to calling ``self._chunk()`` multiple times,
1723 but is faster.
1723 but is faster.
1724
1724
1725 Returns a list with decompressed data for each requested revision.
1725 Returns a list with decompressed data for each requested revision.
1726 """
1726 """
1727 if not revs:
1727 if not revs:
1728 return []
1728 return []
1729 start = self.start
1729 start = self.start
1730 length = self.length
1730 length = self.length
1731 inline = self._inline
1731 inline = self._inline
1732 iosize = self._io.size
1732 iosize = self._io.size
1733 buffer = util.buffer
1733 buffer = util.buffer
1734
1734
1735 l = []
1735 l = []
1736 ladd = l.append
1736 ladd = l.append
1737
1737
1738 if not self._withsparseread:
1738 if not self._withsparseread:
1739 slicedchunks = (revs,)
1739 slicedchunks = (revs,)
1740 else:
1740 else:
1741 slicedchunks = deltautil.slicechunk(
1741 slicedchunks = deltautil.slicechunk(
1742 self, revs, targetsize=targetsize
1742 self, revs, targetsize=targetsize
1743 )
1743 )
1744
1744
1745 for revschunk in slicedchunks:
1745 for revschunk in slicedchunks:
1746 firstrev = revschunk[0]
1746 firstrev = revschunk[0]
1747 # Skip trailing revisions with empty diff
1747 # Skip trailing revisions with empty diff
1748 for lastrev in revschunk[::-1]:
1748 for lastrev in revschunk[::-1]:
1749 if length(lastrev) != 0:
1749 if length(lastrev) != 0:
1750 break
1750 break
1751
1751
1752 try:
1752 try:
1753 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1753 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1754 except OverflowError:
1754 except OverflowError:
1755 # issue4215 - we can't cache a run of chunks greater than
1755 # issue4215 - we can't cache a run of chunks greater than
1756 # 2G on Windows
1756 # 2G on Windows
1757 return [self._chunk(rev, df=df) for rev in revschunk]
1757 return [self._chunk(rev, df=df) for rev in revschunk]
1758
1758
1759 decomp = self.decompress
1759 decomp = self.decompress
1760 for rev in revschunk:
1760 for rev in revschunk:
1761 chunkstart = start(rev)
1761 chunkstart = start(rev)
1762 if inline:
1762 if inline:
1763 chunkstart += (rev + 1) * iosize
1763 chunkstart += (rev + 1) * iosize
1764 chunklength = length(rev)
1764 chunklength = length(rev)
1765 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1765 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1766
1766
1767 return l
1767 return l
1768
1768
1769 def _chunkclear(self):
1769 def _chunkclear(self):
1770 """Clear the raw chunk cache."""
1770 """Clear the raw chunk cache."""
1771 self._chunkcache = (0, b'')
1771 self._chunkcache = (0, b'')
1772
1772
1773 def deltaparent(self, rev):
1773 def deltaparent(self, rev):
1774 """return deltaparent of the given revision"""
1774 """return deltaparent of the given revision"""
1775 base = self.index[rev][3]
1775 base = self.index[rev][3]
1776 if base == rev:
1776 if base == rev:
1777 return nullrev
1777 return nullrev
1778 elif self._generaldelta:
1778 elif self._generaldelta:
1779 return base
1779 return base
1780 else:
1780 else:
1781 return rev - 1
1781 return rev - 1
1782
1782
1783 def issnapshot(self, rev):
1783 def issnapshot(self, rev):
1784 """tells whether rev is a snapshot"""
1784 """tells whether rev is a snapshot"""
1785 if not self._sparserevlog:
1785 if not self._sparserevlog:
1786 return self.deltaparent(rev) == nullrev
1786 return self.deltaparent(rev) == nullrev
1787 elif util.safehasattr(self.index, b'issnapshot'):
1787 elif util.safehasattr(self.index, b'issnapshot'):
1788 # directly assign the method to cache the testing and access
1788 # directly assign the method to cache the testing and access
1789 self.issnapshot = self.index.issnapshot
1789 self.issnapshot = self.index.issnapshot
1790 return self.issnapshot(rev)
1790 return self.issnapshot(rev)
1791 if rev == nullrev:
1791 if rev == nullrev:
1792 return True
1792 return True
1793 entry = self.index[rev]
1793 entry = self.index[rev]
1794 base = entry[3]
1794 base = entry[3]
1795 if base == rev:
1795 if base == rev:
1796 return True
1796 return True
1797 if base == nullrev:
1797 if base == nullrev:
1798 return True
1798 return True
1799 p1 = entry[5]
1799 p1 = entry[5]
1800 p2 = entry[6]
1800 p2 = entry[6]
1801 if base == p1 or base == p2:
1801 if base == p1 or base == p2:
1802 return False
1802 return False
1803 return self.issnapshot(base)
1803 return self.issnapshot(base)
1804
1804
1805 def snapshotdepth(self, rev):
1805 def snapshotdepth(self, rev):
1806 """number of snapshot in the chain before this one"""
1806 """number of snapshot in the chain before this one"""
1807 if not self.issnapshot(rev):
1807 if not self.issnapshot(rev):
1808 raise error.ProgrammingError(b'revision %d not a snapshot')
1808 raise error.ProgrammingError(b'revision %d not a snapshot')
1809 return len(self._deltachain(rev)[0]) - 1
1809 return len(self._deltachain(rev)[0]) - 1
1810
1810
1811 def revdiff(self, rev1, rev2):
1811 def revdiff(self, rev1, rev2):
1812 """return or calculate a delta between two revisions
1812 """return or calculate a delta between two revisions
1813
1813
1814 The delta calculated is in binary form and is intended to be written to
1814 The delta calculated is in binary form and is intended to be written to
1815 revlog data directly. So this function needs raw revision data.
1815 revlog data directly. So this function needs raw revision data.
1816 """
1816 """
1817 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1817 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1818 return bytes(self._chunk(rev2))
1818 return bytes(self._chunk(rev2))
1819
1819
1820 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1820 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1821
1821
1822 def _processflags(self, text, flags, operation, raw=False):
1822 def _processflags(self, text, flags, operation, raw=False):
1823 """deprecated entry point to access flag processors"""
1823 """deprecated entry point to access flag processors"""
1824 msg = b'_processflag(...) use the specialized variant'
1824 msg = b'_processflag(...) use the specialized variant'
1825 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1825 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1826 if raw:
1826 if raw:
1827 return text, flagutil.processflagsraw(self, text, flags)
1827 return text, flagutil.processflagsraw(self, text, flags)
1828 elif operation == b'read':
1828 elif operation == b'read':
1829 return flagutil.processflagsread(self, text, flags)
1829 return flagutil.processflagsread(self, text, flags)
1830 else: # write operation
1830 else: # write operation
1831 return flagutil.processflagswrite(self, text, flags, None)
1831 return flagutil.processflagswrite(self, text, flags, None)
1832
1832
1833 def revision(self, nodeorrev, _df=None, raw=False):
1833 def revision(self, nodeorrev, _df=None, raw=False):
1834 """return an uncompressed revision of a given node or revision
1834 """return an uncompressed revision of a given node or revision
1835 number.
1835 number.
1836
1836
1837 _df - an existing file handle to read from. (internal-only)
1837 _df - an existing file handle to read from. (internal-only)
1838 raw - an optional argument specifying if the revision data is to be
1838 raw - an optional argument specifying if the revision data is to be
1839 treated as raw data when applying flag transforms. 'raw' should be set
1839 treated as raw data when applying flag transforms. 'raw' should be set
1840 to True when generating changegroups or in debug commands.
1840 to True when generating changegroups or in debug commands.
1841 """
1841 """
1842 if raw:
1842 if raw:
1843 msg = (
1843 msg = (
1844 b'revlog.revision(..., raw=True) is deprecated, '
1844 b'revlog.revision(..., raw=True) is deprecated, '
1845 b'use revlog.rawdata(...)'
1845 b'use revlog.rawdata(...)'
1846 )
1846 )
1847 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1847 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1848 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1848 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1849
1849
1850 def sidedata(self, nodeorrev, _df=None):
1850 def sidedata(self, nodeorrev, _df=None):
1851 """a map of extra data related to the changeset but not part of the hash
1851 """a map of extra data related to the changeset but not part of the hash
1852
1852
1853 This function currently return a dictionary. However, more advanced
1853 This function currently return a dictionary. However, more advanced
1854 mapping object will likely be used in the future for a more
1854 mapping object will likely be used in the future for a more
1855 efficient/lazy code.
1855 efficient/lazy code.
1856 """
1856 """
1857 return self._revisiondata(nodeorrev, _df)[1]
1857 return self._revisiondata(nodeorrev, _df)[1]
1858
1858
1859 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1859 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1860 # deal with <nodeorrev> argument type
1860 # deal with <nodeorrev> argument type
1861 if isinstance(nodeorrev, int):
1861 if isinstance(nodeorrev, int):
1862 rev = nodeorrev
1862 rev = nodeorrev
1863 node = self.node(rev)
1863 node = self.node(rev)
1864 else:
1864 else:
1865 node = nodeorrev
1865 node = nodeorrev
1866 rev = None
1866 rev = None
1867
1867
1868 # fast path the special `nullid` rev
1868 # fast path the special `nullid` rev
1869 if node == nullid:
1869 if node == nullid:
1870 return b"", {}
1870 return b"", {}
1871
1871
1872 # ``rawtext`` is the text as stored inside the revlog. Might be the
1872 # ``rawtext`` is the text as stored inside the revlog. Might be the
1873 # revision or might need to be processed to retrieve the revision.
1873 # revision or might need to be processed to retrieve the revision.
1874 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1874 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1875
1875
1876 if raw and validated:
1876 if raw and validated:
1877 # if we don't want to process the raw text and that raw
1877 # if we don't want to process the raw text and that raw
1878 # text is cached, we can exit early.
1878 # text is cached, we can exit early.
1879 return rawtext, {}
1879 return rawtext, {}
1880 if rev is None:
1880 if rev is None:
1881 rev = self.rev(node)
1881 rev = self.rev(node)
1882 # the revlog's flag for this revision
1882 # the revlog's flag for this revision
1883 # (usually alter its state or content)
1883 # (usually alter its state or content)
1884 flags = self.flags(rev)
1884 flags = self.flags(rev)
1885
1885
1886 if validated and flags == REVIDX_DEFAULT_FLAGS:
1886 if validated and flags == REVIDX_DEFAULT_FLAGS:
1887 # no extra flags set, no flag processor runs, text = rawtext
1887 # no extra flags set, no flag processor runs, text = rawtext
1888 return rawtext, {}
1888 return rawtext, {}
1889
1889
1890 sidedata = {}
1890 sidedata = {}
1891 if raw:
1891 if raw:
1892 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1892 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1893 text = rawtext
1893 text = rawtext
1894 else:
1894 else:
1895 try:
1895 try:
1896 r = flagutil.processflagsread(self, rawtext, flags)
1896 r = flagutil.processflagsread(self, rawtext, flags)
1897 except error.SidedataHashError as exc:
1897 except error.SidedataHashError as exc:
1898 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1898 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1899 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1899 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1900 raise error.RevlogError(msg)
1900 raise error.RevlogError(msg)
1901 text, validatehash, sidedata = r
1901 text, validatehash, sidedata = r
1902 if validatehash:
1902 if validatehash:
1903 self.checkhash(text, node, rev=rev)
1903 self.checkhash(text, node, rev=rev)
1904 if not validated:
1904 if not validated:
1905 self._revisioncache = (node, rev, rawtext)
1905 self._revisioncache = (node, rev, rawtext)
1906
1906
1907 return text, sidedata
1907 return text, sidedata
1908
1908
1909 def _rawtext(self, node, rev, _df=None):
1909 def _rawtext(self, node, rev, _df=None):
1910 """return the possibly unvalidated rawtext for a revision
1910 """return the possibly unvalidated rawtext for a revision
1911
1911
1912 returns (rev, rawtext, validated)
1912 returns (rev, rawtext, validated)
1913 """
1913 """
1914
1914
1915 # revision in the cache (could be useful to apply delta)
1915 # revision in the cache (could be useful to apply delta)
1916 cachedrev = None
1916 cachedrev = None
1917 # An intermediate text to apply deltas to
1917 # An intermediate text to apply deltas to
1918 basetext = None
1918 basetext = None
1919
1919
1920 # Check if we have the entry in cache
1920 # Check if we have the entry in cache
1921 # The cache entry looks like (node, rev, rawtext)
1921 # The cache entry looks like (node, rev, rawtext)
1922 if self._revisioncache:
1922 if self._revisioncache:
1923 if self._revisioncache[0] == node:
1923 if self._revisioncache[0] == node:
1924 return (rev, self._revisioncache[2], True)
1924 return (rev, self._revisioncache[2], True)
1925 cachedrev = self._revisioncache[1]
1925 cachedrev = self._revisioncache[1]
1926
1926
1927 if rev is None:
1927 if rev is None:
1928 rev = self.rev(node)
1928 rev = self.rev(node)
1929
1929
1930 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1930 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1931 if stopped:
1931 if stopped:
1932 basetext = self._revisioncache[2]
1932 basetext = self._revisioncache[2]
1933
1933
1934 # drop cache to save memory, the caller is expected to
1934 # drop cache to save memory, the caller is expected to
1935 # update self._revisioncache after validating the text
1935 # update self._revisioncache after validating the text
1936 self._revisioncache = None
1936 self._revisioncache = None
1937
1937
1938 targetsize = None
1938 targetsize = None
1939 rawsize = self.index[rev][2]
1939 rawsize = self.index[rev][2]
1940 if 0 <= rawsize:
1940 if 0 <= rawsize:
1941 targetsize = 4 * rawsize
1941 targetsize = 4 * rawsize
1942
1942
1943 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1943 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1944 if basetext is None:
1944 if basetext is None:
1945 basetext = bytes(bins[0])
1945 basetext = bytes(bins[0])
1946 bins = bins[1:]
1946 bins = bins[1:]
1947
1947
1948 rawtext = mdiff.patches(basetext, bins)
1948 rawtext = mdiff.patches(basetext, bins)
1949 del basetext # let us have a chance to free memory early
1949 del basetext # let us have a chance to free memory early
1950 return (rev, rawtext, False)
1950 return (rev, rawtext, False)
1951
1951
1952 def rawdata(self, nodeorrev, _df=None):
1952 def rawdata(self, nodeorrev, _df=None):
1953 """return an uncompressed raw data of a given node or revision number.
1953 """return an uncompressed raw data of a given node or revision number.
1954
1954
1955 _df - an existing file handle to read from. (internal-only)
1955 _df - an existing file handle to read from. (internal-only)
1956 """
1956 """
1957 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1957 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1958
1958
1959 def hash(self, text, p1, p2):
1959 def hash(self, text, p1, p2):
1960 """Compute a node hash.
1960 """Compute a node hash.
1961
1961
1962 Available as a function so that subclasses can replace the hash
1962 Available as a function so that subclasses can replace the hash
1963 as needed.
1963 as needed.
1964 """
1964 """
1965 return storageutil.hashrevisionsha1(text, p1, p2)
1965 return storageutil.hashrevisionsha1(text, p1, p2)
1966
1966
1967 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1967 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1968 """Check node hash integrity.
1968 """Check node hash integrity.
1969
1969
1970 Available as a function so that subclasses can extend hash mismatch
1970 Available as a function so that subclasses can extend hash mismatch
1971 behaviors as needed.
1971 behaviors as needed.
1972 """
1972 """
1973 try:
1973 try:
1974 if p1 is None and p2 is None:
1974 if p1 is None and p2 is None:
1975 p1, p2 = self.parents(node)
1975 p1, p2 = self.parents(node)
1976 if node != self.hash(text, p1, p2):
1976 if node != self.hash(text, p1, p2):
1977 # Clear the revision cache on hash failure. The revision cache
1977 # Clear the revision cache on hash failure. The revision cache
1978 # only stores the raw revision and clearing the cache does have
1978 # only stores the raw revision and clearing the cache does have
1979 # the side-effect that we won't have a cache hit when the raw
1979 # the side-effect that we won't have a cache hit when the raw
1980 # revision data is accessed. But this case should be rare and
1980 # revision data is accessed. But this case should be rare and
1981 # it is extra work to teach the cache about the hash
1981 # it is extra work to teach the cache about the hash
1982 # verification state.
1982 # verification state.
1983 if self._revisioncache and self._revisioncache[0] == node:
1983 if self._revisioncache and self._revisioncache[0] == node:
1984 self._revisioncache = None
1984 self._revisioncache = None
1985
1985
1986 revornode = rev
1986 revornode = rev
1987 if revornode is None:
1987 if revornode is None:
1988 revornode = templatefilters.short(hex(node))
1988 revornode = templatefilters.short(hex(node))
1989 raise error.RevlogError(
1989 raise error.RevlogError(
1990 _(b"integrity check failed on %s:%s")
1990 _(b"integrity check failed on %s:%s")
1991 % (self.indexfile, pycompat.bytestr(revornode))
1991 % (self.indexfile, pycompat.bytestr(revornode))
1992 )
1992 )
1993 except error.RevlogError:
1993 except error.RevlogError:
1994 if self._censorable and storageutil.iscensoredtext(text):
1994 if self._censorable and storageutil.iscensoredtext(text):
1995 raise error.CensoredNodeError(self.indexfile, node, text)
1995 raise error.CensoredNodeError(self.indexfile, node, text)
1996 raise
1996 raise
1997
1997
1998 def _enforceinlinesize(self, tr, fp=None):
1998 def _enforceinlinesize(self, tr, fp=None):
1999 """Check if the revlog is too big for inline and convert if so.
1999 """Check if the revlog is too big for inline and convert if so.
2000
2000
2001 This should be called after revisions are added to the revlog. If the
2001 This should be called after revisions are added to the revlog. If the
2002 revlog has grown too large to be an inline revlog, it will convert it
2002 revlog has grown too large to be an inline revlog, it will convert it
2003 to use multiple index and data files.
2003 to use multiple index and data files.
2004 """
2004 """
2005 tiprev = len(self) - 1
2005 tiprev = len(self) - 1
2006 if (
2006 if (
2007 not self._inline
2007 not self._inline
2008 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
2008 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
2009 ):
2009 ):
2010 return
2010 return
2011
2011
2012 troffset = tr.findoffset(self.indexfile)
2012 troffset = tr.findoffset(self.indexfile)
2013 if troffset is None:
2013 if troffset is None:
2014 raise error.RevlogError(
2014 raise error.RevlogError(
2015 _(b"%s not found in the transaction") % self.indexfile
2015 _(b"%s not found in the transaction") % self.indexfile
2016 )
2016 )
2017 trindex = 0
2017 trindex = 0
2018 tr.add(self.datafile, 0)
2018 tr.add(self.datafile, 0)
2019
2019
2020 if fp:
2020 if fp:
2021 fp.flush()
2021 fp.flush()
2022 fp.close()
2022 fp.close()
2023 # We can't use the cached file handle after close(). So prevent
2023 # We can't use the cached file handle after close(). So prevent
2024 # its usage.
2024 # its usage.
2025 self._writinghandles = None
2025 self._writinghandles = None
2026
2026
2027 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2027 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2028 for r in self:
2028 for r in self:
2029 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2029 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2030 if troffset <= self.start(r):
2030 if troffset <= self.start(r):
2031 trindex = r
2031 trindex = r
2032
2032
2033 with self._indexfp(b'w') as fp:
2033 with self._indexfp(b'w') as fp:
2034 self.version &= ~FLAG_INLINE_DATA
2034 self.version &= ~FLAG_INLINE_DATA
2035 self._inline = False
2035 self._inline = False
2036 io = self._io
2036 io = self._io
2037 for i in self:
2037 for i in self:
2038 e = io.packentry(self.index[i], self.node, self.version, i)
2038 e = io.packentry(self.index[i], self.node, self.version, i)
2039 fp.write(e)
2039 fp.write(e)
2040
2040
2041 # the temp file replace the real index when we exit the context
2041 # the temp file replace the real index when we exit the context
2042 # manager
2042 # manager
2043
2043
2044 tr.replace(self.indexfile, trindex * self._io.size)
2044 tr.replace(self.indexfile, trindex * self._io.size)
2045 nodemaputil.setup_persistent_nodemap(tr, self)
2045 nodemaputil.setup_persistent_nodemap(tr, self)
2046 self._chunkclear()
2046 self._chunkclear()
2047
2047
2048 def _nodeduplicatecallback(self, transaction, node):
2048 def _nodeduplicatecallback(self, transaction, node):
2049 """called when trying to add a node already stored."""
2049 """called when trying to add a node already stored."""
2050
2050
2051 def addrevision(
2051 def addrevision(
2052 self,
2052 self,
2053 text,
2053 text,
2054 transaction,
2054 transaction,
2055 link,
2055 link,
2056 p1,
2056 p1,
2057 p2,
2057 p2,
2058 cachedelta=None,
2058 cachedelta=None,
2059 node=None,
2059 node=None,
2060 flags=REVIDX_DEFAULT_FLAGS,
2060 flags=REVIDX_DEFAULT_FLAGS,
2061 deltacomputer=None,
2061 deltacomputer=None,
2062 sidedata=None,
2062 sidedata=None,
2063 ):
2063 ):
2064 """add a revision to the log
2064 """add a revision to the log
2065
2065
2066 text - the revision data to add
2066 text - the revision data to add
2067 transaction - the transaction object used for rollback
2067 transaction - the transaction object used for rollback
2068 link - the linkrev data to add
2068 link - the linkrev data to add
2069 p1, p2 - the parent nodeids of the revision
2069 p1, p2 - the parent nodeids of the revision
2070 cachedelta - an optional precomputed delta
2070 cachedelta - an optional precomputed delta
2071 node - nodeid of revision; typically node is not specified, and it is
2071 node - nodeid of revision; typically node is not specified, and it is
2072 computed by default as hash(text, p1, p2), however subclasses might
2072 computed by default as hash(text, p1, p2), however subclasses might
2073 use different hashing method (and override checkhash() in such case)
2073 use different hashing method (and override checkhash() in such case)
2074 flags - the known flags to set on the revision
2074 flags - the known flags to set on the revision
2075 deltacomputer - an optional deltacomputer instance shared between
2075 deltacomputer - an optional deltacomputer instance shared between
2076 multiple calls
2076 multiple calls
2077 """
2077 """
2078 if link == nullrev:
2078 if link == nullrev:
2079 raise error.RevlogError(
2079 raise error.RevlogError(
2080 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2080 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2081 )
2081 )
2082
2082
2083 if sidedata is None:
2083 if sidedata is None:
2084 sidedata = {}
2084 sidedata = {}
2085 flags = flags & ~REVIDX_SIDEDATA
2085 flags = flags & ~REVIDX_SIDEDATA
2086 elif not self.hassidedata:
2086 elif not self.hassidedata:
2087 raise error.ProgrammingError(
2087 raise error.ProgrammingError(
2088 _(b"trying to add sidedata to a revlog who don't support them")
2088 _(b"trying to add sidedata to a revlog who don't support them")
2089 )
2089 )
2090 else:
2090 else:
2091 flags |= REVIDX_SIDEDATA
2091 flags |= REVIDX_SIDEDATA
2092
2092
2093 if flags:
2093 if flags:
2094 node = node or self.hash(text, p1, p2)
2094 node = node or self.hash(text, p1, p2)
2095
2095
2096 rawtext, validatehash = flagutil.processflagswrite(
2096 rawtext, validatehash = flagutil.processflagswrite(
2097 self, text, flags, sidedata=sidedata
2097 self, text, flags, sidedata=sidedata
2098 )
2098 )
2099
2099
2100 # If the flag processor modifies the revision data, ignore any provided
2100 # If the flag processor modifies the revision data, ignore any provided
2101 # cachedelta.
2101 # cachedelta.
2102 if rawtext != text:
2102 if rawtext != text:
2103 cachedelta = None
2103 cachedelta = None
2104
2104
2105 if len(rawtext) > _maxentrysize:
2105 if len(rawtext) > _maxentrysize:
2106 raise error.RevlogError(
2106 raise error.RevlogError(
2107 _(
2107 _(
2108 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2108 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2109 )
2109 )
2110 % (self.indexfile, len(rawtext))
2110 % (self.indexfile, len(rawtext))
2111 )
2111 )
2112
2112
2113 node = node or self.hash(rawtext, p1, p2)
2113 node = node or self.hash(rawtext, p1, p2)
2114 if self.index.has_node(node):
2114 if self.index.has_node(node):
2115 return node
2115 return node
2116
2116
2117 if validatehash:
2117 if validatehash:
2118 self.checkhash(rawtext, node, p1=p1, p2=p2)
2118 self.checkhash(rawtext, node, p1=p1, p2=p2)
2119
2119
2120 return self.addrawrevision(
2120 return self.addrawrevision(
2121 rawtext,
2121 rawtext,
2122 transaction,
2122 transaction,
2123 link,
2123 link,
2124 p1,
2124 p1,
2125 p2,
2125 p2,
2126 node,
2126 node,
2127 flags,
2127 flags,
2128 cachedelta=cachedelta,
2128 cachedelta=cachedelta,
2129 deltacomputer=deltacomputer,
2129 deltacomputer=deltacomputer,
2130 )
2130 )
2131
2131
2132 def addrawrevision(
2132 def addrawrevision(
2133 self,
2133 self,
2134 rawtext,
2134 rawtext,
2135 transaction,
2135 transaction,
2136 link,
2136 link,
2137 p1,
2137 p1,
2138 p2,
2138 p2,
2139 node,
2139 node,
2140 flags,
2140 flags,
2141 cachedelta=None,
2141 cachedelta=None,
2142 deltacomputer=None,
2142 deltacomputer=None,
2143 ):
2143 ):
2144 """add a raw revision with known flags, node and parents
2144 """add a raw revision with known flags, node and parents
2145 useful when reusing a revision not stored in this revlog (ex: received
2145 useful when reusing a revision not stored in this revlog (ex: received
2146 over wire, or read from an external bundle).
2146 over wire, or read from an external bundle).
2147 """
2147 """
2148 dfh = None
2148 dfh = None
2149 if not self._inline:
2149 if not self._inline:
2150 dfh = self._datafp(b"a+")
2150 dfh = self._datafp(b"a+")
2151 ifh = self._indexfp(b"a+")
2151 ifh = self._indexfp(b"a+")
2152 try:
2152 try:
2153 return self._addrevision(
2153 return self._addrevision(
2154 node,
2154 node,
2155 rawtext,
2155 rawtext,
2156 transaction,
2156 transaction,
2157 link,
2157 link,
2158 p1,
2158 p1,
2159 p2,
2159 p2,
2160 flags,
2160 flags,
2161 cachedelta,
2161 cachedelta,
2162 ifh,
2162 ifh,
2163 dfh,
2163 dfh,
2164 deltacomputer=deltacomputer,
2164 deltacomputer=deltacomputer,
2165 )
2165 )
2166 finally:
2166 finally:
2167 if dfh:
2167 if dfh:
2168 dfh.close()
2168 dfh.close()
2169 ifh.close()
2169 ifh.close()
2170
2170
2171 def compress(self, data):
2171 def compress(self, data):
2172 """Generate a possibly-compressed representation of data."""
2172 """Generate a possibly-compressed representation of data."""
2173 if not data:
2173 if not data:
2174 return b'', data
2174 return b'', data
2175
2175
2176 compressed = self._compressor.compress(data)
2176 compressed = self._compressor.compress(data)
2177
2177
2178 if compressed:
2178 if compressed:
2179 # The revlog compressor added the header in the returned data.
2179 # The revlog compressor added the header in the returned data.
2180 return b'', compressed
2180 return b'', compressed
2181
2181
2182 if data[0:1] == b'\0':
2182 if data[0:1] == b'\0':
2183 return b'', data
2183 return b'', data
2184 return b'u', data
2184 return b'u', data
2185
2185
2186 def decompress(self, data):
2186 def decompress(self, data):
2187 """Decompress a revlog chunk.
2187 """Decompress a revlog chunk.
2188
2188
2189 The chunk is expected to begin with a header identifying the
2189 The chunk is expected to begin with a header identifying the
2190 format type so it can be routed to an appropriate decompressor.
2190 format type so it can be routed to an appropriate decompressor.
2191 """
2191 """
2192 if not data:
2192 if not data:
2193 return data
2193 return data
2194
2194
2195 # Revlogs are read much more frequently than they are written and many
2195 # Revlogs are read much more frequently than they are written and many
2196 # chunks only take microseconds to decompress, so performance is
2196 # chunks only take microseconds to decompress, so performance is
2197 # important here.
2197 # important here.
2198 #
2198 #
2199 # We can make a few assumptions about revlogs:
2199 # We can make a few assumptions about revlogs:
2200 #
2200 #
2201 # 1) the majority of chunks will be compressed (as opposed to inline
2201 # 1) the majority of chunks will be compressed (as opposed to inline
2202 # raw data).
2202 # raw data).
2203 # 2) decompressing *any* data will likely by at least 10x slower than
2203 # 2) decompressing *any* data will likely by at least 10x slower than
2204 # returning raw inline data.
2204 # returning raw inline data.
2205 # 3) we want to prioritize common and officially supported compression
2205 # 3) we want to prioritize common and officially supported compression
2206 # engines
2206 # engines
2207 #
2207 #
2208 # It follows that we want to optimize for "decompress compressed data
2208 # It follows that we want to optimize for "decompress compressed data
2209 # when encoded with common and officially supported compression engines"
2209 # when encoded with common and officially supported compression engines"
2210 # case over "raw data" and "data encoded by less common or non-official
2210 # case over "raw data" and "data encoded by less common or non-official
2211 # compression engines." That is why we have the inline lookup first
2211 # compression engines." That is why we have the inline lookup first
2212 # followed by the compengines lookup.
2212 # followed by the compengines lookup.
2213 #
2213 #
2214 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2214 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2215 # compressed chunks. And this matters for changelog and manifest reads.
2215 # compressed chunks. And this matters for changelog and manifest reads.
2216 t = data[0:1]
2216 t = data[0:1]
2217
2217
2218 if t == b'x':
2218 if t == b'x':
2219 try:
2219 try:
2220 return _zlibdecompress(data)
2220 return _zlibdecompress(data)
2221 except zlib.error as e:
2221 except zlib.error as e:
2222 raise error.RevlogError(
2222 raise error.RevlogError(
2223 _(b'revlog decompress error: %s')
2223 _(b'revlog decompress error: %s')
2224 % stringutil.forcebytestr(e)
2224 % stringutil.forcebytestr(e)
2225 )
2225 )
2226 # '\0' is more common than 'u' so it goes first.
2226 # '\0' is more common than 'u' so it goes first.
2227 elif t == b'\0':
2227 elif t == b'\0':
2228 return data
2228 return data
2229 elif t == b'u':
2229 elif t == b'u':
2230 return util.buffer(data, 1)
2230 return util.buffer(data, 1)
2231
2231
2232 try:
2232 try:
2233 compressor = self._decompressors[t]
2233 compressor = self._decompressors[t]
2234 except KeyError:
2234 except KeyError:
2235 try:
2235 try:
2236 engine = util.compengines.forrevlogheader(t)
2236 engine = util.compengines.forrevlogheader(t)
2237 compressor = engine.revlogcompressor(self._compengineopts)
2237 compressor = engine.revlogcompressor(self._compengineopts)
2238 self._decompressors[t] = compressor
2238 self._decompressors[t] = compressor
2239 except KeyError:
2239 except KeyError:
2240 raise error.RevlogError(_(b'unknown compression type %r') % t)
2240 raise error.RevlogError(_(b'unknown compression type %r') % t)
2241
2241
2242 return compressor.decompress(data)
2242 return compressor.decompress(data)
2243
2243
2244 def _addrevision(
2244 def _addrevision(
2245 self,
2245 self,
2246 node,
2246 node,
2247 rawtext,
2247 rawtext,
2248 transaction,
2248 transaction,
2249 link,
2249 link,
2250 p1,
2250 p1,
2251 p2,
2251 p2,
2252 flags,
2252 flags,
2253 cachedelta,
2253 cachedelta,
2254 ifh,
2254 ifh,
2255 dfh,
2255 dfh,
2256 alwayscache=False,
2256 alwayscache=False,
2257 deltacomputer=None,
2257 deltacomputer=None,
2258 ):
2258 ):
2259 """internal function to add revisions to the log
2259 """internal function to add revisions to the log
2260
2260
2261 see addrevision for argument descriptions.
2261 see addrevision for argument descriptions.
2262
2262
2263 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2263 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2264
2264
2265 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2265 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2266 be used.
2266 be used.
2267
2267
2268 invariants:
2268 invariants:
2269 - rawtext is optional (can be None); if not set, cachedelta must be set.
2269 - rawtext is optional (can be None); if not set, cachedelta must be set.
2270 if both are set, they must correspond to each other.
2270 if both are set, they must correspond to each other.
2271 """
2271 """
2272 if node == nullid:
2272 if node == nullid:
2273 raise error.RevlogError(
2273 raise error.RevlogError(
2274 _(b"%s: attempt to add null revision") % self.indexfile
2274 _(b"%s: attempt to add null revision") % self.indexfile
2275 )
2275 )
2276 if node == wdirid or node in wdirfilenodeids:
2276 if node == wdirid or node in wdirfilenodeids:
2277 raise error.RevlogError(
2277 raise error.RevlogError(
2278 _(b"%s: attempt to add wdir revision") % self.indexfile
2278 _(b"%s: attempt to add wdir revision") % self.indexfile
2279 )
2279 )
2280
2280
2281 if self._inline:
2281 if self._inline:
2282 fh = ifh
2282 fh = ifh
2283 else:
2283 else:
2284 fh = dfh
2284 fh = dfh
2285
2285
2286 btext = [rawtext]
2286 btext = [rawtext]
2287
2287
2288 curr = len(self)
2288 curr = len(self)
2289 prev = curr - 1
2289 prev = curr - 1
2290 offset = self.end(prev)
2290 offset = self.end(prev)
2291 p1r, p2r = self.rev(p1), self.rev(p2)
2291 p1r, p2r = self.rev(p1), self.rev(p2)
2292
2292
2293 # full versions are inserted when the needed deltas
2293 # full versions are inserted when the needed deltas
2294 # become comparable to the uncompressed text
2294 # become comparable to the uncompressed text
2295 if rawtext is None:
2295 if rawtext is None:
2296 # need rawtext size, before changed by flag processors, which is
2296 # need rawtext size, before changed by flag processors, which is
2297 # the non-raw size. use revlog explicitly to avoid filelog's extra
2297 # the non-raw size. use revlog explicitly to avoid filelog's extra
2298 # logic that might remove metadata size.
2298 # logic that might remove metadata size.
2299 textlen = mdiff.patchedsize(
2299 textlen = mdiff.patchedsize(
2300 revlog.size(self, cachedelta[0]), cachedelta[1]
2300 revlog.size(self, cachedelta[0]), cachedelta[1]
2301 )
2301 )
2302 else:
2302 else:
2303 textlen = len(rawtext)
2303 textlen = len(rawtext)
2304
2304
2305 if deltacomputer is None:
2305 if deltacomputer is None:
2306 deltacomputer = deltautil.deltacomputer(self)
2306 deltacomputer = deltautil.deltacomputer(self)
2307
2307
2308 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2308 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2309
2309
2310 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2310 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2311
2311
2312 e = (
2312 e = (
2313 offset_type(offset, flags),
2313 offset_type(offset, flags),
2314 deltainfo.deltalen,
2314 deltainfo.deltalen,
2315 textlen,
2315 textlen,
2316 deltainfo.base,
2316 deltainfo.base,
2317 link,
2317 link,
2318 p1r,
2318 p1r,
2319 p2r,
2319 p2r,
2320 node,
2320 node,
2321 )
2321 )
2322 self.index.append(e)
2322 self.index.append(e)
2323
2323
2324 entry = self._io.packentry(e, self.node, self.version, curr)
2324 entry = self._io.packentry(e, self.node, self.version, curr)
2325 self._writeentry(
2325 self._writeentry(
2326 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2326 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2327 )
2327 )
2328
2328
2329 rawtext = btext[0]
2329 rawtext = btext[0]
2330
2330
2331 if alwayscache and rawtext is None:
2331 if alwayscache and rawtext is None:
2332 rawtext = deltacomputer.buildtext(revinfo, fh)
2332 rawtext = deltacomputer.buildtext(revinfo, fh)
2333
2333
2334 if type(rawtext) == bytes: # only accept immutable objects
2334 if type(rawtext) == bytes: # only accept immutable objects
2335 self._revisioncache = (node, curr, rawtext)
2335 self._revisioncache = (node, curr, rawtext)
2336 self._chainbasecache[curr] = deltainfo.chainbase
2336 self._chainbasecache[curr] = deltainfo.chainbase
2337 return node
2337 return node
2338
2338
2339 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2339 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2340 # Files opened in a+ mode have inconsistent behavior on various
2340 # Files opened in a+ mode have inconsistent behavior on various
2341 # platforms. Windows requires that a file positioning call be made
2341 # platforms. Windows requires that a file positioning call be made
2342 # when the file handle transitions between reads and writes. See
2342 # when the file handle transitions between reads and writes. See
2343 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2343 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2344 # platforms, Python or the platform itself can be buggy. Some versions
2344 # platforms, Python or the platform itself can be buggy. Some versions
2345 # of Solaris have been observed to not append at the end of the file
2345 # of Solaris have been observed to not append at the end of the file
2346 # if the file was seeked to before the end. See issue4943 for more.
2346 # if the file was seeked to before the end. See issue4943 for more.
2347 #
2347 #
2348 # We work around this issue by inserting a seek() before writing.
2348 # We work around this issue by inserting a seek() before writing.
2349 # Note: This is likely not necessary on Python 3. However, because
2349 # Note: This is likely not necessary on Python 3. However, because
2350 # the file handle is reused for reads and may be seeked there, we need
2350 # the file handle is reused for reads and may be seeked there, we need
2351 # to be careful before changing this.
2351 # to be careful before changing this.
2352 ifh.seek(0, os.SEEK_END)
2352 ifh.seek(0, os.SEEK_END)
2353 if dfh:
2353 if dfh:
2354 dfh.seek(0, os.SEEK_END)
2354 dfh.seek(0, os.SEEK_END)
2355
2355
2356 curr = len(self) - 1
2356 curr = len(self) - 1
2357 if not self._inline:
2357 if not self._inline:
2358 transaction.add(self.datafile, offset)
2358 transaction.add(self.datafile, offset)
2359 transaction.add(self.indexfile, curr * len(entry))
2359 transaction.add(self.indexfile, curr * len(entry))
2360 if data[0]:
2360 if data[0]:
2361 dfh.write(data[0])
2361 dfh.write(data[0])
2362 dfh.write(data[1])
2362 dfh.write(data[1])
2363 ifh.write(entry)
2363 ifh.write(entry)
2364 else:
2364 else:
2365 offset += curr * self._io.size
2365 offset += curr * self._io.size
2366 transaction.add(self.indexfile, offset)
2366 transaction.add(self.indexfile, offset)
2367 ifh.write(entry)
2367 ifh.write(entry)
2368 ifh.write(data[0])
2368 ifh.write(data[0])
2369 ifh.write(data[1])
2369 ifh.write(data[1])
2370 self._enforceinlinesize(transaction, ifh)
2370 self._enforceinlinesize(transaction, ifh)
2371 nodemaputil.setup_persistent_nodemap(transaction, self)
2371 nodemaputil.setup_persistent_nodemap(transaction, self)
2372
2372
2373 def addgroup(
2373 def addgroup(
2374 self,
2374 self,
2375 deltas,
2375 deltas,
2376 linkmapper,
2376 linkmapper,
2377 transaction,
2377 transaction,
2378 alwayscache=False,
2378 addrevisioncb=None,
2379 addrevisioncb=None,
2379 duplicaterevisioncb=None,
2380 duplicaterevisioncb=None,
2380 ):
2381 ):
2381 """
2382 """
2382 add a delta group
2383 add a delta group
2383
2384
2384 given a set of deltas, add them to the revision log. the
2385 given a set of deltas, add them to the revision log. the
2385 first delta is against its parent, which should be in our
2386 first delta is against its parent, which should be in our
2386 log, the rest are against the previous delta.
2387 log, the rest are against the previous delta.
2387
2388
2388 If ``addrevisioncb`` is defined, it will be called with arguments of
2389 If ``addrevisioncb`` is defined, it will be called with arguments of
2389 this revlog and the node that was added.
2390 this revlog and the node that was added.
2390 """
2391 """
2391
2392
2392 if self._writinghandles:
2393 if self._writinghandles:
2393 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2394 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2394
2395
2395 r = len(self)
2396 r = len(self)
2396 end = 0
2397 end = 0
2397 if r:
2398 if r:
2398 end = self.end(r - 1)
2399 end = self.end(r - 1)
2399 ifh = self._indexfp(b"a+")
2400 ifh = self._indexfp(b"a+")
2400 isize = r * self._io.size
2401 isize = r * self._io.size
2401 if self._inline:
2402 if self._inline:
2402 transaction.add(self.indexfile, end + isize)
2403 transaction.add(self.indexfile, end + isize)
2403 dfh = None
2404 dfh = None
2404 else:
2405 else:
2405 transaction.add(self.indexfile, isize)
2406 transaction.add(self.indexfile, isize)
2406 transaction.add(self.datafile, end)
2407 transaction.add(self.datafile, end)
2407 dfh = self._datafp(b"a+")
2408 dfh = self._datafp(b"a+")
2408
2409
2409 def flush():
2410 def flush():
2410 if dfh:
2411 if dfh:
2411 dfh.flush()
2412 dfh.flush()
2412 ifh.flush()
2413 ifh.flush()
2413
2414
2414 self._writinghandles = (ifh, dfh)
2415 self._writinghandles = (ifh, dfh)
2415 empty = True
2416 empty = True
2416
2417
2417 try:
2418 try:
2418 deltacomputer = deltautil.deltacomputer(self)
2419 deltacomputer = deltautil.deltacomputer(self)
2419 # loop through our set of deltas
2420 # loop through our set of deltas
2420 for data in deltas:
2421 for data in deltas:
2421 node, p1, p2, linknode, deltabase, delta, flags = data
2422 node, p1, p2, linknode, deltabase, delta, flags = data
2422 link = linkmapper(linknode)
2423 link = linkmapper(linknode)
2423 flags = flags or REVIDX_DEFAULT_FLAGS
2424 flags = flags or REVIDX_DEFAULT_FLAGS
2424
2425
2425 if self.index.has_node(node):
2426 if self.index.has_node(node):
2426 # this can happen if two branches make the same change
2427 # this can happen if two branches make the same change
2427 self._nodeduplicatecallback(transaction, node)
2428 self._nodeduplicatecallback(transaction, node)
2428 if duplicaterevisioncb:
2429 if duplicaterevisioncb:
2429 duplicaterevisioncb(self, node)
2430 duplicaterevisioncb(self, node)
2430 empty = False
2431 empty = False
2431 continue
2432 continue
2432
2433
2433 for p in (p1, p2):
2434 for p in (p1, p2):
2434 if not self.index.has_node(p):
2435 if not self.index.has_node(p):
2435 raise error.LookupError(
2436 raise error.LookupError(
2436 p, self.indexfile, _(b'unknown parent')
2437 p, self.indexfile, _(b'unknown parent')
2437 )
2438 )
2438
2439
2439 if not self.index.has_node(deltabase):
2440 if not self.index.has_node(deltabase):
2440 raise error.LookupError(
2441 raise error.LookupError(
2441 deltabase, self.indexfile, _(b'unknown delta base')
2442 deltabase, self.indexfile, _(b'unknown delta base')
2442 )
2443 )
2443
2444
2444 baserev = self.rev(deltabase)
2445 baserev = self.rev(deltabase)
2445
2446
2446 if baserev != nullrev and self.iscensored(baserev):
2447 if baserev != nullrev and self.iscensored(baserev):
2447 # if base is censored, delta must be full replacement in a
2448 # if base is censored, delta must be full replacement in a
2448 # single patch operation
2449 # single patch operation
2449 hlen = struct.calcsize(b">lll")
2450 hlen = struct.calcsize(b">lll")
2450 oldlen = self.rawsize(baserev)
2451 oldlen = self.rawsize(baserev)
2451 newlen = len(delta) - hlen
2452 newlen = len(delta) - hlen
2452 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2453 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2453 raise error.CensoredBaseError(
2454 raise error.CensoredBaseError(
2454 self.indexfile, self.node(baserev)
2455 self.indexfile, self.node(baserev)
2455 )
2456 )
2456
2457
2457 if not flags and self._peek_iscensored(baserev, delta, flush):
2458 if not flags and self._peek_iscensored(baserev, delta, flush):
2458 flags |= REVIDX_ISCENSORED
2459 flags |= REVIDX_ISCENSORED
2459
2460
2460 # We assume consumers of addrevisioncb will want to retrieve
2461 # We assume consumers of addrevisioncb will want to retrieve
2461 # the added revision, which will require a call to
2462 # the added revision, which will require a call to
2462 # revision(). revision() will fast path if there is a cache
2463 # revision(). revision() will fast path if there is a cache
2463 # hit. So, we tell _addrevision() to always cache in this case.
2464 # hit. So, we tell _addrevision() to always cache in this case.
2464 # We're only using addgroup() in the context of changegroup
2465 # We're only using addgroup() in the context of changegroup
2465 # generation so the revision data can always be handled as raw
2466 # generation so the revision data can always be handled as raw
2466 # by the flagprocessor.
2467 # by the flagprocessor.
2467 self._addrevision(
2468 self._addrevision(
2468 node,
2469 node,
2469 None,
2470 None,
2470 transaction,
2471 transaction,
2471 link,
2472 link,
2472 p1,
2473 p1,
2473 p2,
2474 p2,
2474 flags,
2475 flags,
2475 (baserev, delta),
2476 (baserev, delta),
2476 ifh,
2477 ifh,
2477 dfh,
2478 dfh,
2478 alwayscache=bool(addrevisioncb),
2479 alwayscache=alwayscache,
2479 deltacomputer=deltacomputer,
2480 deltacomputer=deltacomputer,
2480 )
2481 )
2481
2482
2482 if addrevisioncb:
2483 if addrevisioncb:
2483 addrevisioncb(self, node)
2484 addrevisioncb(self, node)
2484 empty = False
2485 empty = False
2485
2486
2486 if not dfh and not self._inline:
2487 if not dfh and not self._inline:
2487 # addrevision switched from inline to conventional
2488 # addrevision switched from inline to conventional
2488 # reopen the index
2489 # reopen the index
2489 ifh.close()
2490 ifh.close()
2490 dfh = self._datafp(b"a+")
2491 dfh = self._datafp(b"a+")
2491 ifh = self._indexfp(b"a+")
2492 ifh = self._indexfp(b"a+")
2492 self._writinghandles = (ifh, dfh)
2493 self._writinghandles = (ifh, dfh)
2493 finally:
2494 finally:
2494 self._writinghandles = None
2495 self._writinghandles = None
2495
2496
2496 if dfh:
2497 if dfh:
2497 dfh.close()
2498 dfh.close()
2498 ifh.close()
2499 ifh.close()
2499 return not empty
2500 return not empty
2500
2501
2501 def iscensored(self, rev):
2502 def iscensored(self, rev):
2502 """Check if a file revision is censored."""
2503 """Check if a file revision is censored."""
2503 if not self._censorable:
2504 if not self._censorable:
2504 return False
2505 return False
2505
2506
2506 return self.flags(rev) & REVIDX_ISCENSORED
2507 return self.flags(rev) & REVIDX_ISCENSORED
2507
2508
2508 def _peek_iscensored(self, baserev, delta, flush):
2509 def _peek_iscensored(self, baserev, delta, flush):
2509 """Quickly check if a delta produces a censored revision."""
2510 """Quickly check if a delta produces a censored revision."""
2510 if not self._censorable:
2511 if not self._censorable:
2511 return False
2512 return False
2512
2513
2513 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2514 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2514
2515
2515 def getstrippoint(self, minlink):
2516 def getstrippoint(self, minlink):
2516 """find the minimum rev that must be stripped to strip the linkrev
2517 """find the minimum rev that must be stripped to strip the linkrev
2517
2518
2518 Returns a tuple containing the minimum rev and a set of all revs that
2519 Returns a tuple containing the minimum rev and a set of all revs that
2519 have linkrevs that will be broken by this strip.
2520 have linkrevs that will be broken by this strip.
2520 """
2521 """
2521 return storageutil.resolvestripinfo(
2522 return storageutil.resolvestripinfo(
2522 minlink,
2523 minlink,
2523 len(self) - 1,
2524 len(self) - 1,
2524 self.headrevs(),
2525 self.headrevs(),
2525 self.linkrev,
2526 self.linkrev,
2526 self.parentrevs,
2527 self.parentrevs,
2527 )
2528 )
2528
2529
2529 def strip(self, minlink, transaction):
2530 def strip(self, minlink, transaction):
2530 """truncate the revlog on the first revision with a linkrev >= minlink
2531 """truncate the revlog on the first revision with a linkrev >= minlink
2531
2532
2532 This function is called when we're stripping revision minlink and
2533 This function is called when we're stripping revision minlink and
2533 its descendants from the repository.
2534 its descendants from the repository.
2534
2535
2535 We have to remove all revisions with linkrev >= minlink, because
2536 We have to remove all revisions with linkrev >= minlink, because
2536 the equivalent changelog revisions will be renumbered after the
2537 the equivalent changelog revisions will be renumbered after the
2537 strip.
2538 strip.
2538
2539
2539 So we truncate the revlog on the first of these revisions, and
2540 So we truncate the revlog on the first of these revisions, and
2540 trust that the caller has saved the revisions that shouldn't be
2541 trust that the caller has saved the revisions that shouldn't be
2541 removed and that it'll re-add them after this truncation.
2542 removed and that it'll re-add them after this truncation.
2542 """
2543 """
2543 if len(self) == 0:
2544 if len(self) == 0:
2544 return
2545 return
2545
2546
2546 rev, _ = self.getstrippoint(minlink)
2547 rev, _ = self.getstrippoint(minlink)
2547 if rev == len(self):
2548 if rev == len(self):
2548 return
2549 return
2549
2550
2550 # first truncate the files on disk
2551 # first truncate the files on disk
2551 end = self.start(rev)
2552 end = self.start(rev)
2552 if not self._inline:
2553 if not self._inline:
2553 transaction.add(self.datafile, end)
2554 transaction.add(self.datafile, end)
2554 end = rev * self._io.size
2555 end = rev * self._io.size
2555 else:
2556 else:
2556 end += rev * self._io.size
2557 end += rev * self._io.size
2557
2558
2558 transaction.add(self.indexfile, end)
2559 transaction.add(self.indexfile, end)
2559
2560
2560 # then reset internal state in memory to forget those revisions
2561 # then reset internal state in memory to forget those revisions
2561 self._revisioncache = None
2562 self._revisioncache = None
2562 self._chaininfocache = util.lrucachedict(500)
2563 self._chaininfocache = util.lrucachedict(500)
2563 self._chunkclear()
2564 self._chunkclear()
2564
2565
2565 del self.index[rev:-1]
2566 del self.index[rev:-1]
2566
2567
2567 def checksize(self):
2568 def checksize(self):
2568 """Check size of index and data files
2569 """Check size of index and data files
2569
2570
2570 return a (dd, di) tuple.
2571 return a (dd, di) tuple.
2571 - dd: extra bytes for the "data" file
2572 - dd: extra bytes for the "data" file
2572 - di: extra bytes for the "index" file
2573 - di: extra bytes for the "index" file
2573
2574
2574 A healthy revlog will return (0, 0).
2575 A healthy revlog will return (0, 0).
2575 """
2576 """
2576 expected = 0
2577 expected = 0
2577 if len(self):
2578 if len(self):
2578 expected = max(0, self.end(len(self) - 1))
2579 expected = max(0, self.end(len(self) - 1))
2579
2580
2580 try:
2581 try:
2581 with self._datafp() as f:
2582 with self._datafp() as f:
2582 f.seek(0, io.SEEK_END)
2583 f.seek(0, io.SEEK_END)
2583 actual = f.tell()
2584 actual = f.tell()
2584 dd = actual - expected
2585 dd = actual - expected
2585 except IOError as inst:
2586 except IOError as inst:
2586 if inst.errno != errno.ENOENT:
2587 if inst.errno != errno.ENOENT:
2587 raise
2588 raise
2588 dd = 0
2589 dd = 0
2589
2590
2590 try:
2591 try:
2591 f = self.opener(self.indexfile)
2592 f = self.opener(self.indexfile)
2592 f.seek(0, io.SEEK_END)
2593 f.seek(0, io.SEEK_END)
2593 actual = f.tell()
2594 actual = f.tell()
2594 f.close()
2595 f.close()
2595 s = self._io.size
2596 s = self._io.size
2596 i = max(0, actual // s)
2597 i = max(0, actual // s)
2597 di = actual - (i * s)
2598 di = actual - (i * s)
2598 if self._inline:
2599 if self._inline:
2599 databytes = 0
2600 databytes = 0
2600 for r in self:
2601 for r in self:
2601 databytes += max(0, self.length(r))
2602 databytes += max(0, self.length(r))
2602 dd = 0
2603 dd = 0
2603 di = actual - len(self) * s - databytes
2604 di = actual - len(self) * s - databytes
2604 except IOError as inst:
2605 except IOError as inst:
2605 if inst.errno != errno.ENOENT:
2606 if inst.errno != errno.ENOENT:
2606 raise
2607 raise
2607 di = 0
2608 di = 0
2608
2609
2609 return (dd, di)
2610 return (dd, di)
2610
2611
2611 def files(self):
2612 def files(self):
2612 res = [self.indexfile]
2613 res = [self.indexfile]
2613 if not self._inline:
2614 if not self._inline:
2614 res.append(self.datafile)
2615 res.append(self.datafile)
2615 return res
2616 return res
2616
2617
2617 def emitrevisions(
2618 def emitrevisions(
2618 self,
2619 self,
2619 nodes,
2620 nodes,
2620 nodesorder=None,
2621 nodesorder=None,
2621 revisiondata=False,
2622 revisiondata=False,
2622 assumehaveparentrevisions=False,
2623 assumehaveparentrevisions=False,
2623 deltamode=repository.CG_DELTAMODE_STD,
2624 deltamode=repository.CG_DELTAMODE_STD,
2624 ):
2625 ):
2625 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2626 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2626 raise error.ProgrammingError(
2627 raise error.ProgrammingError(
2627 b'unhandled value for nodesorder: %s' % nodesorder
2628 b'unhandled value for nodesorder: %s' % nodesorder
2628 )
2629 )
2629
2630
2630 if nodesorder is None and not self._generaldelta:
2631 if nodesorder is None and not self._generaldelta:
2631 nodesorder = b'storage'
2632 nodesorder = b'storage'
2632
2633
2633 if (
2634 if (
2634 not self._storedeltachains
2635 not self._storedeltachains
2635 and deltamode != repository.CG_DELTAMODE_PREV
2636 and deltamode != repository.CG_DELTAMODE_PREV
2636 ):
2637 ):
2637 deltamode = repository.CG_DELTAMODE_FULL
2638 deltamode = repository.CG_DELTAMODE_FULL
2638
2639
2639 return storageutil.emitrevisions(
2640 return storageutil.emitrevisions(
2640 self,
2641 self,
2641 nodes,
2642 nodes,
2642 nodesorder,
2643 nodesorder,
2643 revlogrevisiondelta,
2644 revlogrevisiondelta,
2644 deltaparentfn=self.deltaparent,
2645 deltaparentfn=self.deltaparent,
2645 candeltafn=self.candelta,
2646 candeltafn=self.candelta,
2646 rawsizefn=self.rawsize,
2647 rawsizefn=self.rawsize,
2647 revdifffn=self.revdiff,
2648 revdifffn=self.revdiff,
2648 flagsfn=self.flags,
2649 flagsfn=self.flags,
2649 deltamode=deltamode,
2650 deltamode=deltamode,
2650 revisiondata=revisiondata,
2651 revisiondata=revisiondata,
2651 assumehaveparentrevisions=assumehaveparentrevisions,
2652 assumehaveparentrevisions=assumehaveparentrevisions,
2652 )
2653 )
2653
2654
2654 DELTAREUSEALWAYS = b'always'
2655 DELTAREUSEALWAYS = b'always'
2655 DELTAREUSESAMEREVS = b'samerevs'
2656 DELTAREUSESAMEREVS = b'samerevs'
2656 DELTAREUSENEVER = b'never'
2657 DELTAREUSENEVER = b'never'
2657
2658
2658 DELTAREUSEFULLADD = b'fulladd'
2659 DELTAREUSEFULLADD = b'fulladd'
2659
2660
2660 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2661 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2661
2662
2662 def clone(
2663 def clone(
2663 self,
2664 self,
2664 tr,
2665 tr,
2665 destrevlog,
2666 destrevlog,
2666 addrevisioncb=None,
2667 addrevisioncb=None,
2667 deltareuse=DELTAREUSESAMEREVS,
2668 deltareuse=DELTAREUSESAMEREVS,
2668 forcedeltabothparents=None,
2669 forcedeltabothparents=None,
2669 sidedatacompanion=None,
2670 sidedatacompanion=None,
2670 ):
2671 ):
2671 """Copy this revlog to another, possibly with format changes.
2672 """Copy this revlog to another, possibly with format changes.
2672
2673
2673 The destination revlog will contain the same revisions and nodes.
2674 The destination revlog will contain the same revisions and nodes.
2674 However, it may not be bit-for-bit identical due to e.g. delta encoding
2675 However, it may not be bit-for-bit identical due to e.g. delta encoding
2675 differences.
2676 differences.
2676
2677
2677 The ``deltareuse`` argument control how deltas from the existing revlog
2678 The ``deltareuse`` argument control how deltas from the existing revlog
2678 are preserved in the destination revlog. The argument can have the
2679 are preserved in the destination revlog. The argument can have the
2679 following values:
2680 following values:
2680
2681
2681 DELTAREUSEALWAYS
2682 DELTAREUSEALWAYS
2682 Deltas will always be reused (if possible), even if the destination
2683 Deltas will always be reused (if possible), even if the destination
2683 revlog would not select the same revisions for the delta. This is the
2684 revlog would not select the same revisions for the delta. This is the
2684 fastest mode of operation.
2685 fastest mode of operation.
2685 DELTAREUSESAMEREVS
2686 DELTAREUSESAMEREVS
2686 Deltas will be reused if the destination revlog would pick the same
2687 Deltas will be reused if the destination revlog would pick the same
2687 revisions for the delta. This mode strikes a balance between speed
2688 revisions for the delta. This mode strikes a balance between speed
2688 and optimization.
2689 and optimization.
2689 DELTAREUSENEVER
2690 DELTAREUSENEVER
2690 Deltas will never be reused. This is the slowest mode of execution.
2691 Deltas will never be reused. This is the slowest mode of execution.
2691 This mode can be used to recompute deltas (e.g. if the diff/delta
2692 This mode can be used to recompute deltas (e.g. if the diff/delta
2692 algorithm changes).
2693 algorithm changes).
2693 DELTAREUSEFULLADD
2694 DELTAREUSEFULLADD
2694 Revision will be re-added as if their were new content. This is
2695 Revision will be re-added as if their were new content. This is
2695 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2696 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2696 eg: large file detection and handling.
2697 eg: large file detection and handling.
2697
2698
2698 Delta computation can be slow, so the choice of delta reuse policy can
2699 Delta computation can be slow, so the choice of delta reuse policy can
2699 significantly affect run time.
2700 significantly affect run time.
2700
2701
2701 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2702 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2702 two extremes. Deltas will be reused if they are appropriate. But if the
2703 two extremes. Deltas will be reused if they are appropriate. But if the
2703 delta could choose a better revision, it will do so. This means if you
2704 delta could choose a better revision, it will do so. This means if you
2704 are converting a non-generaldelta revlog to a generaldelta revlog,
2705 are converting a non-generaldelta revlog to a generaldelta revlog,
2705 deltas will be recomputed if the delta's parent isn't a parent of the
2706 deltas will be recomputed if the delta's parent isn't a parent of the
2706 revision.
2707 revision.
2707
2708
2708 In addition to the delta policy, the ``forcedeltabothparents``
2709 In addition to the delta policy, the ``forcedeltabothparents``
2709 argument controls whether to force compute deltas against both parents
2710 argument controls whether to force compute deltas against both parents
2710 for merges. By default, the current default is used.
2711 for merges. By default, the current default is used.
2711
2712
2712 If not None, the `sidedatacompanion` is callable that accept two
2713 If not None, the `sidedatacompanion` is callable that accept two
2713 arguments:
2714 arguments:
2714
2715
2715 (srcrevlog, rev)
2716 (srcrevlog, rev)
2716
2717
2717 and return a quintet that control changes to sidedata content from the
2718 and return a quintet that control changes to sidedata content from the
2718 old revision to the new clone result:
2719 old revision to the new clone result:
2719
2720
2720 (dropall, filterout, update, new_flags, dropped_flags)
2721 (dropall, filterout, update, new_flags, dropped_flags)
2721
2722
2722 * if `dropall` is True, all sidedata should be dropped
2723 * if `dropall` is True, all sidedata should be dropped
2723 * `filterout` is a set of sidedata keys that should be dropped
2724 * `filterout` is a set of sidedata keys that should be dropped
2724 * `update` is a mapping of additionnal/new key -> value
2725 * `update` is a mapping of additionnal/new key -> value
2725 * new_flags is a bitfields of new flags that the revision should get
2726 * new_flags is a bitfields of new flags that the revision should get
2726 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2727 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2727 """
2728 """
2728 if deltareuse not in self.DELTAREUSEALL:
2729 if deltareuse not in self.DELTAREUSEALL:
2729 raise ValueError(
2730 raise ValueError(
2730 _(b'value for deltareuse invalid: %s') % deltareuse
2731 _(b'value for deltareuse invalid: %s') % deltareuse
2731 )
2732 )
2732
2733
2733 if len(destrevlog):
2734 if len(destrevlog):
2734 raise ValueError(_(b'destination revlog is not empty'))
2735 raise ValueError(_(b'destination revlog is not empty'))
2735
2736
2736 if getattr(self, 'filteredrevs', None):
2737 if getattr(self, 'filteredrevs', None):
2737 raise ValueError(_(b'source revlog has filtered revisions'))
2738 raise ValueError(_(b'source revlog has filtered revisions'))
2738 if getattr(destrevlog, 'filteredrevs', None):
2739 if getattr(destrevlog, 'filteredrevs', None):
2739 raise ValueError(_(b'destination revlog has filtered revisions'))
2740 raise ValueError(_(b'destination revlog has filtered revisions'))
2740
2741
2741 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2742 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2742 # if possible.
2743 # if possible.
2743 oldlazydelta = destrevlog._lazydelta
2744 oldlazydelta = destrevlog._lazydelta
2744 oldlazydeltabase = destrevlog._lazydeltabase
2745 oldlazydeltabase = destrevlog._lazydeltabase
2745 oldamd = destrevlog._deltabothparents
2746 oldamd = destrevlog._deltabothparents
2746
2747
2747 try:
2748 try:
2748 if deltareuse == self.DELTAREUSEALWAYS:
2749 if deltareuse == self.DELTAREUSEALWAYS:
2749 destrevlog._lazydeltabase = True
2750 destrevlog._lazydeltabase = True
2750 destrevlog._lazydelta = True
2751 destrevlog._lazydelta = True
2751 elif deltareuse == self.DELTAREUSESAMEREVS:
2752 elif deltareuse == self.DELTAREUSESAMEREVS:
2752 destrevlog._lazydeltabase = False
2753 destrevlog._lazydeltabase = False
2753 destrevlog._lazydelta = True
2754 destrevlog._lazydelta = True
2754 elif deltareuse == self.DELTAREUSENEVER:
2755 elif deltareuse == self.DELTAREUSENEVER:
2755 destrevlog._lazydeltabase = False
2756 destrevlog._lazydeltabase = False
2756 destrevlog._lazydelta = False
2757 destrevlog._lazydelta = False
2757
2758
2758 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2759 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2759
2760
2760 self._clone(
2761 self._clone(
2761 tr,
2762 tr,
2762 destrevlog,
2763 destrevlog,
2763 addrevisioncb,
2764 addrevisioncb,
2764 deltareuse,
2765 deltareuse,
2765 forcedeltabothparents,
2766 forcedeltabothparents,
2766 sidedatacompanion,
2767 sidedatacompanion,
2767 )
2768 )
2768
2769
2769 finally:
2770 finally:
2770 destrevlog._lazydelta = oldlazydelta
2771 destrevlog._lazydelta = oldlazydelta
2771 destrevlog._lazydeltabase = oldlazydeltabase
2772 destrevlog._lazydeltabase = oldlazydeltabase
2772 destrevlog._deltabothparents = oldamd
2773 destrevlog._deltabothparents = oldamd
2773
2774
2774 def _clone(
2775 def _clone(
2775 self,
2776 self,
2776 tr,
2777 tr,
2777 destrevlog,
2778 destrevlog,
2778 addrevisioncb,
2779 addrevisioncb,
2779 deltareuse,
2780 deltareuse,
2780 forcedeltabothparents,
2781 forcedeltabothparents,
2781 sidedatacompanion,
2782 sidedatacompanion,
2782 ):
2783 ):
2783 """perform the core duty of `revlog.clone` after parameter processing"""
2784 """perform the core duty of `revlog.clone` after parameter processing"""
2784 deltacomputer = deltautil.deltacomputer(destrevlog)
2785 deltacomputer = deltautil.deltacomputer(destrevlog)
2785 index = self.index
2786 index = self.index
2786 for rev in self:
2787 for rev in self:
2787 entry = index[rev]
2788 entry = index[rev]
2788
2789
2789 # Some classes override linkrev to take filtered revs into
2790 # Some classes override linkrev to take filtered revs into
2790 # account. Use raw entry from index.
2791 # account. Use raw entry from index.
2791 flags = entry[0] & 0xFFFF
2792 flags = entry[0] & 0xFFFF
2792 linkrev = entry[4]
2793 linkrev = entry[4]
2793 p1 = index[entry[5]][7]
2794 p1 = index[entry[5]][7]
2794 p2 = index[entry[6]][7]
2795 p2 = index[entry[6]][7]
2795 node = entry[7]
2796 node = entry[7]
2796
2797
2797 sidedataactions = (False, [], {}, 0, 0)
2798 sidedataactions = (False, [], {}, 0, 0)
2798 if sidedatacompanion is not None:
2799 if sidedatacompanion is not None:
2799 sidedataactions = sidedatacompanion(self, rev)
2800 sidedataactions = sidedatacompanion(self, rev)
2800
2801
2801 # (Possibly) reuse the delta from the revlog if allowed and
2802 # (Possibly) reuse the delta from the revlog if allowed and
2802 # the revlog chunk is a delta.
2803 # the revlog chunk is a delta.
2803 cachedelta = None
2804 cachedelta = None
2804 rawtext = None
2805 rawtext = None
2805 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2806 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2806 dropall = sidedataactions[0]
2807 dropall = sidedataactions[0]
2807 filterout = sidedataactions[1]
2808 filterout = sidedataactions[1]
2808 update = sidedataactions[2]
2809 update = sidedataactions[2]
2809 new_flags = sidedataactions[3]
2810 new_flags = sidedataactions[3]
2810 dropped_flags = sidedataactions[4]
2811 dropped_flags = sidedataactions[4]
2811 text, sidedata = self._revisiondata(rev)
2812 text, sidedata = self._revisiondata(rev)
2812 if dropall:
2813 if dropall:
2813 sidedata = {}
2814 sidedata = {}
2814 for key in filterout:
2815 for key in filterout:
2815 sidedata.pop(key, None)
2816 sidedata.pop(key, None)
2816 sidedata.update(update)
2817 sidedata.update(update)
2817 if not sidedata:
2818 if not sidedata:
2818 sidedata = None
2819 sidedata = None
2819
2820
2820 flags |= new_flags
2821 flags |= new_flags
2821 flags &= ~dropped_flags
2822 flags &= ~dropped_flags
2822
2823
2823 destrevlog.addrevision(
2824 destrevlog.addrevision(
2824 text,
2825 text,
2825 tr,
2826 tr,
2826 linkrev,
2827 linkrev,
2827 p1,
2828 p1,
2828 p2,
2829 p2,
2829 cachedelta=cachedelta,
2830 cachedelta=cachedelta,
2830 node=node,
2831 node=node,
2831 flags=flags,
2832 flags=flags,
2832 deltacomputer=deltacomputer,
2833 deltacomputer=deltacomputer,
2833 sidedata=sidedata,
2834 sidedata=sidedata,
2834 )
2835 )
2835 else:
2836 else:
2836 if destrevlog._lazydelta:
2837 if destrevlog._lazydelta:
2837 dp = self.deltaparent(rev)
2838 dp = self.deltaparent(rev)
2838 if dp != nullrev:
2839 if dp != nullrev:
2839 cachedelta = (dp, bytes(self._chunk(rev)))
2840 cachedelta = (dp, bytes(self._chunk(rev)))
2840
2841
2841 if not cachedelta:
2842 if not cachedelta:
2842 rawtext = self.rawdata(rev)
2843 rawtext = self.rawdata(rev)
2843
2844
2844 ifh = destrevlog.opener(
2845 ifh = destrevlog.opener(
2845 destrevlog.indexfile, b'a+', checkambig=False
2846 destrevlog.indexfile, b'a+', checkambig=False
2846 )
2847 )
2847 dfh = None
2848 dfh = None
2848 if not destrevlog._inline:
2849 if not destrevlog._inline:
2849 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2850 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2850 try:
2851 try:
2851 destrevlog._addrevision(
2852 destrevlog._addrevision(
2852 node,
2853 node,
2853 rawtext,
2854 rawtext,
2854 tr,
2855 tr,
2855 linkrev,
2856 linkrev,
2856 p1,
2857 p1,
2857 p2,
2858 p2,
2858 flags,
2859 flags,
2859 cachedelta,
2860 cachedelta,
2860 ifh,
2861 ifh,
2861 dfh,
2862 dfh,
2862 deltacomputer=deltacomputer,
2863 deltacomputer=deltacomputer,
2863 )
2864 )
2864 finally:
2865 finally:
2865 if dfh:
2866 if dfh:
2866 dfh.close()
2867 dfh.close()
2867 ifh.close()
2868 ifh.close()
2868
2869
2869 if addrevisioncb:
2870 if addrevisioncb:
2870 addrevisioncb(self, rev, node)
2871 addrevisioncb(self, rev, node)
2871
2872
2872 def censorrevision(self, tr, censornode, tombstone=b''):
2873 def censorrevision(self, tr, censornode, tombstone=b''):
2873 if (self.version & 0xFFFF) == REVLOGV0:
2874 if (self.version & 0xFFFF) == REVLOGV0:
2874 raise error.RevlogError(
2875 raise error.RevlogError(
2875 _(b'cannot censor with version %d revlogs') % self.version
2876 _(b'cannot censor with version %d revlogs') % self.version
2876 )
2877 )
2877
2878
2878 censorrev = self.rev(censornode)
2879 censorrev = self.rev(censornode)
2879 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2880 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2880
2881
2881 if len(tombstone) > self.rawsize(censorrev):
2882 if len(tombstone) > self.rawsize(censorrev):
2882 raise error.Abort(
2883 raise error.Abort(
2883 _(b'censor tombstone must be no longer than censored data')
2884 _(b'censor tombstone must be no longer than censored data')
2884 )
2885 )
2885
2886
2886 # Rewriting the revlog in place is hard. Our strategy for censoring is
2887 # Rewriting the revlog in place is hard. Our strategy for censoring is
2887 # to create a new revlog, copy all revisions to it, then replace the
2888 # to create a new revlog, copy all revisions to it, then replace the
2888 # revlogs on transaction close.
2889 # revlogs on transaction close.
2889
2890
2890 newindexfile = self.indexfile + b'.tmpcensored'
2891 newindexfile = self.indexfile + b'.tmpcensored'
2891 newdatafile = self.datafile + b'.tmpcensored'
2892 newdatafile = self.datafile + b'.tmpcensored'
2892
2893
2893 # This is a bit dangerous. We could easily have a mismatch of state.
2894 # This is a bit dangerous. We could easily have a mismatch of state.
2894 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2895 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2895 newrl.version = self.version
2896 newrl.version = self.version
2896 newrl._generaldelta = self._generaldelta
2897 newrl._generaldelta = self._generaldelta
2897 newrl._io = self._io
2898 newrl._io = self._io
2898
2899
2899 for rev in self.revs():
2900 for rev in self.revs():
2900 node = self.node(rev)
2901 node = self.node(rev)
2901 p1, p2 = self.parents(node)
2902 p1, p2 = self.parents(node)
2902
2903
2903 if rev == censorrev:
2904 if rev == censorrev:
2904 newrl.addrawrevision(
2905 newrl.addrawrevision(
2905 tombstone,
2906 tombstone,
2906 tr,
2907 tr,
2907 self.linkrev(censorrev),
2908 self.linkrev(censorrev),
2908 p1,
2909 p1,
2909 p2,
2910 p2,
2910 censornode,
2911 censornode,
2911 REVIDX_ISCENSORED,
2912 REVIDX_ISCENSORED,
2912 )
2913 )
2913
2914
2914 if newrl.deltaparent(rev) != nullrev:
2915 if newrl.deltaparent(rev) != nullrev:
2915 raise error.Abort(
2916 raise error.Abort(
2916 _(
2917 _(
2917 b'censored revision stored as delta; '
2918 b'censored revision stored as delta; '
2918 b'cannot censor'
2919 b'cannot censor'
2919 ),
2920 ),
2920 hint=_(
2921 hint=_(
2921 b'censoring of revlogs is not '
2922 b'censoring of revlogs is not '
2922 b'fully implemented; please report '
2923 b'fully implemented; please report '
2923 b'this bug'
2924 b'this bug'
2924 ),
2925 ),
2925 )
2926 )
2926 continue
2927 continue
2927
2928
2928 if self.iscensored(rev):
2929 if self.iscensored(rev):
2929 if self.deltaparent(rev) != nullrev:
2930 if self.deltaparent(rev) != nullrev:
2930 raise error.Abort(
2931 raise error.Abort(
2931 _(
2932 _(
2932 b'cannot censor due to censored '
2933 b'cannot censor due to censored '
2933 b'revision having delta stored'
2934 b'revision having delta stored'
2934 )
2935 )
2935 )
2936 )
2936 rawtext = self._chunk(rev)
2937 rawtext = self._chunk(rev)
2937 else:
2938 else:
2938 rawtext = self.rawdata(rev)
2939 rawtext = self.rawdata(rev)
2939
2940
2940 newrl.addrawrevision(
2941 newrl.addrawrevision(
2941 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2942 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2942 )
2943 )
2943
2944
2944 tr.addbackup(self.indexfile, location=b'store')
2945 tr.addbackup(self.indexfile, location=b'store')
2945 if not self._inline:
2946 if not self._inline:
2946 tr.addbackup(self.datafile, location=b'store')
2947 tr.addbackup(self.datafile, location=b'store')
2947
2948
2948 self.opener.rename(newrl.indexfile, self.indexfile)
2949 self.opener.rename(newrl.indexfile, self.indexfile)
2949 if not self._inline:
2950 if not self._inline:
2950 self.opener.rename(newrl.datafile, self.datafile)
2951 self.opener.rename(newrl.datafile, self.datafile)
2951
2952
2952 self.clearcaches()
2953 self.clearcaches()
2953 self._loadindex()
2954 self._loadindex()
2954
2955
2955 def verifyintegrity(self, state):
2956 def verifyintegrity(self, state):
2956 """Verifies the integrity of the revlog.
2957 """Verifies the integrity of the revlog.
2957
2958
2958 Yields ``revlogproblem`` instances describing problems that are
2959 Yields ``revlogproblem`` instances describing problems that are
2959 found.
2960 found.
2960 """
2961 """
2961 dd, di = self.checksize()
2962 dd, di = self.checksize()
2962 if dd:
2963 if dd:
2963 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2964 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2964 if di:
2965 if di:
2965 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2966 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2966
2967
2967 version = self.version & 0xFFFF
2968 version = self.version & 0xFFFF
2968
2969
2969 # The verifier tells us what version revlog we should be.
2970 # The verifier tells us what version revlog we should be.
2970 if version != state[b'expectedversion']:
2971 if version != state[b'expectedversion']:
2971 yield revlogproblem(
2972 yield revlogproblem(
2972 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2973 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2973 % (self.indexfile, version, state[b'expectedversion'])
2974 % (self.indexfile, version, state[b'expectedversion'])
2974 )
2975 )
2975
2976
2976 state[b'skipread'] = set()
2977 state[b'skipread'] = set()
2977 state[b'safe_renamed'] = set()
2978 state[b'safe_renamed'] = set()
2978
2979
2979 for rev in self:
2980 for rev in self:
2980 node = self.node(rev)
2981 node = self.node(rev)
2981
2982
2982 # Verify contents. 4 cases to care about:
2983 # Verify contents. 4 cases to care about:
2983 #
2984 #
2984 # common: the most common case
2985 # common: the most common case
2985 # rename: with a rename
2986 # rename: with a rename
2986 # meta: file content starts with b'\1\n', the metadata
2987 # meta: file content starts with b'\1\n', the metadata
2987 # header defined in filelog.py, but without a rename
2988 # header defined in filelog.py, but without a rename
2988 # ext: content stored externally
2989 # ext: content stored externally
2989 #
2990 #
2990 # More formally, their differences are shown below:
2991 # More formally, their differences are shown below:
2991 #
2992 #
2992 # | common | rename | meta | ext
2993 # | common | rename | meta | ext
2993 # -------------------------------------------------------
2994 # -------------------------------------------------------
2994 # flags() | 0 | 0 | 0 | not 0
2995 # flags() | 0 | 0 | 0 | not 0
2995 # renamed() | False | True | False | ?
2996 # renamed() | False | True | False | ?
2996 # rawtext[0:2]=='\1\n'| False | True | True | ?
2997 # rawtext[0:2]=='\1\n'| False | True | True | ?
2997 #
2998 #
2998 # "rawtext" means the raw text stored in revlog data, which
2999 # "rawtext" means the raw text stored in revlog data, which
2999 # could be retrieved by "rawdata(rev)". "text"
3000 # could be retrieved by "rawdata(rev)". "text"
3000 # mentioned below is "revision(rev)".
3001 # mentioned below is "revision(rev)".
3001 #
3002 #
3002 # There are 3 different lengths stored physically:
3003 # There are 3 different lengths stored physically:
3003 # 1. L1: rawsize, stored in revlog index
3004 # 1. L1: rawsize, stored in revlog index
3004 # 2. L2: len(rawtext), stored in revlog data
3005 # 2. L2: len(rawtext), stored in revlog data
3005 # 3. L3: len(text), stored in revlog data if flags==0, or
3006 # 3. L3: len(text), stored in revlog data if flags==0, or
3006 # possibly somewhere else if flags!=0
3007 # possibly somewhere else if flags!=0
3007 #
3008 #
3008 # L1 should be equal to L2. L3 could be different from them.
3009 # L1 should be equal to L2. L3 could be different from them.
3009 # "text" may or may not affect commit hash depending on flag
3010 # "text" may or may not affect commit hash depending on flag
3010 # processors (see flagutil.addflagprocessor).
3011 # processors (see flagutil.addflagprocessor).
3011 #
3012 #
3012 # | common | rename | meta | ext
3013 # | common | rename | meta | ext
3013 # -------------------------------------------------
3014 # -------------------------------------------------
3014 # rawsize() | L1 | L1 | L1 | L1
3015 # rawsize() | L1 | L1 | L1 | L1
3015 # size() | L1 | L2-LM | L1(*) | L1 (?)
3016 # size() | L1 | L2-LM | L1(*) | L1 (?)
3016 # len(rawtext) | L2 | L2 | L2 | L2
3017 # len(rawtext) | L2 | L2 | L2 | L2
3017 # len(text) | L2 | L2 | L2 | L3
3018 # len(text) | L2 | L2 | L2 | L3
3018 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3019 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3019 #
3020 #
3020 # LM: length of metadata, depending on rawtext
3021 # LM: length of metadata, depending on rawtext
3021 # (*): not ideal, see comment in filelog.size
3022 # (*): not ideal, see comment in filelog.size
3022 # (?): could be "- len(meta)" if the resolved content has
3023 # (?): could be "- len(meta)" if the resolved content has
3023 # rename metadata
3024 # rename metadata
3024 #
3025 #
3025 # Checks needed to be done:
3026 # Checks needed to be done:
3026 # 1. length check: L1 == L2, in all cases.
3027 # 1. length check: L1 == L2, in all cases.
3027 # 2. hash check: depending on flag processor, we may need to
3028 # 2. hash check: depending on flag processor, we may need to
3028 # use either "text" (external), or "rawtext" (in revlog).
3029 # use either "text" (external), or "rawtext" (in revlog).
3029
3030
3030 try:
3031 try:
3031 skipflags = state.get(b'skipflags', 0)
3032 skipflags = state.get(b'skipflags', 0)
3032 if skipflags:
3033 if skipflags:
3033 skipflags &= self.flags(rev)
3034 skipflags &= self.flags(rev)
3034
3035
3035 _verify_revision(self, skipflags, state, node)
3036 _verify_revision(self, skipflags, state, node)
3036
3037
3037 l1 = self.rawsize(rev)
3038 l1 = self.rawsize(rev)
3038 l2 = len(self.rawdata(node))
3039 l2 = len(self.rawdata(node))
3039
3040
3040 if l1 != l2:
3041 if l1 != l2:
3041 yield revlogproblem(
3042 yield revlogproblem(
3042 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3043 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3043 node=node,
3044 node=node,
3044 )
3045 )
3045
3046
3046 except error.CensoredNodeError:
3047 except error.CensoredNodeError:
3047 if state[b'erroroncensored']:
3048 if state[b'erroroncensored']:
3048 yield revlogproblem(
3049 yield revlogproblem(
3049 error=_(b'censored file data'), node=node
3050 error=_(b'censored file data'), node=node
3050 )
3051 )
3051 state[b'skipread'].add(node)
3052 state[b'skipread'].add(node)
3052 except Exception as e:
3053 except Exception as e:
3053 yield revlogproblem(
3054 yield revlogproblem(
3054 error=_(b'unpacking %s: %s')
3055 error=_(b'unpacking %s: %s')
3055 % (short(node), stringutil.forcebytestr(e)),
3056 % (short(node), stringutil.forcebytestr(e)),
3056 node=node,
3057 node=node,
3057 )
3058 )
3058 state[b'skipread'].add(node)
3059 state[b'skipread'].add(node)
3059
3060
3060 def storageinfo(
3061 def storageinfo(
3061 self,
3062 self,
3062 exclusivefiles=False,
3063 exclusivefiles=False,
3063 sharedfiles=False,
3064 sharedfiles=False,
3064 revisionscount=False,
3065 revisionscount=False,
3065 trackedsize=False,
3066 trackedsize=False,
3066 storedsize=False,
3067 storedsize=False,
3067 ):
3068 ):
3068 d = {}
3069 d = {}
3069
3070
3070 if exclusivefiles:
3071 if exclusivefiles:
3071 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3072 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3072 if not self._inline:
3073 if not self._inline:
3073 d[b'exclusivefiles'].append((self.opener, self.datafile))
3074 d[b'exclusivefiles'].append((self.opener, self.datafile))
3074
3075
3075 if sharedfiles:
3076 if sharedfiles:
3076 d[b'sharedfiles'] = []
3077 d[b'sharedfiles'] = []
3077
3078
3078 if revisionscount:
3079 if revisionscount:
3079 d[b'revisionscount'] = len(self)
3080 d[b'revisionscount'] = len(self)
3080
3081
3081 if trackedsize:
3082 if trackedsize:
3082 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3083 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3083
3084
3084 if storedsize:
3085 if storedsize:
3085 d[b'storedsize'] = sum(
3086 d[b'storedsize'] = sum(
3086 self.opener.stat(path).st_size for path in self.files()
3087 self.opener.stat(path).st_size for path in self.files()
3087 )
3088 )
3088
3089
3089 return d
3090 return d
@@ -1,287 +1,288 b''
1 # unionrepo.py - repository class for viewing union of repository changesets
1 # unionrepo.py - repository class for viewing union of repository changesets
2 #
2 #
3 # Derived from bundlerepo.py
3 # Derived from bundlerepo.py
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Repository class for "in-memory pull" of one local repository to another,
10 """Repository class for "in-memory pull" of one local repository to another,
11 allowing operations like diff and log with revsets.
11 allowing operations like diff and log with revsets.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 from .i18n import _
16 from .i18n import _
17 from .pycompat import getattr
17 from .pycompat import getattr
18
18
19 from . import (
19 from . import (
20 changelog,
20 changelog,
21 cmdutil,
21 cmdutil,
22 encoding,
22 encoding,
23 error,
23 error,
24 filelog,
24 filelog,
25 localrepo,
25 localrepo,
26 manifest,
26 manifest,
27 mdiff,
27 mdiff,
28 pathutil,
28 pathutil,
29 revlog,
29 revlog,
30 util,
30 util,
31 vfs as vfsmod,
31 vfs as vfsmod,
32 )
32 )
33
33
34
34
35 class unionrevlog(revlog.revlog):
35 class unionrevlog(revlog.revlog):
36 def __init__(self, opener, indexfile, revlog2, linkmapper):
36 def __init__(self, opener, indexfile, revlog2, linkmapper):
37 # How it works:
37 # How it works:
38 # To retrieve a revision, we just need to know the node id so we can
38 # To retrieve a revision, we just need to know the node id so we can
39 # look it up in revlog2.
39 # look it up in revlog2.
40 #
40 #
41 # To differentiate a rev in the second revlog from a rev in the revlog,
41 # To differentiate a rev in the second revlog from a rev in the revlog,
42 # we check revision against repotiprev.
42 # we check revision against repotiprev.
43 opener = vfsmod.readonlyvfs(opener)
43 opener = vfsmod.readonlyvfs(opener)
44 revlog.revlog.__init__(self, opener, indexfile)
44 revlog.revlog.__init__(self, opener, indexfile)
45 self.revlog2 = revlog2
45 self.revlog2 = revlog2
46
46
47 n = len(self)
47 n = len(self)
48 self.repotiprev = n - 1
48 self.repotiprev = n - 1
49 self.bundlerevs = set() # used by 'bundle()' revset expression
49 self.bundlerevs = set() # used by 'bundle()' revset expression
50 for rev2 in self.revlog2:
50 for rev2 in self.revlog2:
51 rev = self.revlog2.index[rev2]
51 rev = self.revlog2.index[rev2]
52 # rev numbers - in revlog2, very different from self.rev
52 # rev numbers - in revlog2, very different from self.rev
53 _start, _csize, rsize, base, linkrev, p1rev, p2rev, node = rev
53 _start, _csize, rsize, base, linkrev, p1rev, p2rev, node = rev
54 flags = _start & 0xFFFF
54 flags = _start & 0xFFFF
55
55
56 if linkmapper is None: # link is to same revlog
56 if linkmapper is None: # link is to same revlog
57 assert linkrev == rev2 # we never link back
57 assert linkrev == rev2 # we never link back
58 link = n
58 link = n
59 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
59 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
60 link = linkmapper(linkrev)
60 link = linkmapper(linkrev)
61
61
62 if linkmapper is not None: # link is to same revlog
62 if linkmapper is not None: # link is to same revlog
63 base = linkmapper(base)
63 base = linkmapper(base)
64
64
65 this_rev = self.index.get_rev(node)
65 this_rev = self.index.get_rev(node)
66 if this_rev is not None:
66 if this_rev is not None:
67 # this happens for the common revlog revisions
67 # this happens for the common revlog revisions
68 self.bundlerevs.add(this_rev)
68 self.bundlerevs.add(this_rev)
69 continue
69 continue
70
70
71 p1node = self.revlog2.node(p1rev)
71 p1node = self.revlog2.node(p1rev)
72 p2node = self.revlog2.node(p2rev)
72 p2node = self.revlog2.node(p2rev)
73
73
74 # TODO: it's probably wrong to set compressed length to -1, but
74 # TODO: it's probably wrong to set compressed length to -1, but
75 # I have no idea if csize is valid in the base revlog context.
75 # I have no idea if csize is valid in the base revlog context.
76 e = (
76 e = (
77 flags,
77 flags,
78 -1,
78 -1,
79 rsize,
79 rsize,
80 base,
80 base,
81 link,
81 link,
82 self.rev(p1node),
82 self.rev(p1node),
83 self.rev(p2node),
83 self.rev(p2node),
84 node,
84 node,
85 )
85 )
86 self.index.append(e)
86 self.index.append(e)
87 self.bundlerevs.add(n)
87 self.bundlerevs.add(n)
88 n += 1
88 n += 1
89
89
90 def _chunk(self, rev):
90 def _chunk(self, rev):
91 if rev <= self.repotiprev:
91 if rev <= self.repotiprev:
92 return revlog.revlog._chunk(self, rev)
92 return revlog.revlog._chunk(self, rev)
93 return self.revlog2._chunk(self.node(rev))
93 return self.revlog2._chunk(self.node(rev))
94
94
95 def revdiff(self, rev1, rev2):
95 def revdiff(self, rev1, rev2):
96 """return or calculate a delta between two revisions"""
96 """return or calculate a delta between two revisions"""
97 if rev1 > self.repotiprev and rev2 > self.repotiprev:
97 if rev1 > self.repotiprev and rev2 > self.repotiprev:
98 return self.revlog2.revdiff(
98 return self.revlog2.revdiff(
99 self.revlog2.rev(self.node(rev1)),
99 self.revlog2.rev(self.node(rev1)),
100 self.revlog2.rev(self.node(rev2)),
100 self.revlog2.rev(self.node(rev2)),
101 )
101 )
102 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
102 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
103 return super(unionrevlog, self).revdiff(rev1, rev2)
103 return super(unionrevlog, self).revdiff(rev1, rev2)
104
104
105 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
105 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
106
106
107 def _revisiondata(self, nodeorrev, _df=None, raw=False):
107 def _revisiondata(self, nodeorrev, _df=None, raw=False):
108 if isinstance(nodeorrev, int):
108 if isinstance(nodeorrev, int):
109 rev = nodeorrev
109 rev = nodeorrev
110 node = self.node(rev)
110 node = self.node(rev)
111 else:
111 else:
112 node = nodeorrev
112 node = nodeorrev
113 rev = self.rev(node)
113 rev = self.rev(node)
114
114
115 if rev > self.repotiprev:
115 if rev > self.repotiprev:
116 # work around manifestrevlog NOT being a revlog
116 # work around manifestrevlog NOT being a revlog
117 revlog2 = getattr(self.revlog2, '_revlog', self.revlog2)
117 revlog2 = getattr(self.revlog2, '_revlog', self.revlog2)
118 func = revlog2._revisiondata
118 func = revlog2._revisiondata
119 else:
119 else:
120 func = super(unionrevlog, self)._revisiondata
120 func = super(unionrevlog, self)._revisiondata
121 return func(node, _df=_df, raw=raw)
121 return func(node, _df=_df, raw=raw)
122
122
123 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
123 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
124 raise NotImplementedError
124 raise NotImplementedError
125
125
126 def addgroup(
126 def addgroup(
127 self,
127 self,
128 deltas,
128 deltas,
129 linkmapper,
129 linkmapper,
130 transaction,
130 transaction,
131 alwayscache=False,
131 addrevisioncb=None,
132 addrevisioncb=None,
132 duplicaterevisioncb=None,
133 duplicaterevisioncb=None,
133 maybemissingparents=False,
134 maybemissingparents=False,
134 ):
135 ):
135 raise NotImplementedError
136 raise NotImplementedError
136
137
137 def strip(self, minlink, transaction):
138 def strip(self, minlink, transaction):
138 raise NotImplementedError
139 raise NotImplementedError
139
140
140 def checksize(self):
141 def checksize(self):
141 raise NotImplementedError
142 raise NotImplementedError
142
143
143
144
144 class unionchangelog(unionrevlog, changelog.changelog):
145 class unionchangelog(unionrevlog, changelog.changelog):
145 def __init__(self, opener, opener2):
146 def __init__(self, opener, opener2):
146 changelog.changelog.__init__(self, opener)
147 changelog.changelog.__init__(self, opener)
147 linkmapper = None
148 linkmapper = None
148 changelog2 = changelog.changelog(opener2)
149 changelog2 = changelog.changelog(opener2)
149 unionrevlog.__init__(
150 unionrevlog.__init__(
150 self, opener, self.indexfile, changelog2, linkmapper
151 self, opener, self.indexfile, changelog2, linkmapper
151 )
152 )
152
153
153
154
154 class unionmanifest(unionrevlog, manifest.manifestrevlog):
155 class unionmanifest(unionrevlog, manifest.manifestrevlog):
155 def __init__(self, opener, opener2, linkmapper):
156 def __init__(self, opener, opener2, linkmapper):
156 manifest.manifestrevlog.__init__(self, opener)
157 manifest.manifestrevlog.__init__(self, opener)
157 manifest2 = manifest.manifestrevlog(opener2)
158 manifest2 = manifest.manifestrevlog(opener2)
158 unionrevlog.__init__(
159 unionrevlog.__init__(
159 self, opener, self.indexfile, manifest2, linkmapper
160 self, opener, self.indexfile, manifest2, linkmapper
160 )
161 )
161
162
162
163
163 class unionfilelog(filelog.filelog):
164 class unionfilelog(filelog.filelog):
164 def __init__(self, opener, path, opener2, linkmapper, repo):
165 def __init__(self, opener, path, opener2, linkmapper, repo):
165 filelog.filelog.__init__(self, opener, path)
166 filelog.filelog.__init__(self, opener, path)
166 filelog2 = filelog.filelog(opener2, path)
167 filelog2 = filelog.filelog(opener2, path)
167 self._revlog = unionrevlog(
168 self._revlog = unionrevlog(
168 opener, self.indexfile, filelog2._revlog, linkmapper
169 opener, self.indexfile, filelog2._revlog, linkmapper
169 )
170 )
170 self._repo = repo
171 self._repo = repo
171 self.repotiprev = self._revlog.repotiprev
172 self.repotiprev = self._revlog.repotiprev
172 self.revlog2 = self._revlog.revlog2
173 self.revlog2 = self._revlog.revlog2
173
174
174 def iscensored(self, rev):
175 def iscensored(self, rev):
175 """Check if a revision is censored."""
176 """Check if a revision is censored."""
176 if rev <= self.repotiprev:
177 if rev <= self.repotiprev:
177 return filelog.filelog.iscensored(self, rev)
178 return filelog.filelog.iscensored(self, rev)
178 node = self.node(rev)
179 node = self.node(rev)
179 return self.revlog2.iscensored(self.revlog2.rev(node))
180 return self.revlog2.iscensored(self.revlog2.rev(node))
180
181
181
182
182 class unionpeer(localrepo.localpeer):
183 class unionpeer(localrepo.localpeer):
183 def canpush(self):
184 def canpush(self):
184 return False
185 return False
185
186
186
187
187 class unionrepository(object):
188 class unionrepository(object):
188 """Represents the union of data in 2 repositories.
189 """Represents the union of data in 2 repositories.
189
190
190 Instances are not usable if constructed directly. Use ``instance()``
191 Instances are not usable if constructed directly. Use ``instance()``
191 or ``makeunionrepository()`` to create a usable instance.
192 or ``makeunionrepository()`` to create a usable instance.
192 """
193 """
193
194
194 def __init__(self, repo2, url):
195 def __init__(self, repo2, url):
195 self.repo2 = repo2
196 self.repo2 = repo2
196 self._url = url
197 self._url = url
197
198
198 self.ui.setconfig(b'phases', b'publish', False, b'unionrepo')
199 self.ui.setconfig(b'phases', b'publish', False, b'unionrepo')
199
200
200 @localrepo.unfilteredpropertycache
201 @localrepo.unfilteredpropertycache
201 def changelog(self):
202 def changelog(self):
202 return unionchangelog(self.svfs, self.repo2.svfs)
203 return unionchangelog(self.svfs, self.repo2.svfs)
203
204
204 @localrepo.unfilteredpropertycache
205 @localrepo.unfilteredpropertycache
205 def manifestlog(self):
206 def manifestlog(self):
206 rootstore = unionmanifest(
207 rootstore = unionmanifest(
207 self.svfs, self.repo2.svfs, self.unfiltered()._clrev
208 self.svfs, self.repo2.svfs, self.unfiltered()._clrev
208 )
209 )
209 return manifest.manifestlog(
210 return manifest.manifestlog(
210 self.svfs, self, rootstore, self.narrowmatch()
211 self.svfs, self, rootstore, self.narrowmatch()
211 )
212 )
212
213
213 def _clrev(self, rev2):
214 def _clrev(self, rev2):
214 """map from repo2 changelog rev to temporary rev in self.changelog"""
215 """map from repo2 changelog rev to temporary rev in self.changelog"""
215 node = self.repo2.changelog.node(rev2)
216 node = self.repo2.changelog.node(rev2)
216 return self.changelog.rev(node)
217 return self.changelog.rev(node)
217
218
218 def url(self):
219 def url(self):
219 return self._url
220 return self._url
220
221
221 def file(self, f):
222 def file(self, f):
222 return unionfilelog(
223 return unionfilelog(
223 self.svfs, f, self.repo2.svfs, self.unfiltered()._clrev, self
224 self.svfs, f, self.repo2.svfs, self.unfiltered()._clrev, self
224 )
225 )
225
226
226 def close(self):
227 def close(self):
227 self.repo2.close()
228 self.repo2.close()
228
229
229 def cancopy(self):
230 def cancopy(self):
230 return False
231 return False
231
232
232 def peer(self):
233 def peer(self):
233 return unionpeer(self)
234 return unionpeer(self)
234
235
235 def getcwd(self):
236 def getcwd(self):
236 return encoding.getcwd() # always outside the repo
237 return encoding.getcwd() # always outside the repo
237
238
238
239
239 def instance(ui, path, create, intents=None, createopts=None):
240 def instance(ui, path, create, intents=None, createopts=None):
240 if create:
241 if create:
241 raise error.Abort(_(b'cannot create new union repository'))
242 raise error.Abort(_(b'cannot create new union repository'))
242 parentpath = ui.config(b"bundle", b"mainreporoot")
243 parentpath = ui.config(b"bundle", b"mainreporoot")
243 if not parentpath:
244 if not parentpath:
244 # try to find the correct path to the working directory repo
245 # try to find the correct path to the working directory repo
245 parentpath = cmdutil.findrepo(encoding.getcwd())
246 parentpath = cmdutil.findrepo(encoding.getcwd())
246 if parentpath is None:
247 if parentpath is None:
247 parentpath = b''
248 parentpath = b''
248 if parentpath:
249 if parentpath:
249 # Try to make the full path relative so we get a nice, short URL.
250 # Try to make the full path relative so we get a nice, short URL.
250 # In particular, we don't want temp dir names in test outputs.
251 # In particular, we don't want temp dir names in test outputs.
251 cwd = encoding.getcwd()
252 cwd = encoding.getcwd()
252 if parentpath == cwd:
253 if parentpath == cwd:
253 parentpath = b''
254 parentpath = b''
254 else:
255 else:
255 cwd = pathutil.normasprefix(cwd)
256 cwd = pathutil.normasprefix(cwd)
256 if parentpath.startswith(cwd):
257 if parentpath.startswith(cwd):
257 parentpath = parentpath[len(cwd) :]
258 parentpath = parentpath[len(cwd) :]
258 if path.startswith(b'union:'):
259 if path.startswith(b'union:'):
259 s = path.split(b":", 1)[1].split(b"+", 1)
260 s = path.split(b":", 1)[1].split(b"+", 1)
260 if len(s) == 1:
261 if len(s) == 1:
261 repopath, repopath2 = parentpath, s[0]
262 repopath, repopath2 = parentpath, s[0]
262 else:
263 else:
263 repopath, repopath2 = s
264 repopath, repopath2 = s
264 else:
265 else:
265 repopath, repopath2 = parentpath, path
266 repopath, repopath2 = parentpath, path
266
267
267 return makeunionrepository(ui, repopath, repopath2)
268 return makeunionrepository(ui, repopath, repopath2)
268
269
269
270
270 def makeunionrepository(ui, repopath1, repopath2):
271 def makeunionrepository(ui, repopath1, repopath2):
271 """Make a union repository object from 2 local repo paths."""
272 """Make a union repository object from 2 local repo paths."""
272 repo1 = localrepo.instance(ui, repopath1, create=False)
273 repo1 = localrepo.instance(ui, repopath1, create=False)
273 repo2 = localrepo.instance(ui, repopath2, create=False)
274 repo2 = localrepo.instance(ui, repopath2, create=False)
274
275
275 url = b'union:%s+%s' % (
276 url = b'union:%s+%s' % (
276 util.expandpath(repopath1),
277 util.expandpath(repopath1),
277 util.expandpath(repopath2),
278 util.expandpath(repopath2),
278 )
279 )
279
280
280 class derivedunionrepository(unionrepository, repo1.__class__):
281 class derivedunionrepository(unionrepository, repo1.__class__):
281 pass
282 pass
282
283
283 repo = repo1
284 repo = repo1
284 repo.__class__ = derivedunionrepository
285 repo.__class__ = derivedunionrepository
285 unionrepository.__init__(repo1, repo2, url)
286 unionrepository.__init__(repo1, repo2, url)
286
287
287 return repo
288 return repo
General Comments 0
You need to be logged in to leave comments. Login now