##// END OF EJS Templates
manifest: drop the `indexfile` from `manifestrevlog`...
marmoute -
r47917:bc7d465e default
parent child Browse files
Show More
@@ -1,714 +1,714 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@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 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import os
17 import shutil
17 import shutil
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 hex,
21 hex,
22 nullrev,
22 nullrev,
23 )
23 )
24
24
25 from . import (
25 from . import (
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 changelog,
28 changelog,
29 cmdutil,
29 cmdutil,
30 discovery,
30 discovery,
31 encoding,
31 encoding,
32 error,
32 error,
33 exchange,
33 exchange,
34 filelog,
34 filelog,
35 localrepo,
35 localrepo,
36 manifest,
36 manifest,
37 mdiff,
37 mdiff,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 pycompat,
40 pycompat,
41 revlog,
41 revlog,
42 util,
42 util,
43 vfs as vfsmod,
43 vfs as vfsmod,
44 )
44 )
45 from .utils import (
45 from .utils import (
46 urlutil,
46 urlutil,
47 )
47 )
48
48
49 from .revlogutils import (
49 from .revlogutils import (
50 constants as revlog_constants,
50 constants as revlog_constants,
51 )
51 )
52
52
53
53
54 class bundlerevlog(revlog.revlog):
54 class bundlerevlog(revlog.revlog):
55 def __init__(self, opener, target, indexfile, cgunpacker, linkmapper):
55 def __init__(self, opener, target, indexfile, cgunpacker, linkmapper):
56 # How it works:
56 # How it works:
57 # To retrieve a revision, we need to know the offset of the revision in
57 # To retrieve a revision, we need to know the offset of the revision in
58 # the bundle (an unbundle object). We store this offset in the index
58 # the bundle (an unbundle object). We store this offset in the index
59 # (start). The base of the delta is stored in the base field.
59 # (start). The base of the delta is stored in the base field.
60 #
60 #
61 # To differentiate a rev in the bundle from a rev in the revlog, we
61 # To differentiate a rev in the bundle from a rev in the revlog, we
62 # check revision against repotiprev.
62 # check revision against repotiprev.
63 opener = vfsmod.readonlyvfs(opener)
63 opener = vfsmod.readonlyvfs(opener)
64 revlog.revlog.__init__(self, opener, target=target, indexfile=indexfile)
64 revlog.revlog.__init__(self, opener, target=target, indexfile=indexfile)
65 self.bundle = cgunpacker
65 self.bundle = cgunpacker
66 n = len(self)
66 n = len(self)
67 self.repotiprev = n - 1
67 self.repotiprev = n - 1
68 self.bundlerevs = set() # used by 'bundle()' revset expression
68 self.bundlerevs = set() # used by 'bundle()' revset expression
69 for deltadata in cgunpacker.deltaiter():
69 for deltadata in cgunpacker.deltaiter():
70 node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata
70 node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata
71
71
72 size = len(delta)
72 size = len(delta)
73 start = cgunpacker.tell() - size
73 start = cgunpacker.tell() - size
74
74
75 if self.index.has_node(node):
75 if self.index.has_node(node):
76 # this can happen if two branches make the same change
76 # this can happen if two branches make the same change
77 self.bundlerevs.add(self.index.rev(node))
77 self.bundlerevs.add(self.index.rev(node))
78 continue
78 continue
79 if cs == node:
79 if cs == node:
80 linkrev = nullrev
80 linkrev = nullrev
81 else:
81 else:
82 linkrev = linkmapper(cs)
82 linkrev = linkmapper(cs)
83
83
84 for p in (p1, p2):
84 for p in (p1, p2):
85 if not self.index.has_node(p):
85 if not self.index.has_node(p):
86 raise error.LookupError(
86 raise error.LookupError(
87 p, self.indexfile, _(b"unknown parent")
87 p, self.indexfile, _(b"unknown parent")
88 )
88 )
89
89
90 if not self.index.has_node(deltabase):
90 if not self.index.has_node(deltabase):
91 raise LookupError(
91 raise LookupError(
92 deltabase, self.indexfile, _(b'unknown delta base')
92 deltabase, self.indexfile, _(b'unknown delta base')
93 )
93 )
94
94
95 baserev = self.rev(deltabase)
95 baserev = self.rev(deltabase)
96 # start, size, full unc. size, base (unused), link, p1, p2, node, sidedata_offset (unused), sidedata_size (unused)
96 # start, size, full unc. size, base (unused), link, p1, p2, node, sidedata_offset (unused), sidedata_size (unused)
97 e = (
97 e = (
98 revlog.offset_type(start, flags),
98 revlog.offset_type(start, flags),
99 size,
99 size,
100 -1,
100 -1,
101 baserev,
101 baserev,
102 linkrev,
102 linkrev,
103 self.rev(p1),
103 self.rev(p1),
104 self.rev(p2),
104 self.rev(p2),
105 node,
105 node,
106 0,
106 0,
107 0,
107 0,
108 )
108 )
109 self.index.append(e)
109 self.index.append(e)
110 self.bundlerevs.add(n)
110 self.bundlerevs.add(n)
111 n += 1
111 n += 1
112
112
113 def _chunk(self, rev, df=None):
113 def _chunk(self, rev, df=None):
114 # Warning: in case of bundle, the diff is against what we stored as
114 # Warning: in case of bundle, the diff is against what we stored as
115 # delta base, not against rev - 1
115 # delta base, not against rev - 1
116 # XXX: could use some caching
116 # XXX: could use some caching
117 if rev <= self.repotiprev:
117 if rev <= self.repotiprev:
118 return revlog.revlog._chunk(self, rev)
118 return revlog.revlog._chunk(self, rev)
119 self.bundle.seek(self.start(rev))
119 self.bundle.seek(self.start(rev))
120 return self.bundle.read(self.length(rev))
120 return self.bundle.read(self.length(rev))
121
121
122 def revdiff(self, rev1, rev2):
122 def revdiff(self, rev1, rev2):
123 """return or calculate a delta between two revisions"""
123 """return or calculate a delta between two revisions"""
124 if rev1 > self.repotiprev and rev2 > self.repotiprev:
124 if rev1 > self.repotiprev and rev2 > self.repotiprev:
125 # hot path for bundle
125 # hot path for bundle
126 revb = self.index[rev2][3]
126 revb = self.index[rev2][3]
127 if revb == rev1:
127 if revb == rev1:
128 return self._chunk(rev2)
128 return self._chunk(rev2)
129 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
129 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
130 return revlog.revlog.revdiff(self, rev1, rev2)
130 return revlog.revlog.revdiff(self, rev1, rev2)
131
131
132 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
132 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
133
133
134 def _rawtext(self, node, rev, _df=None):
134 def _rawtext(self, node, rev, _df=None):
135 if rev is None:
135 if rev is None:
136 rev = self.rev(node)
136 rev = self.rev(node)
137 validated = False
137 validated = False
138 rawtext = None
138 rawtext = None
139 chain = []
139 chain = []
140 iterrev = rev
140 iterrev = rev
141 # reconstruct the revision if it is from a changegroup
141 # reconstruct the revision if it is from a changegroup
142 while iterrev > self.repotiprev:
142 while iterrev > self.repotiprev:
143 if self._revisioncache and self._revisioncache[1] == iterrev:
143 if self._revisioncache and self._revisioncache[1] == iterrev:
144 rawtext = self._revisioncache[2]
144 rawtext = self._revisioncache[2]
145 break
145 break
146 chain.append(iterrev)
146 chain.append(iterrev)
147 iterrev = self.index[iterrev][3]
147 iterrev = self.index[iterrev][3]
148 if iterrev == nullrev:
148 if iterrev == nullrev:
149 rawtext = b''
149 rawtext = b''
150 elif rawtext is None:
150 elif rawtext is None:
151 r = super(bundlerevlog, self)._rawtext(
151 r = super(bundlerevlog, self)._rawtext(
152 self.node(iterrev), iterrev, _df=_df
152 self.node(iterrev), iterrev, _df=_df
153 )
153 )
154 __, rawtext, validated = r
154 __, rawtext, validated = r
155 if chain:
155 if chain:
156 validated = False
156 validated = False
157 while chain:
157 while chain:
158 delta = self._chunk(chain.pop())
158 delta = self._chunk(chain.pop())
159 rawtext = mdiff.patches(rawtext, [delta])
159 rawtext = mdiff.patches(rawtext, [delta])
160 return rev, rawtext, validated
160 return rev, rawtext, validated
161
161
162 def addrevision(self, *args, **kwargs):
162 def addrevision(self, *args, **kwargs):
163 raise NotImplementedError
163 raise NotImplementedError
164
164
165 def addgroup(self, *args, **kwargs):
165 def addgroup(self, *args, **kwargs):
166 raise NotImplementedError
166 raise NotImplementedError
167
167
168 def strip(self, *args, **kwargs):
168 def strip(self, *args, **kwargs):
169 raise NotImplementedError
169 raise NotImplementedError
170
170
171 def checksize(self):
171 def checksize(self):
172 raise NotImplementedError
172 raise NotImplementedError
173
173
174
174
175 class bundlechangelog(bundlerevlog, changelog.changelog):
175 class bundlechangelog(bundlerevlog, changelog.changelog):
176 def __init__(self, opener, cgunpacker):
176 def __init__(self, opener, cgunpacker):
177 changelog.changelog.__init__(self, opener)
177 changelog.changelog.__init__(self, opener)
178 linkmapper = lambda x: x
178 linkmapper = lambda x: x
179 bundlerevlog.__init__(
179 bundlerevlog.__init__(
180 self,
180 self,
181 opener,
181 opener,
182 (revlog_constants.KIND_CHANGELOG, None),
182 (revlog_constants.KIND_CHANGELOG, None),
183 self.indexfile,
183 self.indexfile,
184 cgunpacker,
184 cgunpacker,
185 linkmapper,
185 linkmapper,
186 )
186 )
187
187
188
188
189 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
189 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
190 def __init__(
190 def __init__(
191 self,
191 self,
192 nodeconstants,
192 nodeconstants,
193 opener,
193 opener,
194 cgunpacker,
194 cgunpacker,
195 linkmapper,
195 linkmapper,
196 dirlogstarts=None,
196 dirlogstarts=None,
197 dir=b'',
197 dir=b'',
198 ):
198 ):
199 manifest.manifestrevlog.__init__(self, nodeconstants, opener, tree=dir)
199 manifest.manifestrevlog.__init__(self, nodeconstants, opener, tree=dir)
200 bundlerevlog.__init__(
200 bundlerevlog.__init__(
201 self,
201 self,
202 opener,
202 opener,
203 (revlog_constants.KIND_MANIFESTLOG, dir),
203 (revlog_constants.KIND_MANIFESTLOG, dir),
204 self.indexfile,
204 self._revlog.indexfile,
205 cgunpacker,
205 cgunpacker,
206 linkmapper,
206 linkmapper,
207 )
207 )
208 if dirlogstarts is None:
208 if dirlogstarts is None:
209 dirlogstarts = {}
209 dirlogstarts = {}
210 if self.bundle.version == b"03":
210 if self.bundle.version == b"03":
211 dirlogstarts = _getfilestarts(self.bundle)
211 dirlogstarts = _getfilestarts(self.bundle)
212 self._dirlogstarts = dirlogstarts
212 self._dirlogstarts = dirlogstarts
213 self._linkmapper = linkmapper
213 self._linkmapper = linkmapper
214
214
215 def dirlog(self, d):
215 def dirlog(self, d):
216 if d in self._dirlogstarts:
216 if d in self._dirlogstarts:
217 self.bundle.seek(self._dirlogstarts[d])
217 self.bundle.seek(self._dirlogstarts[d])
218 return bundlemanifest(
218 return bundlemanifest(
219 self.nodeconstants,
219 self.nodeconstants,
220 self.opener,
220 self.opener,
221 self.bundle,
221 self.bundle,
222 self._linkmapper,
222 self._linkmapper,
223 self._dirlogstarts,
223 self._dirlogstarts,
224 dir=d,
224 dir=d,
225 )
225 )
226 return super(bundlemanifest, self).dirlog(d)
226 return super(bundlemanifest, self).dirlog(d)
227
227
228
228
229 class bundlefilelog(filelog.filelog):
229 class bundlefilelog(filelog.filelog):
230 def __init__(self, opener, path, cgunpacker, linkmapper):
230 def __init__(self, opener, path, cgunpacker, linkmapper):
231 filelog.filelog.__init__(self, opener, path)
231 filelog.filelog.__init__(self, opener, path)
232 self._revlog = bundlerevlog(
232 self._revlog = bundlerevlog(
233 opener,
233 opener,
234 # XXX should use the unencoded path
234 # XXX should use the unencoded path
235 target=(revlog_constants.KIND_FILELOG, path),
235 target=(revlog_constants.KIND_FILELOG, path),
236 indexfile=self.indexfile,
236 indexfile=self.indexfile,
237 cgunpacker=cgunpacker,
237 cgunpacker=cgunpacker,
238 linkmapper=linkmapper,
238 linkmapper=linkmapper,
239 )
239 )
240
240
241
241
242 class bundlepeer(localrepo.localpeer):
242 class bundlepeer(localrepo.localpeer):
243 def canpush(self):
243 def canpush(self):
244 return False
244 return False
245
245
246
246
247 class bundlephasecache(phases.phasecache):
247 class bundlephasecache(phases.phasecache):
248 def __init__(self, *args, **kwargs):
248 def __init__(self, *args, **kwargs):
249 super(bundlephasecache, self).__init__(*args, **kwargs)
249 super(bundlephasecache, self).__init__(*args, **kwargs)
250 if util.safehasattr(self, 'opener'):
250 if util.safehasattr(self, 'opener'):
251 self.opener = vfsmod.readonlyvfs(self.opener)
251 self.opener = vfsmod.readonlyvfs(self.opener)
252
252
253 def write(self):
253 def write(self):
254 raise NotImplementedError
254 raise NotImplementedError
255
255
256 def _write(self, fp):
256 def _write(self, fp):
257 raise NotImplementedError
257 raise NotImplementedError
258
258
259 def _updateroots(self, phase, newroots, tr):
259 def _updateroots(self, phase, newroots, tr):
260 self.phaseroots[phase] = newroots
260 self.phaseroots[phase] = newroots
261 self.invalidate()
261 self.invalidate()
262 self.dirty = True
262 self.dirty = True
263
263
264
264
265 def _getfilestarts(cgunpacker):
265 def _getfilestarts(cgunpacker):
266 filespos = {}
266 filespos = {}
267 for chunkdata in iter(cgunpacker.filelogheader, {}):
267 for chunkdata in iter(cgunpacker.filelogheader, {}):
268 fname = chunkdata[b'filename']
268 fname = chunkdata[b'filename']
269 filespos[fname] = cgunpacker.tell()
269 filespos[fname] = cgunpacker.tell()
270 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
270 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
271 pass
271 pass
272 return filespos
272 return filespos
273
273
274
274
275 class bundlerepository(object):
275 class bundlerepository(object):
276 """A repository instance that is a union of a local repo and a bundle.
276 """A repository instance that is a union of a local repo and a bundle.
277
277
278 Instances represent a read-only repository composed of a local repository
278 Instances represent a read-only repository composed of a local repository
279 with the contents of a bundle file applied. The repository instance is
279 with the contents of a bundle file applied. The repository instance is
280 conceptually similar to the state of a repository after an
280 conceptually similar to the state of a repository after an
281 ``hg unbundle`` operation. However, the contents of the bundle are never
281 ``hg unbundle`` operation. However, the contents of the bundle are never
282 applied to the actual base repository.
282 applied to the actual base repository.
283
283
284 Instances constructed directly are not usable as repository objects.
284 Instances constructed directly are not usable as repository objects.
285 Use instance() or makebundlerepository() to create instances.
285 Use instance() or makebundlerepository() to create instances.
286 """
286 """
287
287
288 def __init__(self, bundlepath, url, tempparent):
288 def __init__(self, bundlepath, url, tempparent):
289 self._tempparent = tempparent
289 self._tempparent = tempparent
290 self._url = url
290 self._url = url
291
291
292 self.ui.setconfig(b'phases', b'publish', False, b'bundlerepo')
292 self.ui.setconfig(b'phases', b'publish', False, b'bundlerepo')
293
293
294 self.tempfile = None
294 self.tempfile = None
295 f = util.posixfile(bundlepath, b"rb")
295 f = util.posixfile(bundlepath, b"rb")
296 bundle = exchange.readbundle(self.ui, f, bundlepath)
296 bundle = exchange.readbundle(self.ui, f, bundlepath)
297
297
298 if isinstance(bundle, bundle2.unbundle20):
298 if isinstance(bundle, bundle2.unbundle20):
299 self._bundlefile = bundle
299 self._bundlefile = bundle
300 self._cgunpacker = None
300 self._cgunpacker = None
301
301
302 cgpart = None
302 cgpart = None
303 for part in bundle.iterparts(seekable=True):
303 for part in bundle.iterparts(seekable=True):
304 if part.type == b'changegroup':
304 if part.type == b'changegroup':
305 if cgpart:
305 if cgpart:
306 raise NotImplementedError(
306 raise NotImplementedError(
307 b"can't process multiple changegroups"
307 b"can't process multiple changegroups"
308 )
308 )
309 cgpart = part
309 cgpart = part
310
310
311 self._handlebundle2part(bundle, part)
311 self._handlebundle2part(bundle, part)
312
312
313 if not cgpart:
313 if not cgpart:
314 raise error.Abort(_(b"No changegroups found"))
314 raise error.Abort(_(b"No changegroups found"))
315
315
316 # This is required to placate a later consumer, which expects
316 # This is required to placate a later consumer, which expects
317 # the payload offset to be at the beginning of the changegroup.
317 # the payload offset to be at the beginning of the changegroup.
318 # We need to do this after the iterparts() generator advances
318 # We need to do this after the iterparts() generator advances
319 # because iterparts() will seek to end of payload after the
319 # because iterparts() will seek to end of payload after the
320 # generator returns control to iterparts().
320 # generator returns control to iterparts().
321 cgpart.seek(0, os.SEEK_SET)
321 cgpart.seek(0, os.SEEK_SET)
322
322
323 elif isinstance(bundle, changegroup.cg1unpacker):
323 elif isinstance(bundle, changegroup.cg1unpacker):
324 if bundle.compressed():
324 if bundle.compressed():
325 f = self._writetempbundle(
325 f = self._writetempbundle(
326 bundle.read, b'.hg10un', header=b'HG10UN'
326 bundle.read, b'.hg10un', header=b'HG10UN'
327 )
327 )
328 bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
328 bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
329
329
330 self._bundlefile = bundle
330 self._bundlefile = bundle
331 self._cgunpacker = bundle
331 self._cgunpacker = bundle
332 else:
332 else:
333 raise error.Abort(
333 raise error.Abort(
334 _(b'bundle type %s cannot be read') % type(bundle)
334 _(b'bundle type %s cannot be read') % type(bundle)
335 )
335 )
336
336
337 # dict with the mapping 'filename' -> position in the changegroup.
337 # dict with the mapping 'filename' -> position in the changegroup.
338 self._cgfilespos = {}
338 self._cgfilespos = {}
339
339
340 self.firstnewrev = self.changelog.repotiprev + 1
340 self.firstnewrev = self.changelog.repotiprev + 1
341 phases.retractboundary(
341 phases.retractboundary(
342 self,
342 self,
343 None,
343 None,
344 phases.draft,
344 phases.draft,
345 [ctx.node() for ctx in self[self.firstnewrev :]],
345 [ctx.node() for ctx in self[self.firstnewrev :]],
346 )
346 )
347
347
348 def _handlebundle2part(self, bundle, part):
348 def _handlebundle2part(self, bundle, part):
349 if part.type != b'changegroup':
349 if part.type != b'changegroup':
350 return
350 return
351
351
352 cgstream = part
352 cgstream = part
353 version = part.params.get(b'version', b'01')
353 version = part.params.get(b'version', b'01')
354 legalcgvers = changegroup.supportedincomingversions(self)
354 legalcgvers = changegroup.supportedincomingversions(self)
355 if version not in legalcgvers:
355 if version not in legalcgvers:
356 msg = _(b'Unsupported changegroup version: %s')
356 msg = _(b'Unsupported changegroup version: %s')
357 raise error.Abort(msg % version)
357 raise error.Abort(msg % version)
358 if bundle.compressed():
358 if bundle.compressed():
359 cgstream = self._writetempbundle(part.read, b'.cg%sun' % version)
359 cgstream = self._writetempbundle(part.read, b'.cg%sun' % version)
360
360
361 self._cgunpacker = changegroup.getunbundler(version, cgstream, b'UN')
361 self._cgunpacker = changegroup.getunbundler(version, cgstream, b'UN')
362
362
363 def _writetempbundle(self, readfn, suffix, header=b''):
363 def _writetempbundle(self, readfn, suffix, header=b''):
364 """Write a temporary file to disk"""
364 """Write a temporary file to disk"""
365 fdtemp, temp = self.vfs.mkstemp(prefix=b"hg-bundle-", suffix=suffix)
365 fdtemp, temp = self.vfs.mkstemp(prefix=b"hg-bundle-", suffix=suffix)
366 self.tempfile = temp
366 self.tempfile = temp
367
367
368 with os.fdopen(fdtemp, 'wb') as fptemp:
368 with os.fdopen(fdtemp, 'wb') as fptemp:
369 fptemp.write(header)
369 fptemp.write(header)
370 while True:
370 while True:
371 chunk = readfn(2 ** 18)
371 chunk = readfn(2 ** 18)
372 if not chunk:
372 if not chunk:
373 break
373 break
374 fptemp.write(chunk)
374 fptemp.write(chunk)
375
375
376 return self.vfs.open(self.tempfile, mode=b"rb")
376 return self.vfs.open(self.tempfile, mode=b"rb")
377
377
378 @localrepo.unfilteredpropertycache
378 @localrepo.unfilteredpropertycache
379 def _phasecache(self):
379 def _phasecache(self):
380 return bundlephasecache(self, self._phasedefaults)
380 return bundlephasecache(self, self._phasedefaults)
381
381
382 @localrepo.unfilteredpropertycache
382 @localrepo.unfilteredpropertycache
383 def changelog(self):
383 def changelog(self):
384 # consume the header if it exists
384 # consume the header if it exists
385 self._cgunpacker.changelogheader()
385 self._cgunpacker.changelogheader()
386 c = bundlechangelog(self.svfs, self._cgunpacker)
386 c = bundlechangelog(self.svfs, self._cgunpacker)
387 self.manstart = self._cgunpacker.tell()
387 self.manstart = self._cgunpacker.tell()
388 return c
388 return c
389
389
390 def _refreshchangelog(self):
390 def _refreshchangelog(self):
391 # changelog for bundle repo are not filecache, this method is not
391 # changelog for bundle repo are not filecache, this method is not
392 # applicable.
392 # applicable.
393 pass
393 pass
394
394
395 @localrepo.unfilteredpropertycache
395 @localrepo.unfilteredpropertycache
396 def manifestlog(self):
396 def manifestlog(self):
397 self._cgunpacker.seek(self.manstart)
397 self._cgunpacker.seek(self.manstart)
398 # consume the header if it exists
398 # consume the header if it exists
399 self._cgunpacker.manifestheader()
399 self._cgunpacker.manifestheader()
400 linkmapper = self.unfiltered().changelog.rev
400 linkmapper = self.unfiltered().changelog.rev
401 rootstore = bundlemanifest(
401 rootstore = bundlemanifest(
402 self.nodeconstants, self.svfs, self._cgunpacker, linkmapper
402 self.nodeconstants, self.svfs, self._cgunpacker, linkmapper
403 )
403 )
404 self.filestart = self._cgunpacker.tell()
404 self.filestart = self._cgunpacker.tell()
405
405
406 return manifest.manifestlog(
406 return manifest.manifestlog(
407 self.svfs, self, rootstore, self.narrowmatch()
407 self.svfs, self, rootstore, self.narrowmatch()
408 )
408 )
409
409
410 def _consumemanifest(self):
410 def _consumemanifest(self):
411 """Consumes the manifest portion of the bundle, setting filestart so the
411 """Consumes the manifest portion of the bundle, setting filestart so the
412 file portion can be read."""
412 file portion can be read."""
413 self._cgunpacker.seek(self.manstart)
413 self._cgunpacker.seek(self.manstart)
414 self._cgunpacker.manifestheader()
414 self._cgunpacker.manifestheader()
415 for delta in self._cgunpacker.deltaiter():
415 for delta in self._cgunpacker.deltaiter():
416 pass
416 pass
417 self.filestart = self._cgunpacker.tell()
417 self.filestart = self._cgunpacker.tell()
418
418
419 @localrepo.unfilteredpropertycache
419 @localrepo.unfilteredpropertycache
420 def manstart(self):
420 def manstart(self):
421 self.changelog
421 self.changelog
422 return self.manstart
422 return self.manstart
423
423
424 @localrepo.unfilteredpropertycache
424 @localrepo.unfilteredpropertycache
425 def filestart(self):
425 def filestart(self):
426 self.manifestlog
426 self.manifestlog
427
427
428 # If filestart was not set by self.manifestlog, that means the
428 # If filestart was not set by self.manifestlog, that means the
429 # manifestlog implementation did not consume the manifests from the
429 # manifestlog implementation did not consume the manifests from the
430 # changegroup (ex: it might be consuming trees from a separate bundle2
430 # changegroup (ex: it might be consuming trees from a separate bundle2
431 # part instead). So we need to manually consume it.
431 # part instead). So we need to manually consume it.
432 if 'filestart' not in self.__dict__:
432 if 'filestart' not in self.__dict__:
433 self._consumemanifest()
433 self._consumemanifest()
434
434
435 return self.filestart
435 return self.filestart
436
436
437 def url(self):
437 def url(self):
438 return self._url
438 return self._url
439
439
440 def file(self, f):
440 def file(self, f):
441 if not self._cgfilespos:
441 if not self._cgfilespos:
442 self._cgunpacker.seek(self.filestart)
442 self._cgunpacker.seek(self.filestart)
443 self._cgfilespos = _getfilestarts(self._cgunpacker)
443 self._cgfilespos = _getfilestarts(self._cgunpacker)
444
444
445 if f in self._cgfilespos:
445 if f in self._cgfilespos:
446 self._cgunpacker.seek(self._cgfilespos[f])
446 self._cgunpacker.seek(self._cgfilespos[f])
447 linkmapper = self.unfiltered().changelog.rev
447 linkmapper = self.unfiltered().changelog.rev
448 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
448 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
449 else:
449 else:
450 return super(bundlerepository, self).file(f)
450 return super(bundlerepository, self).file(f)
451
451
452 def close(self):
452 def close(self):
453 """Close assigned bundle file immediately."""
453 """Close assigned bundle file immediately."""
454 self._bundlefile.close()
454 self._bundlefile.close()
455 if self.tempfile is not None:
455 if self.tempfile is not None:
456 self.vfs.unlink(self.tempfile)
456 self.vfs.unlink(self.tempfile)
457 if self._tempparent:
457 if self._tempparent:
458 shutil.rmtree(self._tempparent, True)
458 shutil.rmtree(self._tempparent, True)
459
459
460 def cancopy(self):
460 def cancopy(self):
461 return False
461 return False
462
462
463 def peer(self):
463 def peer(self):
464 return bundlepeer(self)
464 return bundlepeer(self)
465
465
466 def getcwd(self):
466 def getcwd(self):
467 return encoding.getcwd() # always outside the repo
467 return encoding.getcwd() # always outside the repo
468
468
469 # Check if parents exist in localrepo before setting
469 # Check if parents exist in localrepo before setting
470 def setparents(self, p1, p2=None):
470 def setparents(self, p1, p2=None):
471 if p2 is None:
471 if p2 is None:
472 p2 = self.nullid
472 p2 = self.nullid
473 p1rev = self.changelog.rev(p1)
473 p1rev = self.changelog.rev(p1)
474 p2rev = self.changelog.rev(p2)
474 p2rev = self.changelog.rev(p2)
475 msg = _(b"setting parent to node %s that only exists in the bundle\n")
475 msg = _(b"setting parent to node %s that only exists in the bundle\n")
476 if self.changelog.repotiprev < p1rev:
476 if self.changelog.repotiprev < p1rev:
477 self.ui.warn(msg % hex(p1))
477 self.ui.warn(msg % hex(p1))
478 if self.changelog.repotiprev < p2rev:
478 if self.changelog.repotiprev < p2rev:
479 self.ui.warn(msg % hex(p2))
479 self.ui.warn(msg % hex(p2))
480 return super(bundlerepository, self).setparents(p1, p2)
480 return super(bundlerepository, self).setparents(p1, p2)
481
481
482
482
483 def instance(ui, path, create, intents=None, createopts=None):
483 def instance(ui, path, create, intents=None, createopts=None):
484 if create:
484 if create:
485 raise error.Abort(_(b'cannot create new bundle repository'))
485 raise error.Abort(_(b'cannot create new bundle repository'))
486 # internal config: bundle.mainreporoot
486 # internal config: bundle.mainreporoot
487 parentpath = ui.config(b"bundle", b"mainreporoot")
487 parentpath = ui.config(b"bundle", b"mainreporoot")
488 if not parentpath:
488 if not parentpath:
489 # try to find the correct path to the working directory repo
489 # try to find the correct path to the working directory repo
490 parentpath = cmdutil.findrepo(encoding.getcwd())
490 parentpath = cmdutil.findrepo(encoding.getcwd())
491 if parentpath is None:
491 if parentpath is None:
492 parentpath = b''
492 parentpath = b''
493 if parentpath:
493 if parentpath:
494 # Try to make the full path relative so we get a nice, short URL.
494 # Try to make the full path relative so we get a nice, short URL.
495 # In particular, we don't want temp dir names in test outputs.
495 # In particular, we don't want temp dir names in test outputs.
496 cwd = encoding.getcwd()
496 cwd = encoding.getcwd()
497 if parentpath == cwd:
497 if parentpath == cwd:
498 parentpath = b''
498 parentpath = b''
499 else:
499 else:
500 cwd = pathutil.normasprefix(cwd)
500 cwd = pathutil.normasprefix(cwd)
501 if parentpath.startswith(cwd):
501 if parentpath.startswith(cwd):
502 parentpath = parentpath[len(cwd) :]
502 parentpath = parentpath[len(cwd) :]
503 u = urlutil.url(path)
503 u = urlutil.url(path)
504 path = u.localpath()
504 path = u.localpath()
505 if u.scheme == b'bundle':
505 if u.scheme == b'bundle':
506 s = path.split(b"+", 1)
506 s = path.split(b"+", 1)
507 if len(s) == 1:
507 if len(s) == 1:
508 repopath, bundlename = parentpath, s[0]
508 repopath, bundlename = parentpath, s[0]
509 else:
509 else:
510 repopath, bundlename = s
510 repopath, bundlename = s
511 else:
511 else:
512 repopath, bundlename = parentpath, path
512 repopath, bundlename = parentpath, path
513
513
514 return makebundlerepository(ui, repopath, bundlename)
514 return makebundlerepository(ui, repopath, bundlename)
515
515
516
516
517 def makebundlerepository(ui, repopath, bundlepath):
517 def makebundlerepository(ui, repopath, bundlepath):
518 """Make a bundle repository object based on repo and bundle paths."""
518 """Make a bundle repository object based on repo and bundle paths."""
519 if repopath:
519 if repopath:
520 url = b'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
520 url = b'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
521 else:
521 else:
522 url = b'bundle:%s' % bundlepath
522 url = b'bundle:%s' % bundlepath
523
523
524 # Because we can't make any guarantees about the type of the base
524 # Because we can't make any guarantees about the type of the base
525 # repository, we can't have a static class representing the bundle
525 # repository, we can't have a static class representing the bundle
526 # repository. We also can't make any guarantees about how to even
526 # repository. We also can't make any guarantees about how to even
527 # call the base repository's constructor!
527 # call the base repository's constructor!
528 #
528 #
529 # So, our strategy is to go through ``localrepo.instance()`` to construct
529 # So, our strategy is to go through ``localrepo.instance()`` to construct
530 # a repo instance. Then, we dynamically create a new type derived from
530 # a repo instance. Then, we dynamically create a new type derived from
531 # both it and our ``bundlerepository`` class which overrides some
531 # both it and our ``bundlerepository`` class which overrides some
532 # functionality. We then change the type of the constructed repository
532 # functionality. We then change the type of the constructed repository
533 # to this new type and initialize the bundle-specific bits of it.
533 # to this new type and initialize the bundle-specific bits of it.
534
534
535 try:
535 try:
536 repo = localrepo.instance(ui, repopath, create=False)
536 repo = localrepo.instance(ui, repopath, create=False)
537 tempparent = None
537 tempparent = None
538 except error.RepoError:
538 except error.RepoError:
539 tempparent = pycompat.mkdtemp()
539 tempparent = pycompat.mkdtemp()
540 try:
540 try:
541 repo = localrepo.instance(ui, tempparent, create=True)
541 repo = localrepo.instance(ui, tempparent, create=True)
542 except Exception:
542 except Exception:
543 shutil.rmtree(tempparent)
543 shutil.rmtree(tempparent)
544 raise
544 raise
545
545
546 class derivedbundlerepository(bundlerepository, repo.__class__):
546 class derivedbundlerepository(bundlerepository, repo.__class__):
547 pass
547 pass
548
548
549 repo.__class__ = derivedbundlerepository
549 repo.__class__ = derivedbundlerepository
550 bundlerepository.__init__(repo, bundlepath, url, tempparent)
550 bundlerepository.__init__(repo, bundlepath, url, tempparent)
551
551
552 return repo
552 return repo
553
553
554
554
555 class bundletransactionmanager(object):
555 class bundletransactionmanager(object):
556 def transaction(self):
556 def transaction(self):
557 return None
557 return None
558
558
559 def close(self):
559 def close(self):
560 raise NotImplementedError
560 raise NotImplementedError
561
561
562 def release(self):
562 def release(self):
563 raise NotImplementedError
563 raise NotImplementedError
564
564
565
565
566 def getremotechanges(
566 def getremotechanges(
567 ui, repo, peer, onlyheads=None, bundlename=None, force=False
567 ui, repo, peer, onlyheads=None, bundlename=None, force=False
568 ):
568 ):
569 """obtains a bundle of changes incoming from peer
569 """obtains a bundle of changes incoming from peer
570
570
571 "onlyheads" restricts the returned changes to those reachable from the
571 "onlyheads" restricts the returned changes to those reachable from the
572 specified heads.
572 specified heads.
573 "bundlename", if given, stores the bundle to this file path permanently;
573 "bundlename", if given, stores the bundle to this file path permanently;
574 otherwise it's stored to a temp file and gets deleted again when you call
574 otherwise it's stored to a temp file and gets deleted again when you call
575 the returned "cleanupfn".
575 the returned "cleanupfn".
576 "force" indicates whether to proceed on unrelated repos.
576 "force" indicates whether to proceed on unrelated repos.
577
577
578 Returns a tuple (local, csets, cleanupfn):
578 Returns a tuple (local, csets, cleanupfn):
579
579
580 "local" is a local repo from which to obtain the actual incoming
580 "local" is a local repo from which to obtain the actual incoming
581 changesets; it is a bundlerepo for the obtained bundle when the
581 changesets; it is a bundlerepo for the obtained bundle when the
582 original "peer" is remote.
582 original "peer" is remote.
583 "csets" lists the incoming changeset node ids.
583 "csets" lists the incoming changeset node ids.
584 "cleanupfn" must be called without arguments when you're done processing
584 "cleanupfn" must be called without arguments when you're done processing
585 the changes; it closes both the original "peer" and the one returned
585 the changes; it closes both the original "peer" and the one returned
586 here.
586 here.
587 """
587 """
588 tmp = discovery.findcommonincoming(repo, peer, heads=onlyheads, force=force)
588 tmp = discovery.findcommonincoming(repo, peer, heads=onlyheads, force=force)
589 common, incoming, rheads = tmp
589 common, incoming, rheads = tmp
590 if not incoming:
590 if not incoming:
591 try:
591 try:
592 if bundlename:
592 if bundlename:
593 os.unlink(bundlename)
593 os.unlink(bundlename)
594 except OSError:
594 except OSError:
595 pass
595 pass
596 return repo, [], peer.close
596 return repo, [], peer.close
597
597
598 commonset = set(common)
598 commonset = set(common)
599 rheads = [x for x in rheads if x not in commonset]
599 rheads = [x for x in rheads if x not in commonset]
600
600
601 bundle = None
601 bundle = None
602 bundlerepo = None
602 bundlerepo = None
603 localrepo = peer.local()
603 localrepo = peer.local()
604 if bundlename or not localrepo:
604 if bundlename or not localrepo:
605 # create a bundle (uncompressed if peer repo is not local)
605 # create a bundle (uncompressed if peer repo is not local)
606
606
607 # developer config: devel.legacy.exchange
607 # developer config: devel.legacy.exchange
608 legexc = ui.configlist(b'devel', b'legacy.exchange')
608 legexc = ui.configlist(b'devel', b'legacy.exchange')
609 forcebundle1 = b'bundle2' not in legexc and b'bundle1' in legexc
609 forcebundle1 = b'bundle2' not in legexc and b'bundle1' in legexc
610 canbundle2 = (
610 canbundle2 = (
611 not forcebundle1
611 not forcebundle1
612 and peer.capable(b'getbundle')
612 and peer.capable(b'getbundle')
613 and peer.capable(b'bundle2')
613 and peer.capable(b'bundle2')
614 )
614 )
615 if canbundle2:
615 if canbundle2:
616 with peer.commandexecutor() as e:
616 with peer.commandexecutor() as e:
617 b2 = e.callcommand(
617 b2 = e.callcommand(
618 b'getbundle',
618 b'getbundle',
619 {
619 {
620 b'source': b'incoming',
620 b'source': b'incoming',
621 b'common': common,
621 b'common': common,
622 b'heads': rheads,
622 b'heads': rheads,
623 b'bundlecaps': exchange.caps20to10(
623 b'bundlecaps': exchange.caps20to10(
624 repo, role=b'client'
624 repo, role=b'client'
625 ),
625 ),
626 b'cg': True,
626 b'cg': True,
627 },
627 },
628 ).result()
628 ).result()
629
629
630 fname = bundle = changegroup.writechunks(
630 fname = bundle = changegroup.writechunks(
631 ui, b2._forwardchunks(), bundlename
631 ui, b2._forwardchunks(), bundlename
632 )
632 )
633 else:
633 else:
634 if peer.capable(b'getbundle'):
634 if peer.capable(b'getbundle'):
635 with peer.commandexecutor() as e:
635 with peer.commandexecutor() as e:
636 cg = e.callcommand(
636 cg = e.callcommand(
637 b'getbundle',
637 b'getbundle',
638 {
638 {
639 b'source': b'incoming',
639 b'source': b'incoming',
640 b'common': common,
640 b'common': common,
641 b'heads': rheads,
641 b'heads': rheads,
642 },
642 },
643 ).result()
643 ).result()
644 elif onlyheads is None and not peer.capable(b'changegroupsubset'):
644 elif onlyheads is None and not peer.capable(b'changegroupsubset'):
645 # compat with older servers when pulling all remote heads
645 # compat with older servers when pulling all remote heads
646
646
647 with peer.commandexecutor() as e:
647 with peer.commandexecutor() as e:
648 cg = e.callcommand(
648 cg = e.callcommand(
649 b'changegroup',
649 b'changegroup',
650 {
650 {
651 b'nodes': incoming,
651 b'nodes': incoming,
652 b'source': b'incoming',
652 b'source': b'incoming',
653 },
653 },
654 ).result()
654 ).result()
655
655
656 rheads = None
656 rheads = None
657 else:
657 else:
658 with peer.commandexecutor() as e:
658 with peer.commandexecutor() as e:
659 cg = e.callcommand(
659 cg = e.callcommand(
660 b'changegroupsubset',
660 b'changegroupsubset',
661 {
661 {
662 b'bases': incoming,
662 b'bases': incoming,
663 b'heads': rheads,
663 b'heads': rheads,
664 b'source': b'incoming',
664 b'source': b'incoming',
665 },
665 },
666 ).result()
666 ).result()
667
667
668 if localrepo:
668 if localrepo:
669 bundletype = b"HG10BZ"
669 bundletype = b"HG10BZ"
670 else:
670 else:
671 bundletype = b"HG10UN"
671 bundletype = b"HG10UN"
672 fname = bundle = bundle2.writebundle(ui, cg, bundlename, bundletype)
672 fname = bundle = bundle2.writebundle(ui, cg, bundlename, bundletype)
673 # keep written bundle?
673 # keep written bundle?
674 if bundlename:
674 if bundlename:
675 bundle = None
675 bundle = None
676 if not localrepo:
676 if not localrepo:
677 # use the created uncompressed bundlerepo
677 # use the created uncompressed bundlerepo
678 localrepo = bundlerepo = makebundlerepository(
678 localrepo = bundlerepo = makebundlerepository(
679 repo.baseui, repo.root, fname
679 repo.baseui, repo.root, fname
680 )
680 )
681
681
682 # this repo contains local and peer now, so filter out local again
682 # this repo contains local and peer now, so filter out local again
683 common = repo.heads()
683 common = repo.heads()
684 if localrepo:
684 if localrepo:
685 # Part of common may be remotely filtered
685 # Part of common may be remotely filtered
686 # So use an unfiltered version
686 # So use an unfiltered version
687 # The discovery process probably need cleanup to avoid that
687 # The discovery process probably need cleanup to avoid that
688 localrepo = localrepo.unfiltered()
688 localrepo = localrepo.unfiltered()
689
689
690 csets = localrepo.changelog.findmissing(common, rheads)
690 csets = localrepo.changelog.findmissing(common, rheads)
691
691
692 if bundlerepo:
692 if bundlerepo:
693 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev :]]
693 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev :]]
694
694
695 with peer.commandexecutor() as e:
695 with peer.commandexecutor() as e:
696 remotephases = e.callcommand(
696 remotephases = e.callcommand(
697 b'listkeys',
697 b'listkeys',
698 {
698 {
699 b'namespace': b'phases',
699 b'namespace': b'phases',
700 },
700 },
701 ).result()
701 ).result()
702
702
703 pullop = exchange.pulloperation(bundlerepo, peer, heads=reponodes)
703 pullop = exchange.pulloperation(bundlerepo, peer, heads=reponodes)
704 pullop.trmanager = bundletransactionmanager()
704 pullop.trmanager = bundletransactionmanager()
705 exchange._pullapplyphases(pullop, remotephases)
705 exchange._pullapplyphases(pullop, remotephases)
706
706
707 def cleanup():
707 def cleanup():
708 if bundlerepo:
708 if bundlerepo:
709 bundlerepo.close()
709 bundlerepo.close()
710 if bundle:
710 if bundle:
711 os.unlink(bundle)
711 os.unlink(bundle)
712 peer.close()
712 peer.close()
713
713
714 return (localrepo, csets, cleanup)
714 return (localrepo, csets, cleanup)
@@ -1,1946 +1,1952 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2006 Olivia Mackall <olivia@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 nullrev,
17 nullrev,
18 short,
18 short,
19 )
19 )
20 from .pycompat import open
20 from .pycompat import open
21
21
22 from . import (
22 from . import (
23 error,
23 error,
24 match as matchmod,
24 match as matchmod,
25 mdiff,
25 mdiff,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 requirements,
28 requirements,
29 scmutil,
29 scmutil,
30 util,
30 util,
31 )
31 )
32
32
33 from .interfaces import repository
33 from .interfaces import repository
34 from .revlogutils import sidedata as sidedatamod
34 from .revlogutils import sidedata as sidedatamod
35 from .revlogutils import constants as revlog_constants
35 from .revlogutils import constants as revlog_constants
36 from .utils import storageutil
36 from .utils import storageutil
37
37
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
42
42
43 LFS_REQUIREMENT = b'lfs'
43 LFS_REQUIREMENT = b'lfs'
44
44
45 readexactly = util.readexactly
45 readexactly = util.readexactly
46
46
47
47
48 def getchunk(stream):
48 def getchunk(stream):
49 """return the next chunk from stream as a string"""
49 """return the next chunk from stream as a string"""
50 d = readexactly(stream, 4)
50 d = readexactly(stream, 4)
51 l = struct.unpack(b">l", d)[0]
51 l = struct.unpack(b">l", d)[0]
52 if l <= 4:
52 if l <= 4:
53 if l:
53 if l:
54 raise error.Abort(_(b"invalid chunk length %d") % l)
54 raise error.Abort(_(b"invalid chunk length %d") % l)
55 return b""
55 return b""
56 return readexactly(stream, l - 4)
56 return readexactly(stream, l - 4)
57
57
58
58
59 def chunkheader(length):
59 def chunkheader(length):
60 """return a changegroup chunk header (string)"""
60 """return a changegroup chunk header (string)"""
61 return struct.pack(b">l", length + 4)
61 return struct.pack(b">l", length + 4)
62
62
63
63
64 def closechunk():
64 def closechunk():
65 """return a changegroup chunk header (string) for a zero-length chunk"""
65 """return a changegroup chunk header (string) for a zero-length chunk"""
66 return struct.pack(b">l", 0)
66 return struct.pack(b">l", 0)
67
67
68
68
69 def _fileheader(path):
69 def _fileheader(path):
70 """Obtain a changegroup chunk header for a named path."""
70 """Obtain a changegroup chunk header for a named path."""
71 return chunkheader(len(path)) + path
71 return chunkheader(len(path)) + path
72
72
73
73
74 def writechunks(ui, chunks, filename, vfs=None):
74 def writechunks(ui, chunks, filename, vfs=None):
75 """Write chunks to a file and return its filename.
75 """Write chunks to a file and return its filename.
76
76
77 The stream is assumed to be a bundle file.
77 The stream is assumed to be a bundle file.
78 Existing files will not be overwritten.
78 Existing files will not be overwritten.
79 If no filename is specified, a temporary file is created.
79 If no filename is specified, a temporary file is created.
80 """
80 """
81 fh = None
81 fh = None
82 cleanup = None
82 cleanup = None
83 try:
83 try:
84 if filename:
84 if filename:
85 if vfs:
85 if vfs:
86 fh = vfs.open(filename, b"wb")
86 fh = vfs.open(filename, b"wb")
87 else:
87 else:
88 # Increase default buffer size because default is usually
88 # Increase default buffer size because default is usually
89 # small (4k is common on Linux).
89 # small (4k is common on Linux).
90 fh = open(filename, b"wb", 131072)
90 fh = open(filename, b"wb", 131072)
91 else:
91 else:
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
93 fh = os.fdopen(fd, "wb")
93 fh = os.fdopen(fd, "wb")
94 cleanup = filename
94 cleanup = filename
95 for c in chunks:
95 for c in chunks:
96 fh.write(c)
96 fh.write(c)
97 cleanup = None
97 cleanup = None
98 return filename
98 return filename
99 finally:
99 finally:
100 if fh is not None:
100 if fh is not None:
101 fh.close()
101 fh.close()
102 if cleanup is not None:
102 if cleanup is not None:
103 if filename and vfs:
103 if filename and vfs:
104 vfs.unlink(cleanup)
104 vfs.unlink(cleanup)
105 else:
105 else:
106 os.unlink(cleanup)
106 os.unlink(cleanup)
107
107
108
108
109 class cg1unpacker(object):
109 class cg1unpacker(object):
110 """Unpacker for cg1 changegroup streams.
110 """Unpacker for cg1 changegroup streams.
111
111
112 A changegroup unpacker handles the framing of the revision data in
112 A changegroup unpacker handles the framing of the revision data in
113 the wire format. Most consumers will want to use the apply()
113 the wire format. Most consumers will want to use the apply()
114 method to add the changes from the changegroup to a repository.
114 method to add the changes from the changegroup to a repository.
115
115
116 If you're forwarding a changegroup unmodified to another consumer,
116 If you're forwarding a changegroup unmodified to another consumer,
117 use getchunks(), which returns an iterator of changegroup
117 use getchunks(), which returns an iterator of changegroup
118 chunks. This is mostly useful for cases where you need to know the
118 chunks. This is mostly useful for cases where you need to know the
119 data stream has ended by observing the end of the changegroup.
119 data stream has ended by observing the end of the changegroup.
120
120
121 deltachunk() is useful only if you're applying delta data. Most
121 deltachunk() is useful only if you're applying delta data. Most
122 consumers should prefer apply() instead.
122 consumers should prefer apply() instead.
123
123
124 A few other public methods exist. Those are used only for
124 A few other public methods exist. Those are used only for
125 bundlerepo and some debug commands - their use is discouraged.
125 bundlerepo and some debug commands - their use is discouraged.
126 """
126 """
127
127
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
129 deltaheadersize = deltaheader.size
129 deltaheadersize = deltaheader.size
130 version = b'01'
130 version = b'01'
131 _grouplistcount = 1 # One list of files after the manifests
131 _grouplistcount = 1 # One list of files after the manifests
132
132
133 def __init__(self, fh, alg, extras=None):
133 def __init__(self, fh, alg, extras=None):
134 if alg is None:
134 if alg is None:
135 alg = b'UN'
135 alg = b'UN'
136 if alg not in util.compengines.supportedbundletypes:
136 if alg not in util.compengines.supportedbundletypes:
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
138 if alg == b'BZ':
138 if alg == b'BZ':
139 alg = b'_truncatedBZ'
139 alg = b'_truncatedBZ'
140
140
141 compengine = util.compengines.forbundletype(alg)
141 compengine = util.compengines.forbundletype(alg)
142 self._stream = compengine.decompressorreader(fh)
142 self._stream = compengine.decompressorreader(fh)
143 self._type = alg
143 self._type = alg
144 self.extras = extras or {}
144 self.extras = extras or {}
145 self.callback = None
145 self.callback = None
146
146
147 # These methods (compressed, read, seek, tell) all appear to only
147 # These methods (compressed, read, seek, tell) all appear to only
148 # be used by bundlerepo, but it's a little hard to tell.
148 # be used by bundlerepo, but it's a little hard to tell.
149 def compressed(self):
149 def compressed(self):
150 return self._type is not None and self._type != b'UN'
150 return self._type is not None and self._type != b'UN'
151
151
152 def read(self, l):
152 def read(self, l):
153 return self._stream.read(l)
153 return self._stream.read(l)
154
154
155 def seek(self, pos):
155 def seek(self, pos):
156 return self._stream.seek(pos)
156 return self._stream.seek(pos)
157
157
158 def tell(self):
158 def tell(self):
159 return self._stream.tell()
159 return self._stream.tell()
160
160
161 def close(self):
161 def close(self):
162 return self._stream.close()
162 return self._stream.close()
163
163
164 def _chunklength(self):
164 def _chunklength(self):
165 d = readexactly(self._stream, 4)
165 d = readexactly(self._stream, 4)
166 l = struct.unpack(b">l", d)[0]
166 l = struct.unpack(b">l", d)[0]
167 if l <= 4:
167 if l <= 4:
168 if l:
168 if l:
169 raise error.Abort(_(b"invalid chunk length %d") % l)
169 raise error.Abort(_(b"invalid chunk length %d") % l)
170 return 0
170 return 0
171 if self.callback:
171 if self.callback:
172 self.callback()
172 self.callback()
173 return l - 4
173 return l - 4
174
174
175 def changelogheader(self):
175 def changelogheader(self):
176 """v10 does not have a changelog header chunk"""
176 """v10 does not have a changelog header chunk"""
177 return {}
177 return {}
178
178
179 def manifestheader(self):
179 def manifestheader(self):
180 """v10 does not have a manifest header chunk"""
180 """v10 does not have a manifest header chunk"""
181 return {}
181 return {}
182
182
183 def filelogheader(self):
183 def filelogheader(self):
184 """return the header of the filelogs chunk, v10 only has the filename"""
184 """return the header of the filelogs chunk, v10 only has the filename"""
185 l = self._chunklength()
185 l = self._chunklength()
186 if not l:
186 if not l:
187 return {}
187 return {}
188 fname = readexactly(self._stream, l)
188 fname = readexactly(self._stream, l)
189 return {b'filename': fname}
189 return {b'filename': fname}
190
190
191 def _deltaheader(self, headertuple, prevnode):
191 def _deltaheader(self, headertuple, prevnode):
192 node, p1, p2, cs = headertuple
192 node, p1, p2, cs = headertuple
193 if prevnode is None:
193 if prevnode is None:
194 deltabase = p1
194 deltabase = p1
195 else:
195 else:
196 deltabase = prevnode
196 deltabase = prevnode
197 flags = 0
197 flags = 0
198 protocol_flags = 0
198 protocol_flags = 0
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
200
200
201 def deltachunk(self, prevnode):
201 def deltachunk(self, prevnode):
202 l = self._chunklength()
202 l = self._chunklength()
203 if not l:
203 if not l:
204 return {}
204 return {}
205 headerdata = readexactly(self._stream, self.deltaheadersize)
205 headerdata = readexactly(self._stream, self.deltaheadersize)
206 header = self.deltaheader.unpack(headerdata)
206 header = self.deltaheader.unpack(headerdata)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
208 header = self._deltaheader(header, prevnode)
208 header = self._deltaheader(header, prevnode)
209 node, p1, p2, deltabase, cs, flags, protocol_flags = header
209 node, p1, p2, deltabase, cs, flags, protocol_flags = header
210 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
210 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
211
211
212 def getchunks(self):
212 def getchunks(self):
213 """returns all the chunks contains in the bundle
213 """returns all the chunks contains in the bundle
214
214
215 Used when you need to forward the binary stream to a file or another
215 Used when you need to forward the binary stream to a file or another
216 network API. To do so, it parse the changegroup data, otherwise it will
216 network API. To do so, it parse the changegroup data, otherwise it will
217 block in case of sshrepo because it don't know the end of the stream.
217 block in case of sshrepo because it don't know the end of the stream.
218 """
218 """
219 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
219 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
220 # and a list of filelogs. For changegroup 3, we expect 4 parts:
220 # and a list of filelogs. For changegroup 3, we expect 4 parts:
221 # changelog, manifestlog, a list of tree manifestlogs, and a list of
221 # changelog, manifestlog, a list of tree manifestlogs, and a list of
222 # filelogs.
222 # filelogs.
223 #
223 #
224 # Changelog and manifestlog parts are terminated with empty chunks. The
224 # Changelog and manifestlog parts are terminated with empty chunks. The
225 # tree and file parts are a list of entry sections. Each entry section
225 # tree and file parts are a list of entry sections. Each entry section
226 # is a series of chunks terminating in an empty chunk. The list of these
226 # is a series of chunks terminating in an empty chunk. The list of these
227 # entry sections is terminated in yet another empty chunk, so we know
227 # entry sections is terminated in yet another empty chunk, so we know
228 # we've reached the end of the tree/file list when we reach an empty
228 # we've reached the end of the tree/file list when we reach an empty
229 # chunk that was proceeded by no non-empty chunks.
229 # chunk that was proceeded by no non-empty chunks.
230
230
231 parts = 0
231 parts = 0
232 while parts < 2 + self._grouplistcount:
232 while parts < 2 + self._grouplistcount:
233 noentries = True
233 noentries = True
234 while True:
234 while True:
235 chunk = getchunk(self)
235 chunk = getchunk(self)
236 if not chunk:
236 if not chunk:
237 # The first two empty chunks represent the end of the
237 # The first two empty chunks represent the end of the
238 # changelog and the manifestlog portions. The remaining
238 # changelog and the manifestlog portions. The remaining
239 # empty chunks represent either A) the end of individual
239 # empty chunks represent either A) the end of individual
240 # tree or file entries in the file list, or B) the end of
240 # tree or file entries in the file list, or B) the end of
241 # the entire list. It's the end of the entire list if there
241 # the entire list. It's the end of the entire list if there
242 # were no entries (i.e. noentries is True).
242 # were no entries (i.e. noentries is True).
243 if parts < 2:
243 if parts < 2:
244 parts += 1
244 parts += 1
245 elif noentries:
245 elif noentries:
246 parts += 1
246 parts += 1
247 break
247 break
248 noentries = False
248 noentries = False
249 yield chunkheader(len(chunk))
249 yield chunkheader(len(chunk))
250 pos = 0
250 pos = 0
251 while pos < len(chunk):
251 while pos < len(chunk):
252 next = pos + 2 ** 20
252 next = pos + 2 ** 20
253 yield chunk[pos:next]
253 yield chunk[pos:next]
254 pos = next
254 pos = next
255 yield closechunk()
255 yield closechunk()
256
256
257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
258 self.callback = prog.increment
258 self.callback = prog.increment
259 # no need to check for empty manifest group here:
259 # no need to check for empty manifest group here:
260 # if the result of the merge of 1 and 2 is the same in 3 and 4,
260 # if the result of the merge of 1 and 2 is the same in 3 and 4,
261 # no new manifest will be created and the manifest group will
261 # no new manifest will be created and the manifest group will
262 # be empty during the pull
262 # be empty during the pull
263 self.manifestheader()
263 self.manifestheader()
264 deltas = self.deltaiter()
264 deltas = self.deltaiter()
265 storage = repo.manifestlog.getstorage(b'')
265 storage = repo.manifestlog.getstorage(b'')
266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
267 prog.complete()
267 prog.complete()
268 self.callback = None
268 self.callback = None
269
269
270 def apply(
270 def apply(
271 self,
271 self,
272 repo,
272 repo,
273 tr,
273 tr,
274 srctype,
274 srctype,
275 url,
275 url,
276 targetphase=phases.draft,
276 targetphase=phases.draft,
277 expectedtotal=None,
277 expectedtotal=None,
278 sidedata_categories=None,
278 sidedata_categories=None,
279 ):
279 ):
280 """Add the changegroup returned by source.read() to this repo.
280 """Add the changegroup returned by source.read() to this repo.
281 srctype is a string like 'push', 'pull', or 'unbundle'. url is
281 srctype is a string like 'push', 'pull', or 'unbundle'. url is
282 the URL of the repo where this changegroup is coming from.
282 the URL of the repo where this changegroup is coming from.
283
283
284 Return an integer summarizing the change to this repo:
284 Return an integer summarizing the change to this repo:
285 - nothing changed or no source: 0
285 - nothing changed or no source: 0
286 - more heads than before: 1+added heads (2..n)
286 - more heads than before: 1+added heads (2..n)
287 - fewer heads than before: -1-removed heads (-2..-n)
287 - fewer heads than before: -1-removed heads (-2..-n)
288 - number of heads stays the same: 1
288 - number of heads stays the same: 1
289
289
290 `sidedata_categories` is an optional set of the remote's sidedata wanted
290 `sidedata_categories` is an optional set of the remote's sidedata wanted
291 categories.
291 categories.
292 """
292 """
293 repo = repo.unfiltered()
293 repo = repo.unfiltered()
294
294
295 # Only useful if we're adding sidedata categories. If both peers have
295 # Only useful if we're adding sidedata categories. If both peers have
296 # the same categories, then we simply don't do anything.
296 # the same categories, then we simply don't do anything.
297 adding_sidedata = (
297 adding_sidedata = (
298 requirements.REVLOGV2_REQUIREMENT in repo.requirements
298 requirements.REVLOGV2_REQUIREMENT in repo.requirements
299 and self.version == b'04'
299 and self.version == b'04'
300 and srctype == b'pull'
300 and srctype == b'pull'
301 )
301 )
302 if adding_sidedata:
302 if adding_sidedata:
303 sidedata_helpers = sidedatamod.get_sidedata_helpers(
303 sidedata_helpers = sidedatamod.get_sidedata_helpers(
304 repo,
304 repo,
305 sidedata_categories or set(),
305 sidedata_categories or set(),
306 pull=True,
306 pull=True,
307 )
307 )
308 else:
308 else:
309 sidedata_helpers = None
309 sidedata_helpers = None
310
310
311 def csmap(x):
311 def csmap(x):
312 repo.ui.debug(b"add changeset %s\n" % short(x))
312 repo.ui.debug(b"add changeset %s\n" % short(x))
313 return len(cl)
313 return len(cl)
314
314
315 def revmap(x):
315 def revmap(x):
316 return cl.rev(x)
316 return cl.rev(x)
317
317
318 try:
318 try:
319 # The transaction may already carry source information. In this
319 # The transaction may already carry source information. In this
320 # case we use the top level data. We overwrite the argument
320 # case we use the top level data. We overwrite the argument
321 # because we need to use the top level value (if they exist)
321 # because we need to use the top level value (if they exist)
322 # in this function.
322 # in this function.
323 srctype = tr.hookargs.setdefault(b'source', srctype)
323 srctype = tr.hookargs.setdefault(b'source', srctype)
324 tr.hookargs.setdefault(b'url', url)
324 tr.hookargs.setdefault(b'url', url)
325 repo.hook(
325 repo.hook(
326 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
326 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
327 )
327 )
328
328
329 # write changelog data to temp files so concurrent readers
329 # write changelog data to temp files so concurrent readers
330 # will not see an inconsistent view
330 # will not see an inconsistent view
331 cl = repo.changelog
331 cl = repo.changelog
332 cl.delayupdate(tr)
332 cl.delayupdate(tr)
333 oldheads = set(cl.heads())
333 oldheads = set(cl.heads())
334
334
335 trp = weakref.proxy(tr)
335 trp = weakref.proxy(tr)
336 # pull off the changeset group
336 # pull off the changeset group
337 repo.ui.status(_(b"adding changesets\n"))
337 repo.ui.status(_(b"adding changesets\n"))
338 clstart = len(cl)
338 clstart = len(cl)
339 progress = repo.ui.makeprogress(
339 progress = repo.ui.makeprogress(
340 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
340 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
341 )
341 )
342 self.callback = progress.increment
342 self.callback = progress.increment
343
343
344 efilesset = set()
344 efilesset = set()
345 duprevs = []
345 duprevs = []
346
346
347 def ondupchangelog(cl, rev):
347 def ondupchangelog(cl, rev):
348 if rev < clstart:
348 if rev < clstart:
349 duprevs.append(rev)
349 duprevs.append(rev)
350
350
351 def onchangelog(cl, rev):
351 def onchangelog(cl, rev):
352 ctx = cl.changelogrevision(rev)
352 ctx = cl.changelogrevision(rev)
353 efilesset.update(ctx.files)
353 efilesset.update(ctx.files)
354 repo.register_changeset(rev, ctx)
354 repo.register_changeset(rev, ctx)
355
355
356 self.changelogheader()
356 self.changelogheader()
357 deltas = self.deltaiter()
357 deltas = self.deltaiter()
358 if not cl.addgroup(
358 if not cl.addgroup(
359 deltas,
359 deltas,
360 csmap,
360 csmap,
361 trp,
361 trp,
362 alwayscache=True,
362 alwayscache=True,
363 addrevisioncb=onchangelog,
363 addrevisioncb=onchangelog,
364 duplicaterevisioncb=ondupchangelog,
364 duplicaterevisioncb=ondupchangelog,
365 ):
365 ):
366 repo.ui.develwarn(
366 repo.ui.develwarn(
367 b'applied empty changelog from changegroup',
367 b'applied empty changelog from changegroup',
368 config=b'warn-empty-changegroup',
368 config=b'warn-empty-changegroup',
369 )
369 )
370 efiles = len(efilesset)
370 efiles = len(efilesset)
371 clend = len(cl)
371 clend = len(cl)
372 changesets = clend - clstart
372 changesets = clend - clstart
373 progress.complete()
373 progress.complete()
374 del deltas
374 del deltas
375 # TODO Python 2.7 removal
375 # TODO Python 2.7 removal
376 # del efilesset
376 # del efilesset
377 efilesset = None
377 efilesset = None
378 self.callback = None
378 self.callback = None
379
379
380 # Keep track of the (non-changelog) revlogs we've updated and their
380 # Keep track of the (non-changelog) revlogs we've updated and their
381 # range of new revisions for sidedata rewrite.
381 # range of new revisions for sidedata rewrite.
382 # TODO do something more efficient than keeping the reference to
382 # TODO do something more efficient than keeping the reference to
383 # the revlogs, especially memory-wise.
383 # the revlogs, especially memory-wise.
384 touched_manifests = {}
384 touched_manifests = {}
385 touched_filelogs = {}
385 touched_filelogs = {}
386
386
387 # pull off the manifest group
387 # pull off the manifest group
388 repo.ui.status(_(b"adding manifests\n"))
388 repo.ui.status(_(b"adding manifests\n"))
389 # We know that we'll never have more manifests than we had
389 # We know that we'll never have more manifests than we had
390 # changesets.
390 # changesets.
391 progress = repo.ui.makeprogress(
391 progress = repo.ui.makeprogress(
392 _(b'manifests'), unit=_(b'chunks'), total=changesets
392 _(b'manifests'), unit=_(b'chunks'), total=changesets
393 )
393 )
394 on_manifest_rev = None
394 on_manifest_rev = None
395 if sidedata_helpers:
395 if sidedata_helpers:
396 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
396 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
397
397
398 def on_manifest_rev(manifest, rev):
398 def on_manifest_rev(manifest, rev):
399 range = touched_manifests.get(manifest)
399 range = touched_manifests.get(manifest)
400 if not range:
400 if not range:
401 touched_manifests[manifest] = (rev, rev)
401 touched_manifests[manifest] = (rev, rev)
402 else:
402 else:
403 assert rev == range[1] + 1
403 assert rev == range[1] + 1
404 touched_manifests[manifest] = (range[0], rev)
404 touched_manifests[manifest] = (range[0], rev)
405
405
406 self._unpackmanifests(
406 self._unpackmanifests(
407 repo,
407 repo,
408 revmap,
408 revmap,
409 trp,
409 trp,
410 progress,
410 progress,
411 addrevisioncb=on_manifest_rev,
411 addrevisioncb=on_manifest_rev,
412 )
412 )
413
413
414 needfiles = {}
414 needfiles = {}
415 if repo.ui.configbool(b'server', b'validate'):
415 if repo.ui.configbool(b'server', b'validate'):
416 cl = repo.changelog
416 cl = repo.changelog
417 ml = repo.manifestlog
417 ml = repo.manifestlog
418 # validate incoming csets have their manifests
418 # validate incoming csets have their manifests
419 for cset in pycompat.xrange(clstart, clend):
419 for cset in pycompat.xrange(clstart, clend):
420 mfnode = cl.changelogrevision(cset).manifest
420 mfnode = cl.changelogrevision(cset).manifest
421 mfest = ml[mfnode].readdelta()
421 mfest = ml[mfnode].readdelta()
422 # store file nodes we must see
422 # store file nodes we must see
423 for f, n in pycompat.iteritems(mfest):
423 for f, n in pycompat.iteritems(mfest):
424 needfiles.setdefault(f, set()).add(n)
424 needfiles.setdefault(f, set()).add(n)
425
425
426 on_filelog_rev = None
426 on_filelog_rev = None
427 if sidedata_helpers:
427 if sidedata_helpers:
428 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
428 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
429
429
430 def on_filelog_rev(filelog, rev):
430 def on_filelog_rev(filelog, rev):
431 range = touched_filelogs.get(filelog)
431 range = touched_filelogs.get(filelog)
432 if not range:
432 if not range:
433 touched_filelogs[filelog] = (rev, rev)
433 touched_filelogs[filelog] = (rev, rev)
434 else:
434 else:
435 assert rev == range[1] + 1
435 assert rev == range[1] + 1
436 touched_filelogs[filelog] = (range[0], rev)
436 touched_filelogs[filelog] = (range[0], rev)
437
437
438 # process the files
438 # process the files
439 repo.ui.status(_(b"adding file changes\n"))
439 repo.ui.status(_(b"adding file changes\n"))
440 newrevs, newfiles = _addchangegroupfiles(
440 newrevs, newfiles = _addchangegroupfiles(
441 repo,
441 repo,
442 self,
442 self,
443 revmap,
443 revmap,
444 trp,
444 trp,
445 efiles,
445 efiles,
446 needfiles,
446 needfiles,
447 addrevisioncb=on_filelog_rev,
447 addrevisioncb=on_filelog_rev,
448 )
448 )
449
449
450 if sidedata_helpers:
450 if sidedata_helpers:
451 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
451 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
452 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
452 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
453 for mf, (startrev, endrev) in touched_manifests.items():
453 for mf, (startrev, endrev) in touched_manifests.items():
454 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
454 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
455 for fl, (startrev, endrev) in touched_filelogs.items():
455 for fl, (startrev, endrev) in touched_filelogs.items():
456 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
456 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
457
457
458 # making sure the value exists
458 # making sure the value exists
459 tr.changes.setdefault(b'changegroup-count-changesets', 0)
459 tr.changes.setdefault(b'changegroup-count-changesets', 0)
460 tr.changes.setdefault(b'changegroup-count-revisions', 0)
460 tr.changes.setdefault(b'changegroup-count-revisions', 0)
461 tr.changes.setdefault(b'changegroup-count-files', 0)
461 tr.changes.setdefault(b'changegroup-count-files', 0)
462 tr.changes.setdefault(b'changegroup-count-heads', 0)
462 tr.changes.setdefault(b'changegroup-count-heads', 0)
463
463
464 # some code use bundle operation for internal purpose. They usually
464 # some code use bundle operation for internal purpose. They usually
465 # set `ui.quiet` to do this outside of user sight. Size the report
465 # set `ui.quiet` to do this outside of user sight. Size the report
466 # of such operation now happens at the end of the transaction, that
466 # of such operation now happens at the end of the transaction, that
467 # ui.quiet has not direct effect on the output.
467 # ui.quiet has not direct effect on the output.
468 #
468 #
469 # To preserve this intend use an inelegant hack, we fail to report
469 # To preserve this intend use an inelegant hack, we fail to report
470 # the change if `quiet` is set. We should probably move to
470 # the change if `quiet` is set. We should probably move to
471 # something better, but this is a good first step to allow the "end
471 # something better, but this is a good first step to allow the "end
472 # of transaction report" to pass tests.
472 # of transaction report" to pass tests.
473 if not repo.ui.quiet:
473 if not repo.ui.quiet:
474 tr.changes[b'changegroup-count-changesets'] += changesets
474 tr.changes[b'changegroup-count-changesets'] += changesets
475 tr.changes[b'changegroup-count-revisions'] += newrevs
475 tr.changes[b'changegroup-count-revisions'] += newrevs
476 tr.changes[b'changegroup-count-files'] += newfiles
476 tr.changes[b'changegroup-count-files'] += newfiles
477
477
478 deltaheads = 0
478 deltaheads = 0
479 if oldheads:
479 if oldheads:
480 heads = cl.heads()
480 heads = cl.heads()
481 deltaheads += len(heads) - len(oldheads)
481 deltaheads += len(heads) - len(oldheads)
482 for h in heads:
482 for h in heads:
483 if h not in oldheads and repo[h].closesbranch():
483 if h not in oldheads and repo[h].closesbranch():
484 deltaheads -= 1
484 deltaheads -= 1
485
485
486 # see previous comment about checking ui.quiet
486 # see previous comment about checking ui.quiet
487 if not repo.ui.quiet:
487 if not repo.ui.quiet:
488 tr.changes[b'changegroup-count-heads'] += deltaheads
488 tr.changes[b'changegroup-count-heads'] += deltaheads
489 repo.invalidatevolatilesets()
489 repo.invalidatevolatilesets()
490
490
491 if changesets > 0:
491 if changesets > 0:
492 if b'node' not in tr.hookargs:
492 if b'node' not in tr.hookargs:
493 tr.hookargs[b'node'] = hex(cl.node(clstart))
493 tr.hookargs[b'node'] = hex(cl.node(clstart))
494 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
494 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
495 hookargs = dict(tr.hookargs)
495 hookargs = dict(tr.hookargs)
496 else:
496 else:
497 hookargs = dict(tr.hookargs)
497 hookargs = dict(tr.hookargs)
498 hookargs[b'node'] = hex(cl.node(clstart))
498 hookargs[b'node'] = hex(cl.node(clstart))
499 hookargs[b'node_last'] = hex(cl.node(clend - 1))
499 hookargs[b'node_last'] = hex(cl.node(clend - 1))
500 repo.hook(
500 repo.hook(
501 b'pretxnchangegroup',
501 b'pretxnchangegroup',
502 throw=True,
502 throw=True,
503 **pycompat.strkwargs(hookargs)
503 **pycompat.strkwargs(hookargs)
504 )
504 )
505
505
506 added = pycompat.xrange(clstart, clend)
506 added = pycompat.xrange(clstart, clend)
507 phaseall = None
507 phaseall = None
508 if srctype in (b'push', b'serve'):
508 if srctype in (b'push', b'serve'):
509 # Old servers can not push the boundary themselves.
509 # Old servers can not push the boundary themselves.
510 # New servers won't push the boundary if changeset already
510 # New servers won't push the boundary if changeset already
511 # exists locally as secret
511 # exists locally as secret
512 #
512 #
513 # We should not use added here but the list of all change in
513 # We should not use added here but the list of all change in
514 # the bundle
514 # the bundle
515 if repo.publishing():
515 if repo.publishing():
516 targetphase = phaseall = phases.public
516 targetphase = phaseall = phases.public
517 else:
517 else:
518 # closer target phase computation
518 # closer target phase computation
519
519
520 # Those changesets have been pushed from the
520 # Those changesets have been pushed from the
521 # outside, their phases are going to be pushed
521 # outside, their phases are going to be pushed
522 # alongside. Therefor `targetphase` is
522 # alongside. Therefor `targetphase` is
523 # ignored.
523 # ignored.
524 targetphase = phaseall = phases.draft
524 targetphase = phaseall = phases.draft
525 if added:
525 if added:
526 phases.registernew(repo, tr, targetphase, added)
526 phases.registernew(repo, tr, targetphase, added)
527 if phaseall is not None:
527 if phaseall is not None:
528 if duprevs:
528 if duprevs:
529 duprevs.extend(added)
529 duprevs.extend(added)
530 else:
530 else:
531 duprevs = added
531 duprevs = added
532 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
532 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
533 duprevs = []
533 duprevs = []
534
534
535 if changesets > 0:
535 if changesets > 0:
536
536
537 def runhooks(unused_success):
537 def runhooks(unused_success):
538 # These hooks run when the lock releases, not when the
538 # These hooks run when the lock releases, not when the
539 # transaction closes. So it's possible for the changelog
539 # transaction closes. So it's possible for the changelog
540 # to have changed since we last saw it.
540 # to have changed since we last saw it.
541 if clstart >= len(repo):
541 if clstart >= len(repo):
542 return
542 return
543
543
544 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
544 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
545
545
546 for rev in added:
546 for rev in added:
547 args = hookargs.copy()
547 args = hookargs.copy()
548 args[b'node'] = hex(cl.node(rev))
548 args[b'node'] = hex(cl.node(rev))
549 del args[b'node_last']
549 del args[b'node_last']
550 repo.hook(b"incoming", **pycompat.strkwargs(args))
550 repo.hook(b"incoming", **pycompat.strkwargs(args))
551
551
552 newheads = [h for h in repo.heads() if h not in oldheads]
552 newheads = [h for h in repo.heads() if h not in oldheads]
553 repo.ui.log(
553 repo.ui.log(
554 b"incoming",
554 b"incoming",
555 b"%d incoming changes - new heads: %s\n",
555 b"%d incoming changes - new heads: %s\n",
556 len(added),
556 len(added),
557 b', '.join([hex(c[:6]) for c in newheads]),
557 b', '.join([hex(c[:6]) for c in newheads]),
558 )
558 )
559
559
560 tr.addpostclose(
560 tr.addpostclose(
561 b'changegroup-runhooks-%020i' % clstart,
561 b'changegroup-runhooks-%020i' % clstart,
562 lambda tr: repo._afterlock(runhooks),
562 lambda tr: repo._afterlock(runhooks),
563 )
563 )
564 finally:
564 finally:
565 repo.ui.flush()
565 repo.ui.flush()
566 # never return 0 here:
566 # never return 0 here:
567 if deltaheads < 0:
567 if deltaheads < 0:
568 ret = deltaheads - 1
568 ret = deltaheads - 1
569 else:
569 else:
570 ret = deltaheads + 1
570 ret = deltaheads + 1
571 return ret
571 return ret
572
572
573 def deltaiter(self):
573 def deltaiter(self):
574 """
574 """
575 returns an iterator of the deltas in this changegroup
575 returns an iterator of the deltas in this changegroup
576
576
577 Useful for passing to the underlying storage system to be stored.
577 Useful for passing to the underlying storage system to be stored.
578 """
578 """
579 chain = None
579 chain = None
580 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
580 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
581 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
581 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
582 yield chunkdata
582 yield chunkdata
583 chain = chunkdata[0]
583 chain = chunkdata[0]
584
584
585
585
586 class cg2unpacker(cg1unpacker):
586 class cg2unpacker(cg1unpacker):
587 """Unpacker for cg2 streams.
587 """Unpacker for cg2 streams.
588
588
589 cg2 streams add support for generaldelta, so the delta header
589 cg2 streams add support for generaldelta, so the delta header
590 format is slightly different. All other features about the data
590 format is slightly different. All other features about the data
591 remain the same.
591 remain the same.
592 """
592 """
593
593
594 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
594 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
595 deltaheadersize = deltaheader.size
595 deltaheadersize = deltaheader.size
596 version = b'02'
596 version = b'02'
597
597
598 def _deltaheader(self, headertuple, prevnode):
598 def _deltaheader(self, headertuple, prevnode):
599 node, p1, p2, deltabase, cs = headertuple
599 node, p1, p2, deltabase, cs = headertuple
600 flags = 0
600 flags = 0
601 protocol_flags = 0
601 protocol_flags = 0
602 return node, p1, p2, deltabase, cs, flags, protocol_flags
602 return node, p1, p2, deltabase, cs, flags, protocol_flags
603
603
604
604
605 class cg3unpacker(cg2unpacker):
605 class cg3unpacker(cg2unpacker):
606 """Unpacker for cg3 streams.
606 """Unpacker for cg3 streams.
607
607
608 cg3 streams add support for exchanging treemanifests and revlog
608 cg3 streams add support for exchanging treemanifests and revlog
609 flags. It adds the revlog flags to the delta header and an empty chunk
609 flags. It adds the revlog flags to the delta header and an empty chunk
610 separating manifests and files.
610 separating manifests and files.
611 """
611 """
612
612
613 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
613 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
614 deltaheadersize = deltaheader.size
614 deltaheadersize = deltaheader.size
615 version = b'03'
615 version = b'03'
616 _grouplistcount = 2 # One list of manifests and one list of files
616 _grouplistcount = 2 # One list of manifests and one list of files
617
617
618 def _deltaheader(self, headertuple, prevnode):
618 def _deltaheader(self, headertuple, prevnode):
619 node, p1, p2, deltabase, cs, flags = headertuple
619 node, p1, p2, deltabase, cs, flags = headertuple
620 protocol_flags = 0
620 protocol_flags = 0
621 return node, p1, p2, deltabase, cs, flags, protocol_flags
621 return node, p1, p2, deltabase, cs, flags, protocol_flags
622
622
623 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
623 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
624 super(cg3unpacker, self)._unpackmanifests(
624 super(cg3unpacker, self)._unpackmanifests(
625 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
625 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
626 )
626 )
627 for chunkdata in iter(self.filelogheader, {}):
627 for chunkdata in iter(self.filelogheader, {}):
628 # If we get here, there are directory manifests in the changegroup
628 # If we get here, there are directory manifests in the changegroup
629 d = chunkdata[b"filename"]
629 d = chunkdata[b"filename"]
630 repo.ui.debug(b"adding %s revisions\n" % d)
630 repo.ui.debug(b"adding %s revisions\n" % d)
631 deltas = self.deltaiter()
631 deltas = self.deltaiter()
632 if not repo.manifestlog.getstorage(d).addgroup(
632 if not repo.manifestlog.getstorage(d).addgroup(
633 deltas, revmap, trp, addrevisioncb=addrevisioncb
633 deltas, revmap, trp, addrevisioncb=addrevisioncb
634 ):
634 ):
635 raise error.Abort(_(b"received dir revlog group is empty"))
635 raise error.Abort(_(b"received dir revlog group is empty"))
636
636
637
637
638 class cg4unpacker(cg3unpacker):
638 class cg4unpacker(cg3unpacker):
639 """Unpacker for cg4 streams.
639 """Unpacker for cg4 streams.
640
640
641 cg4 streams add support for exchanging sidedata.
641 cg4 streams add support for exchanging sidedata.
642 """
642 """
643
643
644 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
644 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
645 deltaheadersize = deltaheader.size
645 deltaheadersize = deltaheader.size
646 version = b'04'
646 version = b'04'
647
647
648 def _deltaheader(self, headertuple, prevnode):
648 def _deltaheader(self, headertuple, prevnode):
649 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
649 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
650 return node, p1, p2, deltabase, cs, flags, protocol_flags
650 return node, p1, p2, deltabase, cs, flags, protocol_flags
651
651
652 def deltachunk(self, prevnode):
652 def deltachunk(self, prevnode):
653 res = super(cg4unpacker, self).deltachunk(prevnode)
653 res = super(cg4unpacker, self).deltachunk(prevnode)
654 if not res:
654 if not res:
655 return res
655 return res
656
656
657 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
657 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
658
658
659 sidedata = {}
659 sidedata = {}
660 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
660 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
661 sidedata_raw = getchunk(self._stream)
661 sidedata_raw = getchunk(self._stream)
662 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
662 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
663
663
664 return node, p1, p2, cs, deltabase, delta, flags, sidedata
664 return node, p1, p2, cs, deltabase, delta, flags, sidedata
665
665
666
666
667 class headerlessfixup(object):
667 class headerlessfixup(object):
668 def __init__(self, fh, h):
668 def __init__(self, fh, h):
669 self._h = h
669 self._h = h
670 self._fh = fh
670 self._fh = fh
671
671
672 def read(self, n):
672 def read(self, n):
673 if self._h:
673 if self._h:
674 d, self._h = self._h[:n], self._h[n:]
674 d, self._h = self._h[:n], self._h[n:]
675 if len(d) < n:
675 if len(d) < n:
676 d += readexactly(self._fh, n - len(d))
676 d += readexactly(self._fh, n - len(d))
677 return d
677 return d
678 return readexactly(self._fh, n)
678 return readexactly(self._fh, n)
679
679
680
680
681 def _revisiondeltatochunks(repo, delta, headerfn):
681 def _revisiondeltatochunks(repo, delta, headerfn):
682 """Serialize a revisiondelta to changegroup chunks."""
682 """Serialize a revisiondelta to changegroup chunks."""
683
683
684 # The captured revision delta may be encoded as a delta against
684 # The captured revision delta may be encoded as a delta against
685 # a base revision or as a full revision. The changegroup format
685 # a base revision or as a full revision. The changegroup format
686 # requires that everything on the wire be deltas. So for full
686 # requires that everything on the wire be deltas. So for full
687 # revisions, we need to invent a header that says to rewrite
687 # revisions, we need to invent a header that says to rewrite
688 # data.
688 # data.
689
689
690 if delta.delta is not None:
690 if delta.delta is not None:
691 prefix, data = b'', delta.delta
691 prefix, data = b'', delta.delta
692 elif delta.basenode == repo.nullid:
692 elif delta.basenode == repo.nullid:
693 data = delta.revision
693 data = delta.revision
694 prefix = mdiff.trivialdiffheader(len(data))
694 prefix = mdiff.trivialdiffheader(len(data))
695 else:
695 else:
696 data = delta.revision
696 data = delta.revision
697 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
697 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
698
698
699 meta = headerfn(delta)
699 meta = headerfn(delta)
700
700
701 yield chunkheader(len(meta) + len(prefix) + len(data))
701 yield chunkheader(len(meta) + len(prefix) + len(data))
702 yield meta
702 yield meta
703 if prefix:
703 if prefix:
704 yield prefix
704 yield prefix
705 yield data
705 yield data
706
706
707 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
707 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
708 # Need a separate chunk for sidedata to be able to differentiate
708 # Need a separate chunk for sidedata to be able to differentiate
709 # "raw delta" length and sidedata length
709 # "raw delta" length and sidedata length
710 sidedata = delta.sidedata
710 sidedata = delta.sidedata
711 yield chunkheader(len(sidedata))
711 yield chunkheader(len(sidedata))
712 yield sidedata
712 yield sidedata
713
713
714
714
715 def _sortnodesellipsis(store, nodes, cl, lookup):
715 def _sortnodesellipsis(store, nodes, cl, lookup):
716 """Sort nodes for changegroup generation."""
716 """Sort nodes for changegroup generation."""
717 # Ellipses serving mode.
717 # Ellipses serving mode.
718 #
718 #
719 # In a perfect world, we'd generate better ellipsis-ified graphs
719 # In a perfect world, we'd generate better ellipsis-ified graphs
720 # for non-changelog revlogs. In practice, we haven't started doing
720 # for non-changelog revlogs. In practice, we haven't started doing
721 # that yet, so the resulting DAGs for the manifestlog and filelogs
721 # that yet, so the resulting DAGs for the manifestlog and filelogs
722 # are actually full of bogus parentage on all the ellipsis
722 # are actually full of bogus parentage on all the ellipsis
723 # nodes. This has the side effect that, while the contents are
723 # nodes. This has the side effect that, while the contents are
724 # correct, the individual DAGs might be completely out of whack in
724 # correct, the individual DAGs might be completely out of whack in
725 # a case like 882681bc3166 and its ancestors (back about 10
725 # a case like 882681bc3166 and its ancestors (back about 10
726 # revisions or so) in the main hg repo.
726 # revisions or so) in the main hg repo.
727 #
727 #
728 # The one invariant we *know* holds is that the new (potentially
728 # The one invariant we *know* holds is that the new (potentially
729 # bogus) DAG shape will be valid if we order the nodes in the
729 # bogus) DAG shape will be valid if we order the nodes in the
730 # order that they're introduced in dramatis personae by the
730 # order that they're introduced in dramatis personae by the
731 # changelog, so what we do is we sort the non-changelog histories
731 # changelog, so what we do is we sort the non-changelog histories
732 # by the order in which they are used by the changelog.
732 # by the order in which they are used by the changelog.
733 key = lambda n: cl.rev(lookup(n))
733 key = lambda n: cl.rev(lookup(n))
734 return sorted(nodes, key=key)
734 return sorted(nodes, key=key)
735
735
736
736
737 def _resolvenarrowrevisioninfo(
737 def _resolvenarrowrevisioninfo(
738 cl,
738 cl,
739 store,
739 store,
740 ischangelog,
740 ischangelog,
741 rev,
741 rev,
742 linkrev,
742 linkrev,
743 linknode,
743 linknode,
744 clrevtolocalrev,
744 clrevtolocalrev,
745 fullclnodes,
745 fullclnodes,
746 precomputedellipsis,
746 precomputedellipsis,
747 ):
747 ):
748 linkparents = precomputedellipsis[linkrev]
748 linkparents = precomputedellipsis[linkrev]
749
749
750 def local(clrev):
750 def local(clrev):
751 """Turn a changelog revnum into a local revnum.
751 """Turn a changelog revnum into a local revnum.
752
752
753 The ellipsis dag is stored as revnums on the changelog,
753 The ellipsis dag is stored as revnums on the changelog,
754 but when we're producing ellipsis entries for
754 but when we're producing ellipsis entries for
755 non-changelog revlogs, we need to turn those numbers into
755 non-changelog revlogs, we need to turn those numbers into
756 something local. This does that for us, and during the
756 something local. This does that for us, and during the
757 changelog sending phase will also expand the stored
757 changelog sending phase will also expand the stored
758 mappings as needed.
758 mappings as needed.
759 """
759 """
760 if clrev == nullrev:
760 if clrev == nullrev:
761 return nullrev
761 return nullrev
762
762
763 if ischangelog:
763 if ischangelog:
764 return clrev
764 return clrev
765
765
766 # Walk the ellipsis-ized changelog breadth-first looking for a
766 # Walk the ellipsis-ized changelog breadth-first looking for a
767 # change that has been linked from the current revlog.
767 # change that has been linked from the current revlog.
768 #
768 #
769 # For a flat manifest revlog only a single step should be necessary
769 # For a flat manifest revlog only a single step should be necessary
770 # as all relevant changelog entries are relevant to the flat
770 # as all relevant changelog entries are relevant to the flat
771 # manifest.
771 # manifest.
772 #
772 #
773 # For a filelog or tree manifest dirlog however not every changelog
773 # For a filelog or tree manifest dirlog however not every changelog
774 # entry will have been relevant, so we need to skip some changelog
774 # entry will have been relevant, so we need to skip some changelog
775 # nodes even after ellipsis-izing.
775 # nodes even after ellipsis-izing.
776 walk = [clrev]
776 walk = [clrev]
777 while walk:
777 while walk:
778 p = walk[0]
778 p = walk[0]
779 walk = walk[1:]
779 walk = walk[1:]
780 if p in clrevtolocalrev:
780 if p in clrevtolocalrev:
781 return clrevtolocalrev[p]
781 return clrevtolocalrev[p]
782 elif p in fullclnodes:
782 elif p in fullclnodes:
783 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
783 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
784 elif p in precomputedellipsis:
784 elif p in precomputedellipsis:
785 walk.extend(
785 walk.extend(
786 [pp for pp in precomputedellipsis[p] if pp != nullrev]
786 [pp for pp in precomputedellipsis[p] if pp != nullrev]
787 )
787 )
788 else:
788 else:
789 # In this case, we've got an ellipsis with parents
789 # In this case, we've got an ellipsis with parents
790 # outside the current bundle (likely an
790 # outside the current bundle (likely an
791 # incremental pull). We "know" that we can use the
791 # incremental pull). We "know" that we can use the
792 # value of this same revlog at whatever revision
792 # value of this same revlog at whatever revision
793 # is pointed to by linknode. "Know" is in scare
793 # is pointed to by linknode. "Know" is in scare
794 # quotes because I haven't done enough examination
794 # quotes because I haven't done enough examination
795 # of edge cases to convince myself this is really
795 # of edge cases to convince myself this is really
796 # a fact - it works for all the (admittedly
796 # a fact - it works for all the (admittedly
797 # thorough) cases in our testsuite, but I would be
797 # thorough) cases in our testsuite, but I would be
798 # somewhat unsurprised to find a case in the wild
798 # somewhat unsurprised to find a case in the wild
799 # where this breaks down a bit. That said, I don't
799 # where this breaks down a bit. That said, I don't
800 # know if it would hurt anything.
800 # know if it would hurt anything.
801 for i in pycompat.xrange(rev, 0, -1):
801 for i in pycompat.xrange(rev, 0, -1):
802 if store.linkrev(i) == clrev:
802 if store.linkrev(i) == clrev:
803 return i
803 return i
804 # We failed to resolve a parent for this node, so
804 # We failed to resolve a parent for this node, so
805 # we crash the changegroup construction.
805 # we crash the changegroup construction.
806 if util.safehasattr(store, 'target'):
807 target = store.indexfile
808 else:
809 # some revlog not actually a revlog
810 target = store._revlog.indexfile
811
806 raise error.Abort(
812 raise error.Abort(
807 b"unable to resolve parent while packing '%s' %r"
813 b"unable to resolve parent while packing '%s' %r"
808 b' for changeset %r' % (store.indexfile, rev, clrev)
814 b' for changeset %r' % (target, rev, clrev)
809 )
815 )
810
816
811 return nullrev
817 return nullrev
812
818
813 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
819 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
814 p1, p2 = nullrev, nullrev
820 p1, p2 = nullrev, nullrev
815 elif len(linkparents) == 1:
821 elif len(linkparents) == 1:
816 (p1,) = sorted(local(p) for p in linkparents)
822 (p1,) = sorted(local(p) for p in linkparents)
817 p2 = nullrev
823 p2 = nullrev
818 else:
824 else:
819 p1, p2 = sorted(local(p) for p in linkparents)
825 p1, p2 = sorted(local(p) for p in linkparents)
820
826
821 p1node, p2node = store.node(p1), store.node(p2)
827 p1node, p2node = store.node(p1), store.node(p2)
822
828
823 return p1node, p2node, linknode
829 return p1node, p2node, linknode
824
830
825
831
826 def deltagroup(
832 def deltagroup(
827 repo,
833 repo,
828 store,
834 store,
829 nodes,
835 nodes,
830 ischangelog,
836 ischangelog,
831 lookup,
837 lookup,
832 forcedeltaparentprev,
838 forcedeltaparentprev,
833 topic=None,
839 topic=None,
834 ellipses=False,
840 ellipses=False,
835 clrevtolocalrev=None,
841 clrevtolocalrev=None,
836 fullclnodes=None,
842 fullclnodes=None,
837 precomputedellipsis=None,
843 precomputedellipsis=None,
838 sidedata_helpers=None,
844 sidedata_helpers=None,
839 ):
845 ):
840 """Calculate deltas for a set of revisions.
846 """Calculate deltas for a set of revisions.
841
847
842 Is a generator of ``revisiondelta`` instances.
848 Is a generator of ``revisiondelta`` instances.
843
849
844 If topic is not None, progress detail will be generated using this
850 If topic is not None, progress detail will be generated using this
845 topic name (e.g. changesets, manifests, etc).
851 topic name (e.g. changesets, manifests, etc).
846
852
847 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
853 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
848 `sidedata_helpers`.
854 `sidedata_helpers`.
849 """
855 """
850 if not nodes:
856 if not nodes:
851 return
857 return
852
858
853 cl = repo.changelog
859 cl = repo.changelog
854
860
855 if ischangelog:
861 if ischangelog:
856 # `hg log` shows changesets in storage order. To preserve order
862 # `hg log` shows changesets in storage order. To preserve order
857 # across clones, send out changesets in storage order.
863 # across clones, send out changesets in storage order.
858 nodesorder = b'storage'
864 nodesorder = b'storage'
859 elif ellipses:
865 elif ellipses:
860 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
866 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
861 nodesorder = b'nodes'
867 nodesorder = b'nodes'
862 else:
868 else:
863 nodesorder = None
869 nodesorder = None
864
870
865 # Perform ellipses filtering and revision massaging. We do this before
871 # Perform ellipses filtering and revision massaging. We do this before
866 # emitrevisions() because a) filtering out revisions creates less work
872 # emitrevisions() because a) filtering out revisions creates less work
867 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
873 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
868 # assumptions about delta choices and we would possibly send a delta
874 # assumptions about delta choices and we would possibly send a delta
869 # referencing a missing base revision.
875 # referencing a missing base revision.
870 #
876 #
871 # Also, calling lookup() has side-effects with regards to populating
877 # Also, calling lookup() has side-effects with regards to populating
872 # data structures. If we don't call lookup() for each node or if we call
878 # data structures. If we don't call lookup() for each node or if we call
873 # lookup() after the first pass through each node, things can break -
879 # lookup() after the first pass through each node, things can break -
874 # possibly intermittently depending on the python hash seed! For that
880 # possibly intermittently depending on the python hash seed! For that
875 # reason, we store a mapping of all linknodes during the initial node
881 # reason, we store a mapping of all linknodes during the initial node
876 # pass rather than use lookup() on the output side.
882 # pass rather than use lookup() on the output side.
877 if ellipses:
883 if ellipses:
878 filtered = []
884 filtered = []
879 adjustedparents = {}
885 adjustedparents = {}
880 linknodes = {}
886 linknodes = {}
881
887
882 for node in nodes:
888 for node in nodes:
883 rev = store.rev(node)
889 rev = store.rev(node)
884 linknode = lookup(node)
890 linknode = lookup(node)
885 linkrev = cl.rev(linknode)
891 linkrev = cl.rev(linknode)
886 clrevtolocalrev[linkrev] = rev
892 clrevtolocalrev[linkrev] = rev
887
893
888 # If linknode is in fullclnodes, it means the corresponding
894 # If linknode is in fullclnodes, it means the corresponding
889 # changeset was a full changeset and is being sent unaltered.
895 # changeset was a full changeset and is being sent unaltered.
890 if linknode in fullclnodes:
896 if linknode in fullclnodes:
891 linknodes[node] = linknode
897 linknodes[node] = linknode
892
898
893 # If the corresponding changeset wasn't in the set computed
899 # If the corresponding changeset wasn't in the set computed
894 # as relevant to us, it should be dropped outright.
900 # as relevant to us, it should be dropped outright.
895 elif linkrev not in precomputedellipsis:
901 elif linkrev not in precomputedellipsis:
896 continue
902 continue
897
903
898 else:
904 else:
899 # We could probably do this later and avoid the dict
905 # We could probably do this later and avoid the dict
900 # holding state. But it likely doesn't matter.
906 # holding state. But it likely doesn't matter.
901 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
907 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
902 cl,
908 cl,
903 store,
909 store,
904 ischangelog,
910 ischangelog,
905 rev,
911 rev,
906 linkrev,
912 linkrev,
907 linknode,
913 linknode,
908 clrevtolocalrev,
914 clrevtolocalrev,
909 fullclnodes,
915 fullclnodes,
910 precomputedellipsis,
916 precomputedellipsis,
911 )
917 )
912
918
913 adjustedparents[node] = (p1node, p2node)
919 adjustedparents[node] = (p1node, p2node)
914 linknodes[node] = linknode
920 linknodes[node] = linknode
915
921
916 filtered.append(node)
922 filtered.append(node)
917
923
918 nodes = filtered
924 nodes = filtered
919
925
920 # We expect the first pass to be fast, so we only engage the progress
926 # We expect the first pass to be fast, so we only engage the progress
921 # meter for constructing the revision deltas.
927 # meter for constructing the revision deltas.
922 progress = None
928 progress = None
923 if topic is not None:
929 if topic is not None:
924 progress = repo.ui.makeprogress(
930 progress = repo.ui.makeprogress(
925 topic, unit=_(b'chunks'), total=len(nodes)
931 topic, unit=_(b'chunks'), total=len(nodes)
926 )
932 )
927
933
928 configtarget = repo.ui.config(b'devel', b'bundle.delta')
934 configtarget = repo.ui.config(b'devel', b'bundle.delta')
929 if configtarget not in (b'', b'p1', b'full'):
935 if configtarget not in (b'', b'p1', b'full'):
930 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
936 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
931 repo.ui.warn(msg % configtarget)
937 repo.ui.warn(msg % configtarget)
932
938
933 deltamode = repository.CG_DELTAMODE_STD
939 deltamode = repository.CG_DELTAMODE_STD
934 if forcedeltaparentprev:
940 if forcedeltaparentprev:
935 deltamode = repository.CG_DELTAMODE_PREV
941 deltamode = repository.CG_DELTAMODE_PREV
936 elif configtarget == b'p1':
942 elif configtarget == b'p1':
937 deltamode = repository.CG_DELTAMODE_P1
943 deltamode = repository.CG_DELTAMODE_P1
938 elif configtarget == b'full':
944 elif configtarget == b'full':
939 deltamode = repository.CG_DELTAMODE_FULL
945 deltamode = repository.CG_DELTAMODE_FULL
940
946
941 revisions = store.emitrevisions(
947 revisions = store.emitrevisions(
942 nodes,
948 nodes,
943 nodesorder=nodesorder,
949 nodesorder=nodesorder,
944 revisiondata=True,
950 revisiondata=True,
945 assumehaveparentrevisions=not ellipses,
951 assumehaveparentrevisions=not ellipses,
946 deltamode=deltamode,
952 deltamode=deltamode,
947 sidedata_helpers=sidedata_helpers,
953 sidedata_helpers=sidedata_helpers,
948 )
954 )
949
955
950 for i, revision in enumerate(revisions):
956 for i, revision in enumerate(revisions):
951 if progress:
957 if progress:
952 progress.update(i + 1)
958 progress.update(i + 1)
953
959
954 if ellipses:
960 if ellipses:
955 linknode = linknodes[revision.node]
961 linknode = linknodes[revision.node]
956
962
957 if revision.node in adjustedparents:
963 if revision.node in adjustedparents:
958 p1node, p2node = adjustedparents[revision.node]
964 p1node, p2node = adjustedparents[revision.node]
959 revision.p1node = p1node
965 revision.p1node = p1node
960 revision.p2node = p2node
966 revision.p2node = p2node
961 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
967 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
962
968
963 else:
969 else:
964 linknode = lookup(revision.node)
970 linknode = lookup(revision.node)
965
971
966 revision.linknode = linknode
972 revision.linknode = linknode
967 yield revision
973 yield revision
968
974
969 if progress:
975 if progress:
970 progress.complete()
976 progress.complete()
971
977
972
978
973 class cgpacker(object):
979 class cgpacker(object):
974 def __init__(
980 def __init__(
975 self,
981 self,
976 repo,
982 repo,
977 oldmatcher,
983 oldmatcher,
978 matcher,
984 matcher,
979 version,
985 version,
980 builddeltaheader,
986 builddeltaheader,
981 manifestsend,
987 manifestsend,
982 forcedeltaparentprev=False,
988 forcedeltaparentprev=False,
983 bundlecaps=None,
989 bundlecaps=None,
984 ellipses=False,
990 ellipses=False,
985 shallow=False,
991 shallow=False,
986 ellipsisroots=None,
992 ellipsisroots=None,
987 fullnodes=None,
993 fullnodes=None,
988 remote_sidedata=None,
994 remote_sidedata=None,
989 ):
995 ):
990 """Given a source repo, construct a bundler.
996 """Given a source repo, construct a bundler.
991
997
992 oldmatcher is a matcher that matches on files the client already has.
998 oldmatcher is a matcher that matches on files the client already has.
993 These will not be included in the changegroup.
999 These will not be included in the changegroup.
994
1000
995 matcher is a matcher that matches on files to include in the
1001 matcher is a matcher that matches on files to include in the
996 changegroup. Used to facilitate sparse changegroups.
1002 changegroup. Used to facilitate sparse changegroups.
997
1003
998 forcedeltaparentprev indicates whether delta parents must be against
1004 forcedeltaparentprev indicates whether delta parents must be against
999 the previous revision in a delta group. This should only be used for
1005 the previous revision in a delta group. This should only be used for
1000 compatibility with changegroup version 1.
1006 compatibility with changegroup version 1.
1001
1007
1002 builddeltaheader is a callable that constructs the header for a group
1008 builddeltaheader is a callable that constructs the header for a group
1003 delta.
1009 delta.
1004
1010
1005 manifestsend is a chunk to send after manifests have been fully emitted.
1011 manifestsend is a chunk to send after manifests have been fully emitted.
1006
1012
1007 ellipses indicates whether ellipsis serving mode is enabled.
1013 ellipses indicates whether ellipsis serving mode is enabled.
1008
1014
1009 bundlecaps is optional and can be used to specify the set of
1015 bundlecaps is optional and can be used to specify the set of
1010 capabilities which can be used to build the bundle. While bundlecaps is
1016 capabilities which can be used to build the bundle. While bundlecaps is
1011 unused in core Mercurial, extensions rely on this feature to communicate
1017 unused in core Mercurial, extensions rely on this feature to communicate
1012 capabilities to customize the changegroup packer.
1018 capabilities to customize the changegroup packer.
1013
1019
1014 shallow indicates whether shallow data might be sent. The packer may
1020 shallow indicates whether shallow data might be sent. The packer may
1015 need to pack file contents not introduced by the changes being packed.
1021 need to pack file contents not introduced by the changes being packed.
1016
1022
1017 fullnodes is the set of changelog nodes which should not be ellipsis
1023 fullnodes is the set of changelog nodes which should not be ellipsis
1018 nodes. We store this rather than the set of nodes that should be
1024 nodes. We store this rather than the set of nodes that should be
1019 ellipsis because for very large histories we expect this to be
1025 ellipsis because for very large histories we expect this to be
1020 significantly smaller.
1026 significantly smaller.
1021
1027
1022 remote_sidedata is the set of sidedata categories wanted by the remote.
1028 remote_sidedata is the set of sidedata categories wanted by the remote.
1023 """
1029 """
1024 assert oldmatcher
1030 assert oldmatcher
1025 assert matcher
1031 assert matcher
1026 self._oldmatcher = oldmatcher
1032 self._oldmatcher = oldmatcher
1027 self._matcher = matcher
1033 self._matcher = matcher
1028
1034
1029 self.version = version
1035 self.version = version
1030 self._forcedeltaparentprev = forcedeltaparentprev
1036 self._forcedeltaparentprev = forcedeltaparentprev
1031 self._builddeltaheader = builddeltaheader
1037 self._builddeltaheader = builddeltaheader
1032 self._manifestsend = manifestsend
1038 self._manifestsend = manifestsend
1033 self._ellipses = ellipses
1039 self._ellipses = ellipses
1034
1040
1035 # Set of capabilities we can use to build the bundle.
1041 # Set of capabilities we can use to build the bundle.
1036 if bundlecaps is None:
1042 if bundlecaps is None:
1037 bundlecaps = set()
1043 bundlecaps = set()
1038 self._bundlecaps = bundlecaps
1044 self._bundlecaps = bundlecaps
1039 if remote_sidedata is None:
1045 if remote_sidedata is None:
1040 remote_sidedata = set()
1046 remote_sidedata = set()
1041 self._remote_sidedata = remote_sidedata
1047 self._remote_sidedata = remote_sidedata
1042 self._isshallow = shallow
1048 self._isshallow = shallow
1043 self._fullclnodes = fullnodes
1049 self._fullclnodes = fullnodes
1044
1050
1045 # Maps ellipsis revs to their roots at the changelog level.
1051 # Maps ellipsis revs to their roots at the changelog level.
1046 self._precomputedellipsis = ellipsisroots
1052 self._precomputedellipsis = ellipsisroots
1047
1053
1048 self._repo = repo
1054 self._repo = repo
1049
1055
1050 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1056 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1051 self._verbosenote = self._repo.ui.note
1057 self._verbosenote = self._repo.ui.note
1052 else:
1058 else:
1053 self._verbosenote = lambda s: None
1059 self._verbosenote = lambda s: None
1054
1060
1055 def generate(
1061 def generate(
1056 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1062 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1057 ):
1063 ):
1058 """Yield a sequence of changegroup byte chunks.
1064 """Yield a sequence of changegroup byte chunks.
1059 If changelog is False, changelog data won't be added to changegroup
1065 If changelog is False, changelog data won't be added to changegroup
1060 """
1066 """
1061
1067
1062 repo = self._repo
1068 repo = self._repo
1063 cl = repo.changelog
1069 cl = repo.changelog
1064
1070
1065 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1071 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1066 size = 0
1072 size = 0
1067
1073
1068 sidedata_helpers = None
1074 sidedata_helpers = None
1069 if self.version == b'04':
1075 if self.version == b'04':
1070 remote_sidedata = self._remote_sidedata
1076 remote_sidedata = self._remote_sidedata
1071 if source == b'strip':
1077 if source == b'strip':
1072 # We're our own remote when stripping, get the no-op helpers
1078 # We're our own remote when stripping, get the no-op helpers
1073 # TODO a better approach would be for the strip bundle to
1079 # TODO a better approach would be for the strip bundle to
1074 # correctly advertise its sidedata categories directly.
1080 # correctly advertise its sidedata categories directly.
1075 remote_sidedata = repo._wanted_sidedata
1081 remote_sidedata = repo._wanted_sidedata
1076 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1082 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1077 repo, remote_sidedata
1083 repo, remote_sidedata
1078 )
1084 )
1079
1085
1080 clstate, deltas = self._generatechangelog(
1086 clstate, deltas = self._generatechangelog(
1081 cl,
1087 cl,
1082 clnodes,
1088 clnodes,
1083 generate=changelog,
1089 generate=changelog,
1084 sidedata_helpers=sidedata_helpers,
1090 sidedata_helpers=sidedata_helpers,
1085 )
1091 )
1086 for delta in deltas:
1092 for delta in deltas:
1087 for chunk in _revisiondeltatochunks(
1093 for chunk in _revisiondeltatochunks(
1088 self._repo, delta, self._builddeltaheader
1094 self._repo, delta, self._builddeltaheader
1089 ):
1095 ):
1090 size += len(chunk)
1096 size += len(chunk)
1091 yield chunk
1097 yield chunk
1092
1098
1093 close = closechunk()
1099 close = closechunk()
1094 size += len(close)
1100 size += len(close)
1095 yield closechunk()
1101 yield closechunk()
1096
1102
1097 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1103 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1098
1104
1099 clrevorder = clstate[b'clrevorder']
1105 clrevorder = clstate[b'clrevorder']
1100 manifests = clstate[b'manifests']
1106 manifests = clstate[b'manifests']
1101 changedfiles = clstate[b'changedfiles']
1107 changedfiles = clstate[b'changedfiles']
1102
1108
1103 # We need to make sure that the linkrev in the changegroup refers to
1109 # We need to make sure that the linkrev in the changegroup refers to
1104 # the first changeset that introduced the manifest or file revision.
1110 # the first changeset that introduced the manifest or file revision.
1105 # The fastpath is usually safer than the slowpath, because the filelogs
1111 # The fastpath is usually safer than the slowpath, because the filelogs
1106 # are walked in revlog order.
1112 # are walked in revlog order.
1107 #
1113 #
1108 # When taking the slowpath when the manifest revlog uses generaldelta,
1114 # When taking the slowpath when the manifest revlog uses generaldelta,
1109 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1115 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1110 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1116 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1111 #
1117 #
1112 # When taking the fastpath, we are only vulnerable to reordering
1118 # When taking the fastpath, we are only vulnerable to reordering
1113 # of the changelog itself. The changelog never uses generaldelta and is
1119 # of the changelog itself. The changelog never uses generaldelta and is
1114 # never reordered. To handle this case, we simply take the slowpath,
1120 # never reordered. To handle this case, we simply take the slowpath,
1115 # which already has the 'clrevorder' logic. This was also fixed in
1121 # which already has the 'clrevorder' logic. This was also fixed in
1116 # cc0ff93d0c0c.
1122 # cc0ff93d0c0c.
1117
1123
1118 # Treemanifests don't work correctly with fastpathlinkrev
1124 # Treemanifests don't work correctly with fastpathlinkrev
1119 # either, because we don't discover which directory nodes to
1125 # either, because we don't discover which directory nodes to
1120 # send along with files. This could probably be fixed.
1126 # send along with files. This could probably be fixed.
1121 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1127 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1122
1128
1123 fnodes = {} # needed file nodes
1129 fnodes = {} # needed file nodes
1124
1130
1125 size = 0
1131 size = 0
1126 it = self.generatemanifests(
1132 it = self.generatemanifests(
1127 commonrevs,
1133 commonrevs,
1128 clrevorder,
1134 clrevorder,
1129 fastpathlinkrev,
1135 fastpathlinkrev,
1130 manifests,
1136 manifests,
1131 fnodes,
1137 fnodes,
1132 source,
1138 source,
1133 clstate[b'clrevtomanifestrev'],
1139 clstate[b'clrevtomanifestrev'],
1134 sidedata_helpers=sidedata_helpers,
1140 sidedata_helpers=sidedata_helpers,
1135 )
1141 )
1136
1142
1137 for tree, deltas in it:
1143 for tree, deltas in it:
1138 if tree:
1144 if tree:
1139 assert self.version in (b'03', b'04')
1145 assert self.version in (b'03', b'04')
1140 chunk = _fileheader(tree)
1146 chunk = _fileheader(tree)
1141 size += len(chunk)
1147 size += len(chunk)
1142 yield chunk
1148 yield chunk
1143
1149
1144 for delta in deltas:
1150 for delta in deltas:
1145 chunks = _revisiondeltatochunks(
1151 chunks = _revisiondeltatochunks(
1146 self._repo, delta, self._builddeltaheader
1152 self._repo, delta, self._builddeltaheader
1147 )
1153 )
1148 for chunk in chunks:
1154 for chunk in chunks:
1149 size += len(chunk)
1155 size += len(chunk)
1150 yield chunk
1156 yield chunk
1151
1157
1152 close = closechunk()
1158 close = closechunk()
1153 size += len(close)
1159 size += len(close)
1154 yield close
1160 yield close
1155
1161
1156 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1162 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1157 yield self._manifestsend
1163 yield self._manifestsend
1158
1164
1159 mfdicts = None
1165 mfdicts = None
1160 if self._ellipses and self._isshallow:
1166 if self._ellipses and self._isshallow:
1161 mfdicts = [
1167 mfdicts = [
1162 (repo.manifestlog[n].read(), lr)
1168 (repo.manifestlog[n].read(), lr)
1163 for (n, lr) in pycompat.iteritems(manifests)
1169 for (n, lr) in pycompat.iteritems(manifests)
1164 ]
1170 ]
1165
1171
1166 manifests.clear()
1172 manifests.clear()
1167 clrevs = {cl.rev(x) for x in clnodes}
1173 clrevs = {cl.rev(x) for x in clnodes}
1168
1174
1169 it = self.generatefiles(
1175 it = self.generatefiles(
1170 changedfiles,
1176 changedfiles,
1171 commonrevs,
1177 commonrevs,
1172 source,
1178 source,
1173 mfdicts,
1179 mfdicts,
1174 fastpathlinkrev,
1180 fastpathlinkrev,
1175 fnodes,
1181 fnodes,
1176 clrevs,
1182 clrevs,
1177 sidedata_helpers=sidedata_helpers,
1183 sidedata_helpers=sidedata_helpers,
1178 )
1184 )
1179
1185
1180 for path, deltas in it:
1186 for path, deltas in it:
1181 h = _fileheader(path)
1187 h = _fileheader(path)
1182 size = len(h)
1188 size = len(h)
1183 yield h
1189 yield h
1184
1190
1185 for delta in deltas:
1191 for delta in deltas:
1186 chunks = _revisiondeltatochunks(
1192 chunks = _revisiondeltatochunks(
1187 self._repo, delta, self._builddeltaheader
1193 self._repo, delta, self._builddeltaheader
1188 )
1194 )
1189 for chunk in chunks:
1195 for chunk in chunks:
1190 size += len(chunk)
1196 size += len(chunk)
1191 yield chunk
1197 yield chunk
1192
1198
1193 close = closechunk()
1199 close = closechunk()
1194 size += len(close)
1200 size += len(close)
1195 yield close
1201 yield close
1196
1202
1197 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1203 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1198
1204
1199 yield closechunk()
1205 yield closechunk()
1200
1206
1201 if clnodes:
1207 if clnodes:
1202 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1208 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1203
1209
1204 def _generatechangelog(
1210 def _generatechangelog(
1205 self, cl, nodes, generate=True, sidedata_helpers=None
1211 self, cl, nodes, generate=True, sidedata_helpers=None
1206 ):
1212 ):
1207 """Generate data for changelog chunks.
1213 """Generate data for changelog chunks.
1208
1214
1209 Returns a 2-tuple of a dict containing state and an iterable of
1215 Returns a 2-tuple of a dict containing state and an iterable of
1210 byte chunks. The state will not be fully populated until the
1216 byte chunks. The state will not be fully populated until the
1211 chunk stream has been fully consumed.
1217 chunk stream has been fully consumed.
1212
1218
1213 if generate is False, the state will be fully populated and no chunk
1219 if generate is False, the state will be fully populated and no chunk
1214 stream will be yielded
1220 stream will be yielded
1215
1221
1216 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1222 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1217 `sidedata_helpers`.
1223 `sidedata_helpers`.
1218 """
1224 """
1219 clrevorder = {}
1225 clrevorder = {}
1220 manifests = {}
1226 manifests = {}
1221 mfl = self._repo.manifestlog
1227 mfl = self._repo.manifestlog
1222 changedfiles = set()
1228 changedfiles = set()
1223 clrevtomanifestrev = {}
1229 clrevtomanifestrev = {}
1224
1230
1225 state = {
1231 state = {
1226 b'clrevorder': clrevorder,
1232 b'clrevorder': clrevorder,
1227 b'manifests': manifests,
1233 b'manifests': manifests,
1228 b'changedfiles': changedfiles,
1234 b'changedfiles': changedfiles,
1229 b'clrevtomanifestrev': clrevtomanifestrev,
1235 b'clrevtomanifestrev': clrevtomanifestrev,
1230 }
1236 }
1231
1237
1232 if not (generate or self._ellipses):
1238 if not (generate or self._ellipses):
1233 # sort the nodes in storage order
1239 # sort the nodes in storage order
1234 nodes = sorted(nodes, key=cl.rev)
1240 nodes = sorted(nodes, key=cl.rev)
1235 for node in nodes:
1241 for node in nodes:
1236 c = cl.changelogrevision(node)
1242 c = cl.changelogrevision(node)
1237 clrevorder[node] = len(clrevorder)
1243 clrevorder[node] = len(clrevorder)
1238 # record the first changeset introducing this manifest version
1244 # record the first changeset introducing this manifest version
1239 manifests.setdefault(c.manifest, node)
1245 manifests.setdefault(c.manifest, node)
1240 # Record a complete list of potentially-changed files in
1246 # Record a complete list of potentially-changed files in
1241 # this manifest.
1247 # this manifest.
1242 changedfiles.update(c.files)
1248 changedfiles.update(c.files)
1243
1249
1244 return state, ()
1250 return state, ()
1245
1251
1246 # Callback for the changelog, used to collect changed files and
1252 # Callback for the changelog, used to collect changed files and
1247 # manifest nodes.
1253 # manifest nodes.
1248 # Returns the linkrev node (identity in the changelog case).
1254 # Returns the linkrev node (identity in the changelog case).
1249 def lookupcl(x):
1255 def lookupcl(x):
1250 c = cl.changelogrevision(x)
1256 c = cl.changelogrevision(x)
1251 clrevorder[x] = len(clrevorder)
1257 clrevorder[x] = len(clrevorder)
1252
1258
1253 if self._ellipses:
1259 if self._ellipses:
1254 # Only update manifests if x is going to be sent. Otherwise we
1260 # Only update manifests if x is going to be sent. Otherwise we
1255 # end up with bogus linkrevs specified for manifests and
1261 # end up with bogus linkrevs specified for manifests and
1256 # we skip some manifest nodes that we should otherwise
1262 # we skip some manifest nodes that we should otherwise
1257 # have sent.
1263 # have sent.
1258 if (
1264 if (
1259 x in self._fullclnodes
1265 x in self._fullclnodes
1260 or cl.rev(x) in self._precomputedellipsis
1266 or cl.rev(x) in self._precomputedellipsis
1261 ):
1267 ):
1262
1268
1263 manifestnode = c.manifest
1269 manifestnode = c.manifest
1264 # Record the first changeset introducing this manifest
1270 # Record the first changeset introducing this manifest
1265 # version.
1271 # version.
1266 manifests.setdefault(manifestnode, x)
1272 manifests.setdefault(manifestnode, x)
1267 # Set this narrow-specific dict so we have the lowest
1273 # Set this narrow-specific dict so we have the lowest
1268 # manifest revnum to look up for this cl revnum. (Part of
1274 # manifest revnum to look up for this cl revnum. (Part of
1269 # mapping changelog ellipsis parents to manifest ellipsis
1275 # mapping changelog ellipsis parents to manifest ellipsis
1270 # parents)
1276 # parents)
1271 clrevtomanifestrev.setdefault(
1277 clrevtomanifestrev.setdefault(
1272 cl.rev(x), mfl.rev(manifestnode)
1278 cl.rev(x), mfl.rev(manifestnode)
1273 )
1279 )
1274 # We can't trust the changed files list in the changeset if the
1280 # We can't trust the changed files list in the changeset if the
1275 # client requested a shallow clone.
1281 # client requested a shallow clone.
1276 if self._isshallow:
1282 if self._isshallow:
1277 changedfiles.update(mfl[c.manifest].read().keys())
1283 changedfiles.update(mfl[c.manifest].read().keys())
1278 else:
1284 else:
1279 changedfiles.update(c.files)
1285 changedfiles.update(c.files)
1280 else:
1286 else:
1281 # record the first changeset introducing this manifest version
1287 # record the first changeset introducing this manifest version
1282 manifests.setdefault(c.manifest, x)
1288 manifests.setdefault(c.manifest, x)
1283 # Record a complete list of potentially-changed files in
1289 # Record a complete list of potentially-changed files in
1284 # this manifest.
1290 # this manifest.
1285 changedfiles.update(c.files)
1291 changedfiles.update(c.files)
1286
1292
1287 return x
1293 return x
1288
1294
1289 gen = deltagroup(
1295 gen = deltagroup(
1290 self._repo,
1296 self._repo,
1291 cl,
1297 cl,
1292 nodes,
1298 nodes,
1293 True,
1299 True,
1294 lookupcl,
1300 lookupcl,
1295 self._forcedeltaparentprev,
1301 self._forcedeltaparentprev,
1296 ellipses=self._ellipses,
1302 ellipses=self._ellipses,
1297 topic=_(b'changesets'),
1303 topic=_(b'changesets'),
1298 clrevtolocalrev={},
1304 clrevtolocalrev={},
1299 fullclnodes=self._fullclnodes,
1305 fullclnodes=self._fullclnodes,
1300 precomputedellipsis=self._precomputedellipsis,
1306 precomputedellipsis=self._precomputedellipsis,
1301 sidedata_helpers=sidedata_helpers,
1307 sidedata_helpers=sidedata_helpers,
1302 )
1308 )
1303
1309
1304 return state, gen
1310 return state, gen
1305
1311
1306 def generatemanifests(
1312 def generatemanifests(
1307 self,
1313 self,
1308 commonrevs,
1314 commonrevs,
1309 clrevorder,
1315 clrevorder,
1310 fastpathlinkrev,
1316 fastpathlinkrev,
1311 manifests,
1317 manifests,
1312 fnodes,
1318 fnodes,
1313 source,
1319 source,
1314 clrevtolocalrev,
1320 clrevtolocalrev,
1315 sidedata_helpers=None,
1321 sidedata_helpers=None,
1316 ):
1322 ):
1317 """Returns an iterator of changegroup chunks containing manifests.
1323 """Returns an iterator of changegroup chunks containing manifests.
1318
1324
1319 `source` is unused here, but is used by extensions like remotefilelog to
1325 `source` is unused here, but is used by extensions like remotefilelog to
1320 change what is sent based in pulls vs pushes, etc.
1326 change what is sent based in pulls vs pushes, etc.
1321
1327
1322 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1328 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1323 `sidedata_helpers`.
1329 `sidedata_helpers`.
1324 """
1330 """
1325 repo = self._repo
1331 repo = self._repo
1326 mfl = repo.manifestlog
1332 mfl = repo.manifestlog
1327 tmfnodes = {b'': manifests}
1333 tmfnodes = {b'': manifests}
1328
1334
1329 # Callback for the manifest, used to collect linkrevs for filelog
1335 # Callback for the manifest, used to collect linkrevs for filelog
1330 # revisions.
1336 # revisions.
1331 # Returns the linkrev node (collected in lookupcl).
1337 # Returns the linkrev node (collected in lookupcl).
1332 def makelookupmflinknode(tree, nodes):
1338 def makelookupmflinknode(tree, nodes):
1333 if fastpathlinkrev:
1339 if fastpathlinkrev:
1334 assert not tree
1340 assert not tree
1335
1341
1336 # pytype: disable=unsupported-operands
1342 # pytype: disable=unsupported-operands
1337 return manifests.__getitem__
1343 return manifests.__getitem__
1338 # pytype: enable=unsupported-operands
1344 # pytype: enable=unsupported-operands
1339
1345
1340 def lookupmflinknode(x):
1346 def lookupmflinknode(x):
1341 """Callback for looking up the linknode for manifests.
1347 """Callback for looking up the linknode for manifests.
1342
1348
1343 Returns the linkrev node for the specified manifest.
1349 Returns the linkrev node for the specified manifest.
1344
1350
1345 SIDE EFFECT:
1351 SIDE EFFECT:
1346
1352
1347 1) fclnodes gets populated with the list of relevant
1353 1) fclnodes gets populated with the list of relevant
1348 file nodes if we're not using fastpathlinkrev
1354 file nodes if we're not using fastpathlinkrev
1349 2) When treemanifests are in use, collects treemanifest nodes
1355 2) When treemanifests are in use, collects treemanifest nodes
1350 to send
1356 to send
1351
1357
1352 Note that this means manifests must be completely sent to
1358 Note that this means manifests must be completely sent to
1353 the client before you can trust the list of files and
1359 the client before you can trust the list of files and
1354 treemanifests to send.
1360 treemanifests to send.
1355 """
1361 """
1356 clnode = nodes[x]
1362 clnode = nodes[x]
1357 mdata = mfl.get(tree, x).readfast(shallow=True)
1363 mdata = mfl.get(tree, x).readfast(shallow=True)
1358 for p, n, fl in mdata.iterentries():
1364 for p, n, fl in mdata.iterentries():
1359 if fl == b't': # subdirectory manifest
1365 if fl == b't': # subdirectory manifest
1360 subtree = tree + p + b'/'
1366 subtree = tree + p + b'/'
1361 tmfclnodes = tmfnodes.setdefault(subtree, {})
1367 tmfclnodes = tmfnodes.setdefault(subtree, {})
1362 tmfclnode = tmfclnodes.setdefault(n, clnode)
1368 tmfclnode = tmfclnodes.setdefault(n, clnode)
1363 if clrevorder[clnode] < clrevorder[tmfclnode]:
1369 if clrevorder[clnode] < clrevorder[tmfclnode]:
1364 tmfclnodes[n] = clnode
1370 tmfclnodes[n] = clnode
1365 else:
1371 else:
1366 f = tree + p
1372 f = tree + p
1367 fclnodes = fnodes.setdefault(f, {})
1373 fclnodes = fnodes.setdefault(f, {})
1368 fclnode = fclnodes.setdefault(n, clnode)
1374 fclnode = fclnodes.setdefault(n, clnode)
1369 if clrevorder[clnode] < clrevorder[fclnode]:
1375 if clrevorder[clnode] < clrevorder[fclnode]:
1370 fclnodes[n] = clnode
1376 fclnodes[n] = clnode
1371 return clnode
1377 return clnode
1372
1378
1373 return lookupmflinknode
1379 return lookupmflinknode
1374
1380
1375 while tmfnodes:
1381 while tmfnodes:
1376 tree, nodes = tmfnodes.popitem()
1382 tree, nodes = tmfnodes.popitem()
1377
1383
1378 should_visit = self._matcher.visitdir(tree[:-1])
1384 should_visit = self._matcher.visitdir(tree[:-1])
1379 if tree and not should_visit:
1385 if tree and not should_visit:
1380 continue
1386 continue
1381
1387
1382 store = mfl.getstorage(tree)
1388 store = mfl.getstorage(tree)
1383
1389
1384 if not should_visit:
1390 if not should_visit:
1385 # No nodes to send because this directory is out of
1391 # No nodes to send because this directory is out of
1386 # the client's view of the repository (probably
1392 # the client's view of the repository (probably
1387 # because of narrow clones). Do this even for the root
1393 # because of narrow clones). Do this even for the root
1388 # directory (tree=='')
1394 # directory (tree=='')
1389 prunednodes = []
1395 prunednodes = []
1390 else:
1396 else:
1391 # Avoid sending any manifest nodes we can prove the
1397 # Avoid sending any manifest nodes we can prove the
1392 # client already has by checking linkrevs. See the
1398 # client already has by checking linkrevs. See the
1393 # related comment in generatefiles().
1399 # related comment in generatefiles().
1394 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1400 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1395
1401
1396 if tree and not prunednodes:
1402 if tree and not prunednodes:
1397 continue
1403 continue
1398
1404
1399 lookupfn = makelookupmflinknode(tree, nodes)
1405 lookupfn = makelookupmflinknode(tree, nodes)
1400
1406
1401 deltas = deltagroup(
1407 deltas = deltagroup(
1402 self._repo,
1408 self._repo,
1403 store,
1409 store,
1404 prunednodes,
1410 prunednodes,
1405 False,
1411 False,
1406 lookupfn,
1412 lookupfn,
1407 self._forcedeltaparentprev,
1413 self._forcedeltaparentprev,
1408 ellipses=self._ellipses,
1414 ellipses=self._ellipses,
1409 topic=_(b'manifests'),
1415 topic=_(b'manifests'),
1410 clrevtolocalrev=clrevtolocalrev,
1416 clrevtolocalrev=clrevtolocalrev,
1411 fullclnodes=self._fullclnodes,
1417 fullclnodes=self._fullclnodes,
1412 precomputedellipsis=self._precomputedellipsis,
1418 precomputedellipsis=self._precomputedellipsis,
1413 sidedata_helpers=sidedata_helpers,
1419 sidedata_helpers=sidedata_helpers,
1414 )
1420 )
1415
1421
1416 if not self._oldmatcher.visitdir(store.tree[:-1]):
1422 if not self._oldmatcher.visitdir(store.tree[:-1]):
1417 yield tree, deltas
1423 yield tree, deltas
1418 else:
1424 else:
1419 # 'deltas' is a generator and we need to consume it even if
1425 # 'deltas' is a generator and we need to consume it even if
1420 # we are not going to send it because a side-effect is that
1426 # we are not going to send it because a side-effect is that
1421 # it updates tmdnodes (via lookupfn)
1427 # it updates tmdnodes (via lookupfn)
1422 for d in deltas:
1428 for d in deltas:
1423 pass
1429 pass
1424 if not tree:
1430 if not tree:
1425 yield tree, []
1431 yield tree, []
1426
1432
1427 def _prunemanifests(self, store, nodes, commonrevs):
1433 def _prunemanifests(self, store, nodes, commonrevs):
1428 if not self._ellipses:
1434 if not self._ellipses:
1429 # In non-ellipses case and large repositories, it is better to
1435 # In non-ellipses case and large repositories, it is better to
1430 # prevent calling of store.rev and store.linkrev on a lot of
1436 # prevent calling of store.rev and store.linkrev on a lot of
1431 # nodes as compared to sending some extra data
1437 # nodes as compared to sending some extra data
1432 return nodes.copy()
1438 return nodes.copy()
1433 # This is split out as a separate method to allow filtering
1439 # This is split out as a separate method to allow filtering
1434 # commonrevs in extension code.
1440 # commonrevs in extension code.
1435 #
1441 #
1436 # TODO(augie): this shouldn't be required, instead we should
1442 # TODO(augie): this shouldn't be required, instead we should
1437 # make filtering of revisions to send delegated to the store
1443 # make filtering of revisions to send delegated to the store
1438 # layer.
1444 # layer.
1439 frev, flr = store.rev, store.linkrev
1445 frev, flr = store.rev, store.linkrev
1440 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1446 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1441
1447
1442 # The 'source' parameter is useful for extensions
1448 # The 'source' parameter is useful for extensions
1443 def generatefiles(
1449 def generatefiles(
1444 self,
1450 self,
1445 changedfiles,
1451 changedfiles,
1446 commonrevs,
1452 commonrevs,
1447 source,
1453 source,
1448 mfdicts,
1454 mfdicts,
1449 fastpathlinkrev,
1455 fastpathlinkrev,
1450 fnodes,
1456 fnodes,
1451 clrevs,
1457 clrevs,
1452 sidedata_helpers=None,
1458 sidedata_helpers=None,
1453 ):
1459 ):
1454 changedfiles = [
1460 changedfiles = [
1455 f
1461 f
1456 for f in changedfiles
1462 for f in changedfiles
1457 if self._matcher(f) and not self._oldmatcher(f)
1463 if self._matcher(f) and not self._oldmatcher(f)
1458 ]
1464 ]
1459
1465
1460 if not fastpathlinkrev:
1466 if not fastpathlinkrev:
1461
1467
1462 def normallinknodes(unused, fname):
1468 def normallinknodes(unused, fname):
1463 return fnodes.get(fname, {})
1469 return fnodes.get(fname, {})
1464
1470
1465 else:
1471 else:
1466 cln = self._repo.changelog.node
1472 cln = self._repo.changelog.node
1467
1473
1468 def normallinknodes(store, fname):
1474 def normallinknodes(store, fname):
1469 flinkrev = store.linkrev
1475 flinkrev = store.linkrev
1470 fnode = store.node
1476 fnode = store.node
1471 revs = ((r, flinkrev(r)) for r in store)
1477 revs = ((r, flinkrev(r)) for r in store)
1472 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1478 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1473
1479
1474 clrevtolocalrev = {}
1480 clrevtolocalrev = {}
1475
1481
1476 if self._isshallow:
1482 if self._isshallow:
1477 # In a shallow clone, the linknodes callback needs to also include
1483 # In a shallow clone, the linknodes callback needs to also include
1478 # those file nodes that are in the manifests we sent but weren't
1484 # those file nodes that are in the manifests we sent but weren't
1479 # introduced by those manifests.
1485 # introduced by those manifests.
1480 commonctxs = [self._repo[c] for c in commonrevs]
1486 commonctxs = [self._repo[c] for c in commonrevs]
1481 clrev = self._repo.changelog.rev
1487 clrev = self._repo.changelog.rev
1482
1488
1483 def linknodes(flog, fname):
1489 def linknodes(flog, fname):
1484 for c in commonctxs:
1490 for c in commonctxs:
1485 try:
1491 try:
1486 fnode = c.filenode(fname)
1492 fnode = c.filenode(fname)
1487 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1493 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1488 except error.ManifestLookupError:
1494 except error.ManifestLookupError:
1489 pass
1495 pass
1490 links = normallinknodes(flog, fname)
1496 links = normallinknodes(flog, fname)
1491 if len(links) != len(mfdicts):
1497 if len(links) != len(mfdicts):
1492 for mf, lr in mfdicts:
1498 for mf, lr in mfdicts:
1493 fnode = mf.get(fname, None)
1499 fnode = mf.get(fname, None)
1494 if fnode in links:
1500 if fnode in links:
1495 links[fnode] = min(links[fnode], lr, key=clrev)
1501 links[fnode] = min(links[fnode], lr, key=clrev)
1496 elif fnode:
1502 elif fnode:
1497 links[fnode] = lr
1503 links[fnode] = lr
1498 return links
1504 return links
1499
1505
1500 else:
1506 else:
1501 linknodes = normallinknodes
1507 linknodes = normallinknodes
1502
1508
1503 repo = self._repo
1509 repo = self._repo
1504 progress = repo.ui.makeprogress(
1510 progress = repo.ui.makeprogress(
1505 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1511 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1506 )
1512 )
1507 for i, fname in enumerate(sorted(changedfiles)):
1513 for i, fname in enumerate(sorted(changedfiles)):
1508 filerevlog = repo.file(fname)
1514 filerevlog = repo.file(fname)
1509 if not filerevlog:
1515 if not filerevlog:
1510 raise error.Abort(
1516 raise error.Abort(
1511 _(b"empty or missing file data for %s") % fname
1517 _(b"empty or missing file data for %s") % fname
1512 )
1518 )
1513
1519
1514 clrevtolocalrev.clear()
1520 clrevtolocalrev.clear()
1515
1521
1516 linkrevnodes = linknodes(filerevlog, fname)
1522 linkrevnodes = linknodes(filerevlog, fname)
1517 # Lookup for filenodes, we collected the linkrev nodes above in the
1523 # Lookup for filenodes, we collected the linkrev nodes above in the
1518 # fastpath case and with lookupmf in the slowpath case.
1524 # fastpath case and with lookupmf in the slowpath case.
1519 def lookupfilelog(x):
1525 def lookupfilelog(x):
1520 return linkrevnodes[x]
1526 return linkrevnodes[x]
1521
1527
1522 frev, flr = filerevlog.rev, filerevlog.linkrev
1528 frev, flr = filerevlog.rev, filerevlog.linkrev
1523 # Skip sending any filenode we know the client already
1529 # Skip sending any filenode we know the client already
1524 # has. This avoids over-sending files relatively
1530 # has. This avoids over-sending files relatively
1525 # inexpensively, so it's not a problem if we under-filter
1531 # inexpensively, so it's not a problem if we under-filter
1526 # here.
1532 # here.
1527 filenodes = [
1533 filenodes = [
1528 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1534 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1529 ]
1535 ]
1530
1536
1531 if not filenodes:
1537 if not filenodes:
1532 continue
1538 continue
1533
1539
1534 progress.update(i + 1, item=fname)
1540 progress.update(i + 1, item=fname)
1535
1541
1536 deltas = deltagroup(
1542 deltas = deltagroup(
1537 self._repo,
1543 self._repo,
1538 filerevlog,
1544 filerevlog,
1539 filenodes,
1545 filenodes,
1540 False,
1546 False,
1541 lookupfilelog,
1547 lookupfilelog,
1542 self._forcedeltaparentprev,
1548 self._forcedeltaparentprev,
1543 ellipses=self._ellipses,
1549 ellipses=self._ellipses,
1544 clrevtolocalrev=clrevtolocalrev,
1550 clrevtolocalrev=clrevtolocalrev,
1545 fullclnodes=self._fullclnodes,
1551 fullclnodes=self._fullclnodes,
1546 precomputedellipsis=self._precomputedellipsis,
1552 precomputedellipsis=self._precomputedellipsis,
1547 sidedata_helpers=sidedata_helpers,
1553 sidedata_helpers=sidedata_helpers,
1548 )
1554 )
1549
1555
1550 yield fname, deltas
1556 yield fname, deltas
1551
1557
1552 progress.complete()
1558 progress.complete()
1553
1559
1554
1560
1555 def _makecg1packer(
1561 def _makecg1packer(
1556 repo,
1562 repo,
1557 oldmatcher,
1563 oldmatcher,
1558 matcher,
1564 matcher,
1559 bundlecaps,
1565 bundlecaps,
1560 ellipses=False,
1566 ellipses=False,
1561 shallow=False,
1567 shallow=False,
1562 ellipsisroots=None,
1568 ellipsisroots=None,
1563 fullnodes=None,
1569 fullnodes=None,
1564 remote_sidedata=None,
1570 remote_sidedata=None,
1565 ):
1571 ):
1566 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1572 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1567 d.node, d.p1node, d.p2node, d.linknode
1573 d.node, d.p1node, d.p2node, d.linknode
1568 )
1574 )
1569
1575
1570 return cgpacker(
1576 return cgpacker(
1571 repo,
1577 repo,
1572 oldmatcher,
1578 oldmatcher,
1573 matcher,
1579 matcher,
1574 b'01',
1580 b'01',
1575 builddeltaheader=builddeltaheader,
1581 builddeltaheader=builddeltaheader,
1576 manifestsend=b'',
1582 manifestsend=b'',
1577 forcedeltaparentprev=True,
1583 forcedeltaparentprev=True,
1578 bundlecaps=bundlecaps,
1584 bundlecaps=bundlecaps,
1579 ellipses=ellipses,
1585 ellipses=ellipses,
1580 shallow=shallow,
1586 shallow=shallow,
1581 ellipsisroots=ellipsisroots,
1587 ellipsisroots=ellipsisroots,
1582 fullnodes=fullnodes,
1588 fullnodes=fullnodes,
1583 )
1589 )
1584
1590
1585
1591
1586 def _makecg2packer(
1592 def _makecg2packer(
1587 repo,
1593 repo,
1588 oldmatcher,
1594 oldmatcher,
1589 matcher,
1595 matcher,
1590 bundlecaps,
1596 bundlecaps,
1591 ellipses=False,
1597 ellipses=False,
1592 shallow=False,
1598 shallow=False,
1593 ellipsisroots=None,
1599 ellipsisroots=None,
1594 fullnodes=None,
1600 fullnodes=None,
1595 remote_sidedata=None,
1601 remote_sidedata=None,
1596 ):
1602 ):
1597 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1603 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1598 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1604 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1599 )
1605 )
1600
1606
1601 return cgpacker(
1607 return cgpacker(
1602 repo,
1608 repo,
1603 oldmatcher,
1609 oldmatcher,
1604 matcher,
1610 matcher,
1605 b'02',
1611 b'02',
1606 builddeltaheader=builddeltaheader,
1612 builddeltaheader=builddeltaheader,
1607 manifestsend=b'',
1613 manifestsend=b'',
1608 bundlecaps=bundlecaps,
1614 bundlecaps=bundlecaps,
1609 ellipses=ellipses,
1615 ellipses=ellipses,
1610 shallow=shallow,
1616 shallow=shallow,
1611 ellipsisroots=ellipsisroots,
1617 ellipsisroots=ellipsisroots,
1612 fullnodes=fullnodes,
1618 fullnodes=fullnodes,
1613 )
1619 )
1614
1620
1615
1621
1616 def _makecg3packer(
1622 def _makecg3packer(
1617 repo,
1623 repo,
1618 oldmatcher,
1624 oldmatcher,
1619 matcher,
1625 matcher,
1620 bundlecaps,
1626 bundlecaps,
1621 ellipses=False,
1627 ellipses=False,
1622 shallow=False,
1628 shallow=False,
1623 ellipsisroots=None,
1629 ellipsisroots=None,
1624 fullnodes=None,
1630 fullnodes=None,
1625 remote_sidedata=None,
1631 remote_sidedata=None,
1626 ):
1632 ):
1627 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1633 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1628 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1634 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1629 )
1635 )
1630
1636
1631 return cgpacker(
1637 return cgpacker(
1632 repo,
1638 repo,
1633 oldmatcher,
1639 oldmatcher,
1634 matcher,
1640 matcher,
1635 b'03',
1641 b'03',
1636 builddeltaheader=builddeltaheader,
1642 builddeltaheader=builddeltaheader,
1637 manifestsend=closechunk(),
1643 manifestsend=closechunk(),
1638 bundlecaps=bundlecaps,
1644 bundlecaps=bundlecaps,
1639 ellipses=ellipses,
1645 ellipses=ellipses,
1640 shallow=shallow,
1646 shallow=shallow,
1641 ellipsisroots=ellipsisroots,
1647 ellipsisroots=ellipsisroots,
1642 fullnodes=fullnodes,
1648 fullnodes=fullnodes,
1643 )
1649 )
1644
1650
1645
1651
1646 def _makecg4packer(
1652 def _makecg4packer(
1647 repo,
1653 repo,
1648 oldmatcher,
1654 oldmatcher,
1649 matcher,
1655 matcher,
1650 bundlecaps,
1656 bundlecaps,
1651 ellipses=False,
1657 ellipses=False,
1652 shallow=False,
1658 shallow=False,
1653 ellipsisroots=None,
1659 ellipsisroots=None,
1654 fullnodes=None,
1660 fullnodes=None,
1655 remote_sidedata=None,
1661 remote_sidedata=None,
1656 ):
1662 ):
1657 # Sidedata is in a separate chunk from the delta to differentiate
1663 # Sidedata is in a separate chunk from the delta to differentiate
1658 # "raw delta" and sidedata.
1664 # "raw delta" and sidedata.
1659 def builddeltaheader(d):
1665 def builddeltaheader(d):
1660 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1666 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1661 d.protocol_flags,
1667 d.protocol_flags,
1662 d.node,
1668 d.node,
1663 d.p1node,
1669 d.p1node,
1664 d.p2node,
1670 d.p2node,
1665 d.basenode,
1671 d.basenode,
1666 d.linknode,
1672 d.linknode,
1667 d.flags,
1673 d.flags,
1668 )
1674 )
1669
1675
1670 return cgpacker(
1676 return cgpacker(
1671 repo,
1677 repo,
1672 oldmatcher,
1678 oldmatcher,
1673 matcher,
1679 matcher,
1674 b'04',
1680 b'04',
1675 builddeltaheader=builddeltaheader,
1681 builddeltaheader=builddeltaheader,
1676 manifestsend=closechunk(),
1682 manifestsend=closechunk(),
1677 bundlecaps=bundlecaps,
1683 bundlecaps=bundlecaps,
1678 ellipses=ellipses,
1684 ellipses=ellipses,
1679 shallow=shallow,
1685 shallow=shallow,
1680 ellipsisroots=ellipsisroots,
1686 ellipsisroots=ellipsisroots,
1681 fullnodes=fullnodes,
1687 fullnodes=fullnodes,
1682 remote_sidedata=remote_sidedata,
1688 remote_sidedata=remote_sidedata,
1683 )
1689 )
1684
1690
1685
1691
1686 _packermap = {
1692 _packermap = {
1687 b'01': (_makecg1packer, cg1unpacker),
1693 b'01': (_makecg1packer, cg1unpacker),
1688 # cg2 adds support for exchanging generaldelta
1694 # cg2 adds support for exchanging generaldelta
1689 b'02': (_makecg2packer, cg2unpacker),
1695 b'02': (_makecg2packer, cg2unpacker),
1690 # cg3 adds support for exchanging revlog flags and treemanifests
1696 # cg3 adds support for exchanging revlog flags and treemanifests
1691 b'03': (_makecg3packer, cg3unpacker),
1697 b'03': (_makecg3packer, cg3unpacker),
1692 # ch4 adds support for exchanging sidedata
1698 # ch4 adds support for exchanging sidedata
1693 b'04': (_makecg4packer, cg4unpacker),
1699 b'04': (_makecg4packer, cg4unpacker),
1694 }
1700 }
1695
1701
1696
1702
1697 def allsupportedversions(repo):
1703 def allsupportedversions(repo):
1698 versions = set(_packermap.keys())
1704 versions = set(_packermap.keys())
1699 needv03 = False
1705 needv03 = False
1700 if (
1706 if (
1701 repo.ui.configbool(b'experimental', b'changegroup3')
1707 repo.ui.configbool(b'experimental', b'changegroup3')
1702 or repo.ui.configbool(b'experimental', b'treemanifest')
1708 or repo.ui.configbool(b'experimental', b'treemanifest')
1703 or scmutil.istreemanifest(repo)
1709 or scmutil.istreemanifest(repo)
1704 ):
1710 ):
1705 # we keep version 03 because we need to to exchange treemanifest data
1711 # we keep version 03 because we need to to exchange treemanifest data
1706 #
1712 #
1707 # we also keep vresion 01 and 02, because it is possible for repo to
1713 # we also keep vresion 01 and 02, because it is possible for repo to
1708 # contains both normal and tree manifest at the same time. so using
1714 # contains both normal and tree manifest at the same time. so using
1709 # older version to pull data is viable
1715 # older version to pull data is viable
1710 #
1716 #
1711 # (or even to push subset of history)
1717 # (or even to push subset of history)
1712 needv03 = True
1718 needv03 = True
1713 if not needv03:
1719 if not needv03:
1714 versions.discard(b'03')
1720 versions.discard(b'03')
1715 want_v4 = (
1721 want_v4 = (
1716 repo.ui.configbool(b'experimental', b'changegroup4')
1722 repo.ui.configbool(b'experimental', b'changegroup4')
1717 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1723 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1718 )
1724 )
1719 if not want_v4:
1725 if not want_v4:
1720 versions.discard(b'04')
1726 versions.discard(b'04')
1721 return versions
1727 return versions
1722
1728
1723
1729
1724 # Changegroup versions that can be applied to the repo
1730 # Changegroup versions that can be applied to the repo
1725 def supportedincomingversions(repo):
1731 def supportedincomingversions(repo):
1726 return allsupportedversions(repo)
1732 return allsupportedversions(repo)
1727
1733
1728
1734
1729 # Changegroup versions that can be created from the repo
1735 # Changegroup versions that can be created from the repo
1730 def supportedoutgoingversions(repo):
1736 def supportedoutgoingversions(repo):
1731 versions = allsupportedversions(repo)
1737 versions = allsupportedversions(repo)
1732 if scmutil.istreemanifest(repo):
1738 if scmutil.istreemanifest(repo):
1733 # Versions 01 and 02 support only flat manifests and it's just too
1739 # Versions 01 and 02 support only flat manifests and it's just too
1734 # expensive to convert between the flat manifest and tree manifest on
1740 # expensive to convert between the flat manifest and tree manifest on
1735 # the fly. Since tree manifests are hashed differently, all of history
1741 # the fly. Since tree manifests are hashed differently, all of history
1736 # would have to be converted. Instead, we simply don't even pretend to
1742 # would have to be converted. Instead, we simply don't even pretend to
1737 # support versions 01 and 02.
1743 # support versions 01 and 02.
1738 versions.discard(b'01')
1744 versions.discard(b'01')
1739 versions.discard(b'02')
1745 versions.discard(b'02')
1740 if requirements.NARROW_REQUIREMENT in repo.requirements:
1746 if requirements.NARROW_REQUIREMENT in repo.requirements:
1741 # Versions 01 and 02 don't support revlog flags, and we need to
1747 # Versions 01 and 02 don't support revlog flags, and we need to
1742 # support that for stripping and unbundling to work.
1748 # support that for stripping and unbundling to work.
1743 versions.discard(b'01')
1749 versions.discard(b'01')
1744 versions.discard(b'02')
1750 versions.discard(b'02')
1745 if LFS_REQUIREMENT in repo.requirements:
1751 if LFS_REQUIREMENT in repo.requirements:
1746 # Versions 01 and 02 don't support revlog flags, and we need to
1752 # Versions 01 and 02 don't support revlog flags, and we need to
1747 # mark LFS entries with REVIDX_EXTSTORED.
1753 # mark LFS entries with REVIDX_EXTSTORED.
1748 versions.discard(b'01')
1754 versions.discard(b'01')
1749 versions.discard(b'02')
1755 versions.discard(b'02')
1750
1756
1751 return versions
1757 return versions
1752
1758
1753
1759
1754 def localversion(repo):
1760 def localversion(repo):
1755 # Finds the best version to use for bundles that are meant to be used
1761 # Finds the best version to use for bundles that are meant to be used
1756 # locally, such as those from strip and shelve, and temporary bundles.
1762 # locally, such as those from strip and shelve, and temporary bundles.
1757 return max(supportedoutgoingversions(repo))
1763 return max(supportedoutgoingversions(repo))
1758
1764
1759
1765
1760 def safeversion(repo):
1766 def safeversion(repo):
1761 # Finds the smallest version that it's safe to assume clients of the repo
1767 # Finds the smallest version that it's safe to assume clients of the repo
1762 # will support. For example, all hg versions that support generaldelta also
1768 # will support. For example, all hg versions that support generaldelta also
1763 # support changegroup 02.
1769 # support changegroup 02.
1764 versions = supportedoutgoingversions(repo)
1770 versions = supportedoutgoingversions(repo)
1765 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1771 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1766 versions.discard(b'01')
1772 versions.discard(b'01')
1767 assert versions
1773 assert versions
1768 return min(versions)
1774 return min(versions)
1769
1775
1770
1776
1771 def getbundler(
1777 def getbundler(
1772 version,
1778 version,
1773 repo,
1779 repo,
1774 bundlecaps=None,
1780 bundlecaps=None,
1775 oldmatcher=None,
1781 oldmatcher=None,
1776 matcher=None,
1782 matcher=None,
1777 ellipses=False,
1783 ellipses=False,
1778 shallow=False,
1784 shallow=False,
1779 ellipsisroots=None,
1785 ellipsisroots=None,
1780 fullnodes=None,
1786 fullnodes=None,
1781 remote_sidedata=None,
1787 remote_sidedata=None,
1782 ):
1788 ):
1783 assert version in supportedoutgoingversions(repo)
1789 assert version in supportedoutgoingversions(repo)
1784
1790
1785 if matcher is None:
1791 if matcher is None:
1786 matcher = matchmod.always()
1792 matcher = matchmod.always()
1787 if oldmatcher is None:
1793 if oldmatcher is None:
1788 oldmatcher = matchmod.never()
1794 oldmatcher = matchmod.never()
1789
1795
1790 if version == b'01' and not matcher.always():
1796 if version == b'01' and not matcher.always():
1791 raise error.ProgrammingError(
1797 raise error.ProgrammingError(
1792 b'version 01 changegroups do not support sparse file matchers'
1798 b'version 01 changegroups do not support sparse file matchers'
1793 )
1799 )
1794
1800
1795 if ellipses and version in (b'01', b'02'):
1801 if ellipses and version in (b'01', b'02'):
1796 raise error.Abort(
1802 raise error.Abort(
1797 _(
1803 _(
1798 b'ellipsis nodes require at least cg3 on client and server, '
1804 b'ellipsis nodes require at least cg3 on client and server, '
1799 b'but negotiated version %s'
1805 b'but negotiated version %s'
1800 )
1806 )
1801 % version
1807 % version
1802 )
1808 )
1803
1809
1804 # Requested files could include files not in the local store. So
1810 # Requested files could include files not in the local store. So
1805 # filter those out.
1811 # filter those out.
1806 matcher = repo.narrowmatch(matcher)
1812 matcher = repo.narrowmatch(matcher)
1807
1813
1808 fn = _packermap[version][0]
1814 fn = _packermap[version][0]
1809 return fn(
1815 return fn(
1810 repo,
1816 repo,
1811 oldmatcher,
1817 oldmatcher,
1812 matcher,
1818 matcher,
1813 bundlecaps,
1819 bundlecaps,
1814 ellipses=ellipses,
1820 ellipses=ellipses,
1815 shallow=shallow,
1821 shallow=shallow,
1816 ellipsisroots=ellipsisroots,
1822 ellipsisroots=ellipsisroots,
1817 fullnodes=fullnodes,
1823 fullnodes=fullnodes,
1818 remote_sidedata=remote_sidedata,
1824 remote_sidedata=remote_sidedata,
1819 )
1825 )
1820
1826
1821
1827
1822 def getunbundler(version, fh, alg, extras=None):
1828 def getunbundler(version, fh, alg, extras=None):
1823 return _packermap[version][1](fh, alg, extras=extras)
1829 return _packermap[version][1](fh, alg, extras=extras)
1824
1830
1825
1831
1826 def _changegroupinfo(repo, nodes, source):
1832 def _changegroupinfo(repo, nodes, source):
1827 if repo.ui.verbose or source == b'bundle':
1833 if repo.ui.verbose or source == b'bundle':
1828 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1834 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1829 if repo.ui.debugflag:
1835 if repo.ui.debugflag:
1830 repo.ui.debug(b"list of changesets:\n")
1836 repo.ui.debug(b"list of changesets:\n")
1831 for node in nodes:
1837 for node in nodes:
1832 repo.ui.debug(b"%s\n" % hex(node))
1838 repo.ui.debug(b"%s\n" % hex(node))
1833
1839
1834
1840
1835 def makechangegroup(
1841 def makechangegroup(
1836 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1842 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1837 ):
1843 ):
1838 cgstream = makestream(
1844 cgstream = makestream(
1839 repo,
1845 repo,
1840 outgoing,
1846 outgoing,
1841 version,
1847 version,
1842 source,
1848 source,
1843 fastpath=fastpath,
1849 fastpath=fastpath,
1844 bundlecaps=bundlecaps,
1850 bundlecaps=bundlecaps,
1845 )
1851 )
1846 return getunbundler(
1852 return getunbundler(
1847 version,
1853 version,
1848 util.chunkbuffer(cgstream),
1854 util.chunkbuffer(cgstream),
1849 None,
1855 None,
1850 {b'clcount': len(outgoing.missing)},
1856 {b'clcount': len(outgoing.missing)},
1851 )
1857 )
1852
1858
1853
1859
1854 def makestream(
1860 def makestream(
1855 repo,
1861 repo,
1856 outgoing,
1862 outgoing,
1857 version,
1863 version,
1858 source,
1864 source,
1859 fastpath=False,
1865 fastpath=False,
1860 bundlecaps=None,
1866 bundlecaps=None,
1861 matcher=None,
1867 matcher=None,
1862 remote_sidedata=None,
1868 remote_sidedata=None,
1863 ):
1869 ):
1864 bundler = getbundler(
1870 bundler = getbundler(
1865 version,
1871 version,
1866 repo,
1872 repo,
1867 bundlecaps=bundlecaps,
1873 bundlecaps=bundlecaps,
1868 matcher=matcher,
1874 matcher=matcher,
1869 remote_sidedata=remote_sidedata,
1875 remote_sidedata=remote_sidedata,
1870 )
1876 )
1871
1877
1872 repo = repo.unfiltered()
1878 repo = repo.unfiltered()
1873 commonrevs = outgoing.common
1879 commonrevs = outgoing.common
1874 csets = outgoing.missing
1880 csets = outgoing.missing
1875 heads = outgoing.ancestorsof
1881 heads = outgoing.ancestorsof
1876 # We go through the fast path if we get told to, or if all (unfiltered
1882 # We go through the fast path if we get told to, or if all (unfiltered
1877 # heads have been requested (since we then know there all linkrevs will
1883 # heads have been requested (since we then know there all linkrevs will
1878 # be pulled by the client).
1884 # be pulled by the client).
1879 heads.sort()
1885 heads.sort()
1880 fastpathlinkrev = fastpath or (
1886 fastpathlinkrev = fastpath or (
1881 repo.filtername is None and heads == sorted(repo.heads())
1887 repo.filtername is None and heads == sorted(repo.heads())
1882 )
1888 )
1883
1889
1884 repo.hook(b'preoutgoing', throw=True, source=source)
1890 repo.hook(b'preoutgoing', throw=True, source=source)
1885 _changegroupinfo(repo, csets, source)
1891 _changegroupinfo(repo, csets, source)
1886 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1892 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1887
1893
1888
1894
1889 def _addchangegroupfiles(
1895 def _addchangegroupfiles(
1890 repo,
1896 repo,
1891 source,
1897 source,
1892 revmap,
1898 revmap,
1893 trp,
1899 trp,
1894 expectedfiles,
1900 expectedfiles,
1895 needfiles,
1901 needfiles,
1896 addrevisioncb=None,
1902 addrevisioncb=None,
1897 ):
1903 ):
1898 revisions = 0
1904 revisions = 0
1899 files = 0
1905 files = 0
1900 progress = repo.ui.makeprogress(
1906 progress = repo.ui.makeprogress(
1901 _(b'files'), unit=_(b'files'), total=expectedfiles
1907 _(b'files'), unit=_(b'files'), total=expectedfiles
1902 )
1908 )
1903 for chunkdata in iter(source.filelogheader, {}):
1909 for chunkdata in iter(source.filelogheader, {}):
1904 files += 1
1910 files += 1
1905 f = chunkdata[b"filename"]
1911 f = chunkdata[b"filename"]
1906 repo.ui.debug(b"adding %s revisions\n" % f)
1912 repo.ui.debug(b"adding %s revisions\n" % f)
1907 progress.increment()
1913 progress.increment()
1908 fl = repo.file(f)
1914 fl = repo.file(f)
1909 o = len(fl)
1915 o = len(fl)
1910 try:
1916 try:
1911 deltas = source.deltaiter()
1917 deltas = source.deltaiter()
1912 added = fl.addgroup(
1918 added = fl.addgroup(
1913 deltas,
1919 deltas,
1914 revmap,
1920 revmap,
1915 trp,
1921 trp,
1916 addrevisioncb=addrevisioncb,
1922 addrevisioncb=addrevisioncb,
1917 )
1923 )
1918 if not added:
1924 if not added:
1919 raise error.Abort(_(b"received file revlog group is empty"))
1925 raise error.Abort(_(b"received file revlog group is empty"))
1920 except error.CensoredBaseError as e:
1926 except error.CensoredBaseError as e:
1921 raise error.Abort(_(b"received delta base is censored: %s") % e)
1927 raise error.Abort(_(b"received delta base is censored: %s") % e)
1922 revisions += len(fl) - o
1928 revisions += len(fl) - o
1923 if f in needfiles:
1929 if f in needfiles:
1924 needs = needfiles[f]
1930 needs = needfiles[f]
1925 for new in pycompat.xrange(o, len(fl)):
1931 for new in pycompat.xrange(o, len(fl)):
1926 n = fl.node(new)
1932 n = fl.node(new)
1927 if n in needs:
1933 if n in needs:
1928 needs.remove(n)
1934 needs.remove(n)
1929 else:
1935 else:
1930 raise error.Abort(_(b"received spurious file revlog entry"))
1936 raise error.Abort(_(b"received spurious file revlog entry"))
1931 if not needs:
1937 if not needs:
1932 del needfiles[f]
1938 del needfiles[f]
1933 progress.complete()
1939 progress.complete()
1934
1940
1935 for f, needs in pycompat.iteritems(needfiles):
1941 for f, needs in pycompat.iteritems(needfiles):
1936 fl = repo.file(f)
1942 fl = repo.file(f)
1937 for n in needs:
1943 for n in needs:
1938 try:
1944 try:
1939 fl.rev(n)
1945 fl.rev(n)
1940 except error.LookupError:
1946 except error.LookupError:
1941 raise error.Abort(
1947 raise error.Abort(
1942 _(b'missing file data for %s:%s - run hg verify')
1948 _(b'missing file data for %s:%s - run hg verify')
1943 % (f, hex(n))
1949 % (f, hex(n))
1944 )
1950 )
1945
1951
1946 return revisions, files
1952 return revisions, files
@@ -1,2023 +1,2016 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_HASCOPIESINFO = 1 << 12
30 REVISION_FLAG_HASCOPIESINFO = 1 << 12
31
31
32 REVISION_FLAGS_KNOWN = (
32 REVISION_FLAGS_KNOWN = (
33 REVISION_FLAG_CENSORED
33 REVISION_FLAG_CENSORED
34 | REVISION_FLAG_ELLIPSIS
34 | REVISION_FLAG_ELLIPSIS
35 | REVISION_FLAG_EXTSTORED
35 | REVISION_FLAG_EXTSTORED
36 | REVISION_FLAG_HASCOPIESINFO
36 | REVISION_FLAG_HASCOPIESINFO
37 )
37 )
38
38
39 CG_DELTAMODE_STD = b'default'
39 CG_DELTAMODE_STD = b'default'
40 CG_DELTAMODE_PREV = b'previous'
40 CG_DELTAMODE_PREV = b'previous'
41 CG_DELTAMODE_FULL = b'fulltext'
41 CG_DELTAMODE_FULL = b'fulltext'
42 CG_DELTAMODE_P1 = b'p1'
42 CG_DELTAMODE_P1 = b'p1'
43
43
44
44
45 class ipeerconnection(interfaceutil.Interface):
45 class ipeerconnection(interfaceutil.Interface):
46 """Represents a "connection" to a repository.
46 """Represents a "connection" to a repository.
47
47
48 This is the base interface for representing a connection to a repository.
48 This is the base interface for representing a connection to a repository.
49 It holds basic properties and methods applicable to all peer types.
49 It holds basic properties and methods applicable to all peer types.
50
50
51 This is not a complete interface definition and should not be used
51 This is not a complete interface definition and should not be used
52 outside of this module.
52 outside of this module.
53 """
53 """
54
54
55 ui = interfaceutil.Attribute("""ui.ui instance""")
55 ui = interfaceutil.Attribute("""ui.ui instance""")
56
56
57 def url():
57 def url():
58 """Returns a URL string representing this peer.
58 """Returns a URL string representing this peer.
59
59
60 Currently, implementations expose the raw URL used to construct the
60 Currently, implementations expose the raw URL used to construct the
61 instance. It may contain credentials as part of the URL. The
61 instance. It may contain credentials as part of the URL. The
62 expectations of the value aren't well-defined and this could lead to
62 expectations of the value aren't well-defined and this could lead to
63 data leakage.
63 data leakage.
64
64
65 TODO audit/clean consumers and more clearly define the contents of this
65 TODO audit/clean consumers and more clearly define the contents of this
66 value.
66 value.
67 """
67 """
68
68
69 def local():
69 def local():
70 """Returns a local repository instance.
70 """Returns a local repository instance.
71
71
72 If the peer represents a local repository, returns an object that
72 If the peer represents a local repository, returns an object that
73 can be used to interface with it. Otherwise returns ``None``.
73 can be used to interface with it. Otherwise returns ``None``.
74 """
74 """
75
75
76 def peer():
76 def peer():
77 """Returns an object conforming to this interface.
77 """Returns an object conforming to this interface.
78
78
79 Most implementations will ``return self``.
79 Most implementations will ``return self``.
80 """
80 """
81
81
82 def canpush():
82 def canpush():
83 """Returns a boolean indicating if this peer can be pushed to."""
83 """Returns a boolean indicating if this peer can be pushed to."""
84
84
85 def close():
85 def close():
86 """Close the connection to this peer.
86 """Close the connection to this peer.
87
87
88 This is called when the peer will no longer be used. Resources
88 This is called when the peer will no longer be used. Resources
89 associated with the peer should be cleaned up.
89 associated with the peer should be cleaned up.
90 """
90 """
91
91
92
92
93 class ipeercapabilities(interfaceutil.Interface):
93 class ipeercapabilities(interfaceutil.Interface):
94 """Peer sub-interface related to capabilities."""
94 """Peer sub-interface related to capabilities."""
95
95
96 def capable(name):
96 def capable(name):
97 """Determine support for a named capability.
97 """Determine support for a named capability.
98
98
99 Returns ``False`` if capability not supported.
99 Returns ``False`` if capability not supported.
100
100
101 Returns ``True`` if boolean capability is supported. Returns a string
101 Returns ``True`` if boolean capability is supported. Returns a string
102 if capability support is non-boolean.
102 if capability support is non-boolean.
103
103
104 Capability strings may or may not map to wire protocol capabilities.
104 Capability strings may or may not map to wire protocol capabilities.
105 """
105 """
106
106
107 def requirecap(name, purpose):
107 def requirecap(name, purpose):
108 """Require a capability to be present.
108 """Require a capability to be present.
109
109
110 Raises a ``CapabilityError`` if the capability isn't present.
110 Raises a ``CapabilityError`` if the capability isn't present.
111 """
111 """
112
112
113
113
114 class ipeercommands(interfaceutil.Interface):
114 class ipeercommands(interfaceutil.Interface):
115 """Client-side interface for communicating over the wire protocol.
115 """Client-side interface for communicating over the wire protocol.
116
116
117 This interface is used as a gateway to the Mercurial wire protocol.
117 This interface is used as a gateway to the Mercurial wire protocol.
118 methods commonly call wire protocol commands of the same name.
118 methods commonly call wire protocol commands of the same name.
119 """
119 """
120
120
121 def branchmap():
121 def branchmap():
122 """Obtain heads in named branches.
122 """Obtain heads in named branches.
123
123
124 Returns a dict mapping branch name to an iterable of nodes that are
124 Returns a dict mapping branch name to an iterable of nodes that are
125 heads on that branch.
125 heads on that branch.
126 """
126 """
127
127
128 def capabilities():
128 def capabilities():
129 """Obtain capabilities of the peer.
129 """Obtain capabilities of the peer.
130
130
131 Returns a set of string capabilities.
131 Returns a set of string capabilities.
132 """
132 """
133
133
134 def clonebundles():
134 def clonebundles():
135 """Obtains the clone bundles manifest for the repo.
135 """Obtains the clone bundles manifest for the repo.
136
136
137 Returns the manifest as unparsed bytes.
137 Returns the manifest as unparsed bytes.
138 """
138 """
139
139
140 def debugwireargs(one, two, three=None, four=None, five=None):
140 def debugwireargs(one, two, three=None, four=None, five=None):
141 """Used to facilitate debugging of arguments passed over the wire."""
141 """Used to facilitate debugging of arguments passed over the wire."""
142
142
143 def getbundle(source, **kwargs):
143 def getbundle(source, **kwargs):
144 """Obtain remote repository data as a bundle.
144 """Obtain remote repository data as a bundle.
145
145
146 This command is how the bulk of repository data is transferred from
146 This command is how the bulk of repository data is transferred from
147 the peer to the local repository
147 the peer to the local repository
148
148
149 Returns a generator of bundle data.
149 Returns a generator of bundle data.
150 """
150 """
151
151
152 def heads():
152 def heads():
153 """Determine all known head revisions in the peer.
153 """Determine all known head revisions in the peer.
154
154
155 Returns an iterable of binary nodes.
155 Returns an iterable of binary nodes.
156 """
156 """
157
157
158 def known(nodes):
158 def known(nodes):
159 """Determine whether multiple nodes are known.
159 """Determine whether multiple nodes are known.
160
160
161 Accepts an iterable of nodes whose presence to check for.
161 Accepts an iterable of nodes whose presence to check for.
162
162
163 Returns an iterable of booleans indicating of the corresponding node
163 Returns an iterable of booleans indicating of the corresponding node
164 at that index is known to the peer.
164 at that index is known to the peer.
165 """
165 """
166
166
167 def listkeys(namespace):
167 def listkeys(namespace):
168 """Obtain all keys in a pushkey namespace.
168 """Obtain all keys in a pushkey namespace.
169
169
170 Returns an iterable of key names.
170 Returns an iterable of key names.
171 """
171 """
172
172
173 def lookup(key):
173 def lookup(key):
174 """Resolve a value to a known revision.
174 """Resolve a value to a known revision.
175
175
176 Returns a binary node of the resolved revision on success.
176 Returns a binary node of the resolved revision on success.
177 """
177 """
178
178
179 def pushkey(namespace, key, old, new):
179 def pushkey(namespace, key, old, new):
180 """Set a value using the ``pushkey`` protocol.
180 """Set a value using the ``pushkey`` protocol.
181
181
182 Arguments correspond to the pushkey namespace and key to operate on and
182 Arguments correspond to the pushkey namespace and key to operate on and
183 the old and new values for that key.
183 the old and new values for that key.
184
184
185 Returns a string with the peer result. The value inside varies by the
185 Returns a string with the peer result. The value inside varies by the
186 namespace.
186 namespace.
187 """
187 """
188
188
189 def stream_out():
189 def stream_out():
190 """Obtain streaming clone data.
190 """Obtain streaming clone data.
191
191
192 Successful result should be a generator of data chunks.
192 Successful result should be a generator of data chunks.
193 """
193 """
194
194
195 def unbundle(bundle, heads, url):
195 def unbundle(bundle, heads, url):
196 """Transfer repository data to the peer.
196 """Transfer repository data to the peer.
197
197
198 This is how the bulk of data during a push is transferred.
198 This is how the bulk of data during a push is transferred.
199
199
200 Returns the integer number of heads added to the peer.
200 Returns the integer number of heads added to the peer.
201 """
201 """
202
202
203
203
204 class ipeerlegacycommands(interfaceutil.Interface):
204 class ipeerlegacycommands(interfaceutil.Interface):
205 """Interface for implementing support for legacy wire protocol commands.
205 """Interface for implementing support for legacy wire protocol commands.
206
206
207 Wire protocol commands transition to legacy status when they are no longer
207 Wire protocol commands transition to legacy status when they are no longer
208 used by modern clients. To facilitate identifying which commands are
208 used by modern clients. To facilitate identifying which commands are
209 legacy, the interfaces are split.
209 legacy, the interfaces are split.
210 """
210 """
211
211
212 def between(pairs):
212 def between(pairs):
213 """Obtain nodes between pairs of nodes.
213 """Obtain nodes between pairs of nodes.
214
214
215 ``pairs`` is an iterable of node pairs.
215 ``pairs`` is an iterable of node pairs.
216
216
217 Returns an iterable of iterables of nodes corresponding to each
217 Returns an iterable of iterables of nodes corresponding to each
218 requested pair.
218 requested pair.
219 """
219 """
220
220
221 def branches(nodes):
221 def branches(nodes):
222 """Obtain ancestor changesets of specific nodes back to a branch point.
222 """Obtain ancestor changesets of specific nodes back to a branch point.
223
223
224 For each requested node, the peer finds the first ancestor node that is
224 For each requested node, the peer finds the first ancestor node that is
225 a DAG root or is a merge.
225 a DAG root or is a merge.
226
226
227 Returns an iterable of iterables with the resolved values for each node.
227 Returns an iterable of iterables with the resolved values for each node.
228 """
228 """
229
229
230 def changegroup(nodes, source):
230 def changegroup(nodes, source):
231 """Obtain a changegroup with data for descendants of specified nodes."""
231 """Obtain a changegroup with data for descendants of specified nodes."""
232
232
233 def changegroupsubset(bases, heads, source):
233 def changegroupsubset(bases, heads, source):
234 pass
234 pass
235
235
236
236
237 class ipeercommandexecutor(interfaceutil.Interface):
237 class ipeercommandexecutor(interfaceutil.Interface):
238 """Represents a mechanism to execute remote commands.
238 """Represents a mechanism to execute remote commands.
239
239
240 This is the primary interface for requesting that wire protocol commands
240 This is the primary interface for requesting that wire protocol commands
241 be executed. Instances of this interface are active in a context manager
241 be executed. Instances of this interface are active in a context manager
242 and have a well-defined lifetime. When the context manager exits, all
242 and have a well-defined lifetime. When the context manager exits, all
243 outstanding requests are waited on.
243 outstanding requests are waited on.
244 """
244 """
245
245
246 def callcommand(name, args):
246 def callcommand(name, args):
247 """Request that a named command be executed.
247 """Request that a named command be executed.
248
248
249 Receives the command name and a dictionary of command arguments.
249 Receives the command name and a dictionary of command arguments.
250
250
251 Returns a ``concurrent.futures.Future`` that will resolve to the
251 Returns a ``concurrent.futures.Future`` that will resolve to the
252 result of that command request. That exact value is left up to
252 result of that command request. That exact value is left up to
253 the implementation and possibly varies by command.
253 the implementation and possibly varies by command.
254
254
255 Not all commands can coexist with other commands in an executor
255 Not all commands can coexist with other commands in an executor
256 instance: it depends on the underlying wire protocol transport being
256 instance: it depends on the underlying wire protocol transport being
257 used and the command itself.
257 used and the command itself.
258
258
259 Implementations MAY call ``sendcommands()`` automatically if the
259 Implementations MAY call ``sendcommands()`` automatically if the
260 requested command can not coexist with other commands in this executor.
260 requested command can not coexist with other commands in this executor.
261
261
262 Implementations MAY call ``sendcommands()`` automatically when the
262 Implementations MAY call ``sendcommands()`` automatically when the
263 future's ``result()`` is called. So, consumers using multiple
263 future's ``result()`` is called. So, consumers using multiple
264 commands with an executor MUST ensure that ``result()`` is not called
264 commands with an executor MUST ensure that ``result()`` is not called
265 until all command requests have been issued.
265 until all command requests have been issued.
266 """
266 """
267
267
268 def sendcommands():
268 def sendcommands():
269 """Trigger submission of queued command requests.
269 """Trigger submission of queued command requests.
270
270
271 Not all transports submit commands as soon as they are requested to
271 Not all transports submit commands as soon as they are requested to
272 run. When called, this method forces queued command requests to be
272 run. When called, this method forces queued command requests to be
273 issued. It will no-op if all commands have already been sent.
273 issued. It will no-op if all commands have already been sent.
274
274
275 When called, no more new commands may be issued with this executor.
275 When called, no more new commands may be issued with this executor.
276 """
276 """
277
277
278 def close():
278 def close():
279 """Signal that this command request is finished.
279 """Signal that this command request is finished.
280
280
281 When called, no more new commands may be issued. All outstanding
281 When called, no more new commands may be issued. All outstanding
282 commands that have previously been issued are waited on before
282 commands that have previously been issued are waited on before
283 returning. This not only includes waiting for the futures to resolve,
283 returning. This not only includes waiting for the futures to resolve,
284 but also waiting for all response data to arrive. In other words,
284 but also waiting for all response data to arrive. In other words,
285 calling this waits for all on-wire state for issued command requests
285 calling this waits for all on-wire state for issued command requests
286 to finish.
286 to finish.
287
287
288 When used as a context manager, this method is called when exiting the
288 When used as a context manager, this method is called when exiting the
289 context manager.
289 context manager.
290
290
291 This method may call ``sendcommands()`` if there are buffered commands.
291 This method may call ``sendcommands()`` if there are buffered commands.
292 """
292 """
293
293
294
294
295 class ipeerrequests(interfaceutil.Interface):
295 class ipeerrequests(interfaceutil.Interface):
296 """Interface for executing commands on a peer."""
296 """Interface for executing commands on a peer."""
297
297
298 limitedarguments = interfaceutil.Attribute(
298 limitedarguments = interfaceutil.Attribute(
299 """True if the peer cannot receive large argument value for commands."""
299 """True if the peer cannot receive large argument value for commands."""
300 )
300 )
301
301
302 def commandexecutor():
302 def commandexecutor():
303 """A context manager that resolves to an ipeercommandexecutor.
303 """A context manager that resolves to an ipeercommandexecutor.
304
304
305 The object this resolves to can be used to issue command requests
305 The object this resolves to can be used to issue command requests
306 to the peer.
306 to the peer.
307
307
308 Callers should call its ``callcommand`` method to issue command
308 Callers should call its ``callcommand`` method to issue command
309 requests.
309 requests.
310
310
311 A new executor should be obtained for each distinct set of commands
311 A new executor should be obtained for each distinct set of commands
312 (possibly just a single command) that the consumer wants to execute
312 (possibly just a single command) that the consumer wants to execute
313 as part of a single operation or round trip. This is because some
313 as part of a single operation or round trip. This is because some
314 peers are half-duplex and/or don't support persistent connections.
314 peers are half-duplex and/or don't support persistent connections.
315 e.g. in the case of HTTP peers, commands sent to an executor represent
315 e.g. in the case of HTTP peers, commands sent to an executor represent
316 a single HTTP request. While some peers may support multiple command
316 a single HTTP request. While some peers may support multiple command
317 sends over the wire per executor, consumers need to code to the least
317 sends over the wire per executor, consumers need to code to the least
318 capable peer. So it should be assumed that command executors buffer
318 capable peer. So it should be assumed that command executors buffer
319 called commands until they are told to send them and that each
319 called commands until they are told to send them and that each
320 command executor could result in a new connection or wire-level request
320 command executor could result in a new connection or wire-level request
321 being issued.
321 being issued.
322 """
322 """
323
323
324
324
325 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
325 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
326 """Unified interface for peer repositories.
326 """Unified interface for peer repositories.
327
327
328 All peer instances must conform to this interface.
328 All peer instances must conform to this interface.
329 """
329 """
330
330
331
331
332 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
332 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
333 """Unified peer interface for wire protocol version 2 peers."""
333 """Unified peer interface for wire protocol version 2 peers."""
334
334
335 apidescriptor = interfaceutil.Attribute(
335 apidescriptor = interfaceutil.Attribute(
336 """Data structure holding description of server API."""
336 """Data structure holding description of server API."""
337 )
337 )
338
338
339
339
340 @interfaceutil.implementer(ipeerbase)
340 @interfaceutil.implementer(ipeerbase)
341 class peer(object):
341 class peer(object):
342 """Base class for peer repositories."""
342 """Base class for peer repositories."""
343
343
344 limitedarguments = False
344 limitedarguments = False
345
345
346 def capable(self, name):
346 def capable(self, name):
347 caps = self.capabilities()
347 caps = self.capabilities()
348 if name in caps:
348 if name in caps:
349 return True
349 return True
350
350
351 name = b'%s=' % name
351 name = b'%s=' % name
352 for cap in caps:
352 for cap in caps:
353 if cap.startswith(name):
353 if cap.startswith(name):
354 return cap[len(name) :]
354 return cap[len(name) :]
355
355
356 return False
356 return False
357
357
358 def requirecap(self, name, purpose):
358 def requirecap(self, name, purpose):
359 if self.capable(name):
359 if self.capable(name):
360 return
360 return
361
361
362 raise error.CapabilityError(
362 raise error.CapabilityError(
363 _(
363 _(
364 b'cannot %s; remote repository does not support the '
364 b'cannot %s; remote repository does not support the '
365 b'\'%s\' capability'
365 b'\'%s\' capability'
366 )
366 )
367 % (purpose, name)
367 % (purpose, name)
368 )
368 )
369
369
370
370
371 class iverifyproblem(interfaceutil.Interface):
371 class iverifyproblem(interfaceutil.Interface):
372 """Represents a problem with the integrity of the repository.
372 """Represents a problem with the integrity of the repository.
373
373
374 Instances of this interface are emitted to describe an integrity issue
374 Instances of this interface are emitted to describe an integrity issue
375 with a repository (e.g. corrupt storage, missing data, etc).
375 with a repository (e.g. corrupt storage, missing data, etc).
376
376
377 Instances are essentially messages associated with severity.
377 Instances are essentially messages associated with severity.
378 """
378 """
379
379
380 warning = interfaceutil.Attribute(
380 warning = interfaceutil.Attribute(
381 """Message indicating a non-fatal problem."""
381 """Message indicating a non-fatal problem."""
382 )
382 )
383
383
384 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
384 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
385
385
386 node = interfaceutil.Attribute(
386 node = interfaceutil.Attribute(
387 """Revision encountering the problem.
387 """Revision encountering the problem.
388
388
389 ``None`` means the problem doesn't apply to a single revision.
389 ``None`` means the problem doesn't apply to a single revision.
390 """
390 """
391 )
391 )
392
392
393
393
394 class irevisiondelta(interfaceutil.Interface):
394 class irevisiondelta(interfaceutil.Interface):
395 """Represents a delta between one revision and another.
395 """Represents a delta between one revision and another.
396
396
397 Instances convey enough information to allow a revision to be exchanged
397 Instances convey enough information to allow a revision to be exchanged
398 with another repository.
398 with another repository.
399
399
400 Instances represent the fulltext revision data or a delta against
400 Instances represent the fulltext revision data or a delta against
401 another revision. Therefore the ``revision`` and ``delta`` attributes
401 another revision. Therefore the ``revision`` and ``delta`` attributes
402 are mutually exclusive.
402 are mutually exclusive.
403
403
404 Typically used for changegroup generation.
404 Typically used for changegroup generation.
405 """
405 """
406
406
407 node = interfaceutil.Attribute("""20 byte node of this revision.""")
407 node = interfaceutil.Attribute("""20 byte node of this revision.""")
408
408
409 p1node = interfaceutil.Attribute(
409 p1node = interfaceutil.Attribute(
410 """20 byte node of 1st parent of this revision."""
410 """20 byte node of 1st parent of this revision."""
411 )
411 )
412
412
413 p2node = interfaceutil.Attribute(
413 p2node = interfaceutil.Attribute(
414 """20 byte node of 2nd parent of this revision."""
414 """20 byte node of 2nd parent of this revision."""
415 )
415 )
416
416
417 linknode = interfaceutil.Attribute(
417 linknode = interfaceutil.Attribute(
418 """20 byte node of the changelog revision this node is linked to."""
418 """20 byte node of the changelog revision this node is linked to."""
419 )
419 )
420
420
421 flags = interfaceutil.Attribute(
421 flags = interfaceutil.Attribute(
422 """2 bytes of integer flags that apply to this revision.
422 """2 bytes of integer flags that apply to this revision.
423
423
424 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
424 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
425 """
425 """
426 )
426 )
427
427
428 basenode = interfaceutil.Attribute(
428 basenode = interfaceutil.Attribute(
429 """20 byte node of the revision this data is a delta against.
429 """20 byte node of the revision this data is a delta against.
430
430
431 ``nullid`` indicates that the revision is a full revision and not
431 ``nullid`` indicates that the revision is a full revision and not
432 a delta.
432 a delta.
433 """
433 """
434 )
434 )
435
435
436 baserevisionsize = interfaceutil.Attribute(
436 baserevisionsize = interfaceutil.Attribute(
437 """Size of base revision this delta is against.
437 """Size of base revision this delta is against.
438
438
439 May be ``None`` if ``basenode`` is ``nullid``.
439 May be ``None`` if ``basenode`` is ``nullid``.
440 """
440 """
441 )
441 )
442
442
443 revision = interfaceutil.Attribute(
443 revision = interfaceutil.Attribute(
444 """Raw fulltext of revision data for this node."""
444 """Raw fulltext of revision data for this node."""
445 )
445 )
446
446
447 delta = interfaceutil.Attribute(
447 delta = interfaceutil.Attribute(
448 """Delta between ``basenode`` and ``node``.
448 """Delta between ``basenode`` and ``node``.
449
449
450 Stored in the bdiff delta format.
450 Stored in the bdiff delta format.
451 """
451 """
452 )
452 )
453
453
454 sidedata = interfaceutil.Attribute(
454 sidedata = interfaceutil.Attribute(
455 """Raw sidedata bytes for the given revision."""
455 """Raw sidedata bytes for the given revision."""
456 )
456 )
457
457
458 protocol_flags = interfaceutil.Attribute(
458 protocol_flags = interfaceutil.Attribute(
459 """Single byte of integer flags that can influence the protocol.
459 """Single byte of integer flags that can influence the protocol.
460
460
461 This is a bitwise composition of the ``storageutil.CG_FLAG*`` constants.
461 This is a bitwise composition of the ``storageutil.CG_FLAG*`` constants.
462 """
462 """
463 )
463 )
464
464
465
465
466 class ifilerevisionssequence(interfaceutil.Interface):
466 class ifilerevisionssequence(interfaceutil.Interface):
467 """Contains index data for all revisions of a file.
467 """Contains index data for all revisions of a file.
468
468
469 Types implementing this behave like lists of tuples. The index
469 Types implementing this behave like lists of tuples. The index
470 in the list corresponds to the revision number. The values contain
470 in the list corresponds to the revision number. The values contain
471 index metadata.
471 index metadata.
472
472
473 The *null* revision (revision number -1) is always the last item
473 The *null* revision (revision number -1) is always the last item
474 in the index.
474 in the index.
475 """
475 """
476
476
477 def __len__():
477 def __len__():
478 """The total number of revisions."""
478 """The total number of revisions."""
479
479
480 def __getitem__(rev):
480 def __getitem__(rev):
481 """Returns the object having a specific revision number.
481 """Returns the object having a specific revision number.
482
482
483 Returns an 8-tuple with the following fields:
483 Returns an 8-tuple with the following fields:
484
484
485 offset+flags
485 offset+flags
486 Contains the offset and flags for the revision. 64-bit unsigned
486 Contains the offset and flags for the revision. 64-bit unsigned
487 integer where first 6 bytes are the offset and the next 2 bytes
487 integer where first 6 bytes are the offset and the next 2 bytes
488 are flags. The offset can be 0 if it is not used by the store.
488 are flags. The offset can be 0 if it is not used by the store.
489 compressed size
489 compressed size
490 Size of the revision data in the store. It can be 0 if it isn't
490 Size of the revision data in the store. It can be 0 if it isn't
491 needed by the store.
491 needed by the store.
492 uncompressed size
492 uncompressed size
493 Fulltext size. It can be 0 if it isn't needed by the store.
493 Fulltext size. It can be 0 if it isn't needed by the store.
494 base revision
494 base revision
495 Revision number of revision the delta for storage is encoded
495 Revision number of revision the delta for storage is encoded
496 against. -1 indicates not encoded against a base revision.
496 against. -1 indicates not encoded against a base revision.
497 link revision
497 link revision
498 Revision number of changelog revision this entry is related to.
498 Revision number of changelog revision this entry is related to.
499 p1 revision
499 p1 revision
500 Revision number of 1st parent. -1 if no 1st parent.
500 Revision number of 1st parent. -1 if no 1st parent.
501 p2 revision
501 p2 revision
502 Revision number of 2nd parent. -1 if no 1st parent.
502 Revision number of 2nd parent. -1 if no 1st parent.
503 node
503 node
504 Binary node value for this revision number.
504 Binary node value for this revision number.
505
505
506 Negative values should index off the end of the sequence. ``-1``
506 Negative values should index off the end of the sequence. ``-1``
507 should return the null revision. ``-2`` should return the most
507 should return the null revision. ``-2`` should return the most
508 recent revision.
508 recent revision.
509 """
509 """
510
510
511 def __contains__(rev):
511 def __contains__(rev):
512 """Whether a revision number exists."""
512 """Whether a revision number exists."""
513
513
514 def insert(self, i, entry):
514 def insert(self, i, entry):
515 """Add an item to the index at specific revision."""
515 """Add an item to the index at specific revision."""
516
516
517
517
518 class ifileindex(interfaceutil.Interface):
518 class ifileindex(interfaceutil.Interface):
519 """Storage interface for index data of a single file.
519 """Storage interface for index data of a single file.
520
520
521 File storage data is divided into index metadata and data storage.
521 File storage data is divided into index metadata and data storage.
522 This interface defines the index portion of the interface.
522 This interface defines the index portion of the interface.
523
523
524 The index logically consists of:
524 The index logically consists of:
525
525
526 * A mapping between revision numbers and nodes.
526 * A mapping between revision numbers and nodes.
527 * DAG data (storing and querying the relationship between nodes).
527 * DAG data (storing and querying the relationship between nodes).
528 * Metadata to facilitate storage.
528 * Metadata to facilitate storage.
529 """
529 """
530
530
531 nullid = interfaceutil.Attribute(
531 nullid = interfaceutil.Attribute(
532 """node for the null revision for use as delta base."""
532 """node for the null revision for use as delta base."""
533 )
533 )
534
534
535 def __len__():
535 def __len__():
536 """Obtain the number of revisions stored for this file."""
536 """Obtain the number of revisions stored for this file."""
537
537
538 def __iter__():
538 def __iter__():
539 """Iterate over revision numbers for this file."""
539 """Iterate over revision numbers for this file."""
540
540
541 def hasnode(node):
541 def hasnode(node):
542 """Returns a bool indicating if a node is known to this store.
542 """Returns a bool indicating if a node is known to this store.
543
543
544 Implementations must only return True for full, binary node values:
544 Implementations must only return True for full, binary node values:
545 hex nodes, revision numbers, and partial node matches must be
545 hex nodes, revision numbers, and partial node matches must be
546 rejected.
546 rejected.
547
547
548 The null node is never present.
548 The null node is never present.
549 """
549 """
550
550
551 def revs(start=0, stop=None):
551 def revs(start=0, stop=None):
552 """Iterate over revision numbers for this file, with control."""
552 """Iterate over revision numbers for this file, with control."""
553
553
554 def parents(node):
554 def parents(node):
555 """Returns a 2-tuple of parent nodes for a revision.
555 """Returns a 2-tuple of parent nodes for a revision.
556
556
557 Values will be ``nullid`` if the parent is empty.
557 Values will be ``nullid`` if the parent is empty.
558 """
558 """
559
559
560 def parentrevs(rev):
560 def parentrevs(rev):
561 """Like parents() but operates on revision numbers."""
561 """Like parents() but operates on revision numbers."""
562
562
563 def rev(node):
563 def rev(node):
564 """Obtain the revision number given a node.
564 """Obtain the revision number given a node.
565
565
566 Raises ``error.LookupError`` if the node is not known.
566 Raises ``error.LookupError`` if the node is not known.
567 """
567 """
568
568
569 def node(rev):
569 def node(rev):
570 """Obtain the node value given a revision number.
570 """Obtain the node value given a revision number.
571
571
572 Raises ``IndexError`` if the node is not known.
572 Raises ``IndexError`` if the node is not known.
573 """
573 """
574
574
575 def lookup(node):
575 def lookup(node):
576 """Attempt to resolve a value to a node.
576 """Attempt to resolve a value to a node.
577
577
578 Value can be a binary node, hex node, revision number, or a string
578 Value can be a binary node, hex node, revision number, or a string
579 that can be converted to an integer.
579 that can be converted to an integer.
580
580
581 Raises ``error.LookupError`` if a node could not be resolved.
581 Raises ``error.LookupError`` if a node could not be resolved.
582 """
582 """
583
583
584 def linkrev(rev):
584 def linkrev(rev):
585 """Obtain the changeset revision number a revision is linked to."""
585 """Obtain the changeset revision number a revision is linked to."""
586
586
587 def iscensored(rev):
587 def iscensored(rev):
588 """Return whether a revision's content has been censored."""
588 """Return whether a revision's content has been censored."""
589
589
590 def commonancestorsheads(node1, node2):
590 def commonancestorsheads(node1, node2):
591 """Obtain an iterable of nodes containing heads of common ancestors.
591 """Obtain an iterable of nodes containing heads of common ancestors.
592
592
593 See ``ancestor.commonancestorsheads()``.
593 See ``ancestor.commonancestorsheads()``.
594 """
594 """
595
595
596 def descendants(revs):
596 def descendants(revs):
597 """Obtain descendant revision numbers for a set of revision numbers.
597 """Obtain descendant revision numbers for a set of revision numbers.
598
598
599 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
599 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
600 """
600 """
601
601
602 def heads(start=None, stop=None):
602 def heads(start=None, stop=None):
603 """Obtain a list of nodes that are DAG heads, with control.
603 """Obtain a list of nodes that are DAG heads, with control.
604
604
605 The set of revisions examined can be limited by specifying
605 The set of revisions examined can be limited by specifying
606 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
606 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
607 iterable of nodes. DAG traversal starts at earlier revision
607 iterable of nodes. DAG traversal starts at earlier revision
608 ``start`` and iterates forward until any node in ``stop`` is
608 ``start`` and iterates forward until any node in ``stop`` is
609 encountered.
609 encountered.
610 """
610 """
611
611
612 def children(node):
612 def children(node):
613 """Obtain nodes that are children of a node.
613 """Obtain nodes that are children of a node.
614
614
615 Returns a list of nodes.
615 Returns a list of nodes.
616 """
616 """
617
617
618
618
619 class ifiledata(interfaceutil.Interface):
619 class ifiledata(interfaceutil.Interface):
620 """Storage interface for data storage of a specific file.
620 """Storage interface for data storage of a specific file.
621
621
622 This complements ``ifileindex`` and provides an interface for accessing
622 This complements ``ifileindex`` and provides an interface for accessing
623 data for a tracked file.
623 data for a tracked file.
624 """
624 """
625
625
626 def size(rev):
626 def size(rev):
627 """Obtain the fulltext size of file data.
627 """Obtain the fulltext size of file data.
628
628
629 Any metadata is excluded from size measurements.
629 Any metadata is excluded from size measurements.
630 """
630 """
631
631
632 def revision(node, raw=False):
632 def revision(node, raw=False):
633 """Obtain fulltext data for a node.
633 """Obtain fulltext data for a node.
634
634
635 By default, any storage transformations are applied before the data
635 By default, any storage transformations are applied before the data
636 is returned. If ``raw`` is True, non-raw storage transformations
636 is returned. If ``raw`` is True, non-raw storage transformations
637 are not applied.
637 are not applied.
638
638
639 The fulltext data may contain a header containing metadata. Most
639 The fulltext data may contain a header containing metadata. Most
640 consumers should use ``read()`` to obtain the actual file data.
640 consumers should use ``read()`` to obtain the actual file data.
641 """
641 """
642
642
643 def rawdata(node):
643 def rawdata(node):
644 """Obtain raw data for a node."""
644 """Obtain raw data for a node."""
645
645
646 def read(node):
646 def read(node):
647 """Resolve file fulltext data.
647 """Resolve file fulltext data.
648
648
649 This is similar to ``revision()`` except any metadata in the data
649 This is similar to ``revision()`` except any metadata in the data
650 headers is stripped.
650 headers is stripped.
651 """
651 """
652
652
653 def renamed(node):
653 def renamed(node):
654 """Obtain copy metadata for a node.
654 """Obtain copy metadata for a node.
655
655
656 Returns ``False`` if no copy metadata is stored or a 2-tuple of
656 Returns ``False`` if no copy metadata is stored or a 2-tuple of
657 (path, node) from which this revision was copied.
657 (path, node) from which this revision was copied.
658 """
658 """
659
659
660 def cmp(node, fulltext):
660 def cmp(node, fulltext):
661 """Compare fulltext to another revision.
661 """Compare fulltext to another revision.
662
662
663 Returns True if the fulltext is different from what is stored.
663 Returns True if the fulltext is different from what is stored.
664
664
665 This takes copy metadata into account.
665 This takes copy metadata into account.
666
666
667 TODO better document the copy metadata and censoring logic.
667 TODO better document the copy metadata and censoring logic.
668 """
668 """
669
669
670 def emitrevisions(
670 def emitrevisions(
671 nodes,
671 nodes,
672 nodesorder=None,
672 nodesorder=None,
673 revisiondata=False,
673 revisiondata=False,
674 assumehaveparentrevisions=False,
674 assumehaveparentrevisions=False,
675 deltamode=CG_DELTAMODE_STD,
675 deltamode=CG_DELTAMODE_STD,
676 ):
676 ):
677 """Produce ``irevisiondelta`` for revisions.
677 """Produce ``irevisiondelta`` for revisions.
678
678
679 Given an iterable of nodes, emits objects conforming to the
679 Given an iterable of nodes, emits objects conforming to the
680 ``irevisiondelta`` interface that describe revisions in storage.
680 ``irevisiondelta`` interface that describe revisions in storage.
681
681
682 This method is a generator.
682 This method is a generator.
683
683
684 The input nodes may be unordered. Implementations must ensure that a
684 The input nodes may be unordered. Implementations must ensure that a
685 node's parents are emitted before the node itself. Transitively, this
685 node's parents are emitted before the node itself. Transitively, this
686 means that a node may only be emitted once all its ancestors in
686 means that a node may only be emitted once all its ancestors in
687 ``nodes`` have also been emitted.
687 ``nodes`` have also been emitted.
688
688
689 By default, emits "index" data (the ``node``, ``p1node``, and
689 By default, emits "index" data (the ``node``, ``p1node``, and
690 ``p2node`` attributes). If ``revisiondata`` is set, revision data
690 ``p2node`` attributes). If ``revisiondata`` is set, revision data
691 will also be present on the emitted objects.
691 will also be present on the emitted objects.
692
692
693 With default argument values, implementations can choose to emit
693 With default argument values, implementations can choose to emit
694 either fulltext revision data or a delta. When emitting deltas,
694 either fulltext revision data or a delta. When emitting deltas,
695 implementations must consider whether the delta's base revision
695 implementations must consider whether the delta's base revision
696 fulltext is available to the receiver.
696 fulltext is available to the receiver.
697
697
698 The base revision fulltext is guaranteed to be available if any of
698 The base revision fulltext is guaranteed to be available if any of
699 the following are met:
699 the following are met:
700
700
701 * Its fulltext revision was emitted by this method call.
701 * Its fulltext revision was emitted by this method call.
702 * A delta for that revision was emitted by this method call.
702 * A delta for that revision was emitted by this method call.
703 * ``assumehaveparentrevisions`` is True and the base revision is a
703 * ``assumehaveparentrevisions`` is True and the base revision is a
704 parent of the node.
704 parent of the node.
705
705
706 ``nodesorder`` can be used to control the order that revisions are
706 ``nodesorder`` can be used to control the order that revisions are
707 emitted. By default, revisions can be reordered as long as they are
707 emitted. By default, revisions can be reordered as long as they are
708 in DAG topological order (see above). If the value is ``nodes``,
708 in DAG topological order (see above). If the value is ``nodes``,
709 the iteration order from ``nodes`` should be used. If the value is
709 the iteration order from ``nodes`` should be used. If the value is
710 ``storage``, then the native order from the backing storage layer
710 ``storage``, then the native order from the backing storage layer
711 is used. (Not all storage layers will have strong ordering and behavior
711 is used. (Not all storage layers will have strong ordering and behavior
712 of this mode is storage-dependent.) ``nodes`` ordering can force
712 of this mode is storage-dependent.) ``nodes`` ordering can force
713 revisions to be emitted before their ancestors, so consumers should
713 revisions to be emitted before their ancestors, so consumers should
714 use it with care.
714 use it with care.
715
715
716 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
716 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
717 be set and it is the caller's responsibility to resolve it, if needed.
717 be set and it is the caller's responsibility to resolve it, if needed.
718
718
719 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
719 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
720 all revision data should be emitted as deltas against the revision
720 all revision data should be emitted as deltas against the revision
721 emitted just prior. The initial revision should be a delta against its
721 emitted just prior. The initial revision should be a delta against its
722 1st parent.
722 1st parent.
723 """
723 """
724
724
725
725
726 class ifilemutation(interfaceutil.Interface):
726 class ifilemutation(interfaceutil.Interface):
727 """Storage interface for mutation events of a tracked file."""
727 """Storage interface for mutation events of a tracked file."""
728
728
729 def add(filedata, meta, transaction, linkrev, p1, p2):
729 def add(filedata, meta, transaction, linkrev, p1, p2):
730 """Add a new revision to the store.
730 """Add a new revision to the store.
731
731
732 Takes file data, dictionary of metadata, a transaction, linkrev,
732 Takes file data, dictionary of metadata, a transaction, linkrev,
733 and parent nodes.
733 and parent nodes.
734
734
735 Returns the node that was added.
735 Returns the node that was added.
736
736
737 May no-op if a revision matching the supplied data is already stored.
737 May no-op if a revision matching the supplied data is already stored.
738 """
738 """
739
739
740 def addrevision(
740 def addrevision(
741 revisiondata,
741 revisiondata,
742 transaction,
742 transaction,
743 linkrev,
743 linkrev,
744 p1,
744 p1,
745 p2,
745 p2,
746 node=None,
746 node=None,
747 flags=0,
747 flags=0,
748 cachedelta=None,
748 cachedelta=None,
749 ):
749 ):
750 """Add a new revision to the store and return its number.
750 """Add a new revision to the store and return its number.
751
751
752 This is similar to ``add()`` except it operates at a lower level.
752 This is similar to ``add()`` except it operates at a lower level.
753
753
754 The data passed in already contains a metadata header, if any.
754 The data passed in already contains a metadata header, if any.
755
755
756 ``node`` and ``flags`` can be used to define the expected node and
756 ``node`` and ``flags`` can be used to define the expected node and
757 the flags to use with storage. ``flags`` is a bitwise value composed
757 the flags to use with storage. ``flags`` is a bitwise value composed
758 of the various ``REVISION_FLAG_*`` constants.
758 of the various ``REVISION_FLAG_*`` constants.
759
759
760 ``add()`` is usually called when adding files from e.g. the working
760 ``add()`` is usually called when adding files from e.g. the working
761 directory. ``addrevision()`` is often called by ``add()`` and for
761 directory. ``addrevision()`` is often called by ``add()`` and for
762 scenarios where revision data has already been computed, such as when
762 scenarios where revision data has already been computed, such as when
763 applying raw data from a peer repo.
763 applying raw data from a peer repo.
764 """
764 """
765
765
766 def addgroup(
766 def addgroup(
767 deltas,
767 deltas,
768 linkmapper,
768 linkmapper,
769 transaction,
769 transaction,
770 addrevisioncb=None,
770 addrevisioncb=None,
771 duplicaterevisioncb=None,
771 duplicaterevisioncb=None,
772 maybemissingparents=False,
772 maybemissingparents=False,
773 ):
773 ):
774 """Process a series of deltas for storage.
774 """Process a series of deltas for storage.
775
775
776 ``deltas`` is an iterable of 7-tuples of
776 ``deltas`` is an iterable of 7-tuples of
777 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
777 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
778 to add.
778 to add.
779
779
780 The ``delta`` field contains ``mpatch`` data to apply to a base
780 The ``delta`` field contains ``mpatch`` data to apply to a base
781 revision, identified by ``deltabase``. The base node can be
781 revision, identified by ``deltabase``. The base node can be
782 ``nullid``, in which case the header from the delta can be ignored
782 ``nullid``, in which case the header from the delta can be ignored
783 and the delta used as the fulltext.
783 and the delta used as the fulltext.
784
784
785 ``alwayscache`` instructs the lower layers to cache the content of the
785 ``alwayscache`` instructs the lower layers to cache the content of the
786 newly added revision, even if it needs to be explicitly computed.
786 newly added revision, even if it needs to be explicitly computed.
787 This used to be the default when ``addrevisioncb`` was provided up to
787 This used to be the default when ``addrevisioncb`` was provided up to
788 Mercurial 5.8.
788 Mercurial 5.8.
789
789
790 ``addrevisioncb`` should be called for each new rev as it is committed.
790 ``addrevisioncb`` should be called for each new rev as it is committed.
791 ``duplicaterevisioncb`` should be called for all revs with a
791 ``duplicaterevisioncb`` should be called for all revs with a
792 pre-existing node.
792 pre-existing node.
793
793
794 ``maybemissingparents`` is a bool indicating whether the incoming
794 ``maybemissingparents`` is a bool indicating whether the incoming
795 data may reference parents/ancestor revisions that aren't present.
795 data may reference parents/ancestor revisions that aren't present.
796 This flag is set when receiving data into a "shallow" store that
796 This flag is set when receiving data into a "shallow" store that
797 doesn't hold all history.
797 doesn't hold all history.
798
798
799 Returns a list of nodes that were processed. A node will be in the list
799 Returns a list of nodes that were processed. A node will be in the list
800 even if it existed in the store previously.
800 even if it existed in the store previously.
801 """
801 """
802
802
803 def censorrevision(tr, node, tombstone=b''):
803 def censorrevision(tr, node, tombstone=b''):
804 """Remove the content of a single revision.
804 """Remove the content of a single revision.
805
805
806 The specified ``node`` will have its content purged from storage.
806 The specified ``node`` will have its content purged from storage.
807 Future attempts to access the revision data for this node will
807 Future attempts to access the revision data for this node will
808 result in failure.
808 result in failure.
809
809
810 A ``tombstone`` message can optionally be stored. This message may be
810 A ``tombstone`` message can optionally be stored. This message may be
811 displayed to users when they attempt to access the missing revision
811 displayed to users when they attempt to access the missing revision
812 data.
812 data.
813
813
814 Storage backends may have stored deltas against the previous content
814 Storage backends may have stored deltas against the previous content
815 in this revision. As part of censoring a revision, these storage
815 in this revision. As part of censoring a revision, these storage
816 backends are expected to rewrite any internally stored deltas such
816 backends are expected to rewrite any internally stored deltas such
817 that they no longer reference the deleted content.
817 that they no longer reference the deleted content.
818 """
818 """
819
819
820 def getstrippoint(minlink):
820 def getstrippoint(minlink):
821 """Find the minimum revision that must be stripped to strip a linkrev.
821 """Find the minimum revision that must be stripped to strip a linkrev.
822
822
823 Returns a 2-tuple containing the minimum revision number and a set
823 Returns a 2-tuple containing the minimum revision number and a set
824 of all revisions numbers that would be broken by this strip.
824 of all revisions numbers that would be broken by this strip.
825
825
826 TODO this is highly revlog centric and should be abstracted into
826 TODO this is highly revlog centric and should be abstracted into
827 a higher-level deletion API. ``repair.strip()`` relies on this.
827 a higher-level deletion API. ``repair.strip()`` relies on this.
828 """
828 """
829
829
830 def strip(minlink, transaction):
830 def strip(minlink, transaction):
831 """Remove storage of items starting at a linkrev.
831 """Remove storage of items starting at a linkrev.
832
832
833 This uses ``getstrippoint()`` to determine the first node to remove.
833 This uses ``getstrippoint()`` to determine the first node to remove.
834 Then it effectively truncates storage for all revisions after that.
834 Then it effectively truncates storage for all revisions after that.
835
835
836 TODO this is highly revlog centric and should be abstracted into a
836 TODO this is highly revlog centric and should be abstracted into a
837 higher-level deletion API.
837 higher-level deletion API.
838 """
838 """
839
839
840
840
841 class ifilestorage(ifileindex, ifiledata, ifilemutation):
841 class ifilestorage(ifileindex, ifiledata, ifilemutation):
842 """Complete storage interface for a single tracked file."""
842 """Complete storage interface for a single tracked file."""
843
843
844 def files():
844 def files():
845 """Obtain paths that are backing storage for this file.
845 """Obtain paths that are backing storage for this file.
846
846
847 TODO this is used heavily by verify code and there should probably
847 TODO this is used heavily by verify code and there should probably
848 be a better API for that.
848 be a better API for that.
849 """
849 """
850
850
851 def storageinfo(
851 def storageinfo(
852 exclusivefiles=False,
852 exclusivefiles=False,
853 sharedfiles=False,
853 sharedfiles=False,
854 revisionscount=False,
854 revisionscount=False,
855 trackedsize=False,
855 trackedsize=False,
856 storedsize=False,
856 storedsize=False,
857 ):
857 ):
858 """Obtain information about storage for this file's data.
858 """Obtain information about storage for this file's data.
859
859
860 Returns a dict describing storage for this tracked path. The keys
860 Returns a dict describing storage for this tracked path. The keys
861 in the dict map to arguments of the same. The arguments are bools
861 in the dict map to arguments of the same. The arguments are bools
862 indicating whether to calculate and obtain that data.
862 indicating whether to calculate and obtain that data.
863
863
864 exclusivefiles
864 exclusivefiles
865 Iterable of (vfs, path) describing files that are exclusively
865 Iterable of (vfs, path) describing files that are exclusively
866 used to back storage for this tracked path.
866 used to back storage for this tracked path.
867
867
868 sharedfiles
868 sharedfiles
869 Iterable of (vfs, path) describing files that are used to back
869 Iterable of (vfs, path) describing files that are used to back
870 storage for this tracked path. Those files may also provide storage
870 storage for this tracked path. Those files may also provide storage
871 for other stored entities.
871 for other stored entities.
872
872
873 revisionscount
873 revisionscount
874 Number of revisions available for retrieval.
874 Number of revisions available for retrieval.
875
875
876 trackedsize
876 trackedsize
877 Total size in bytes of all tracked revisions. This is a sum of the
877 Total size in bytes of all tracked revisions. This is a sum of the
878 length of the fulltext of all revisions.
878 length of the fulltext of all revisions.
879
879
880 storedsize
880 storedsize
881 Total size in bytes used to store data for all tracked revisions.
881 Total size in bytes used to store data for all tracked revisions.
882 This is commonly less than ``trackedsize`` due to internal usage
882 This is commonly less than ``trackedsize`` due to internal usage
883 of deltas rather than fulltext revisions.
883 of deltas rather than fulltext revisions.
884
884
885 Not all storage backends may support all queries are have a reasonable
885 Not all storage backends may support all queries are have a reasonable
886 value to use. In that case, the value should be set to ``None`` and
886 value to use. In that case, the value should be set to ``None`` and
887 callers are expected to handle this special value.
887 callers are expected to handle this special value.
888 """
888 """
889
889
890 def verifyintegrity(state):
890 def verifyintegrity(state):
891 """Verifies the integrity of file storage.
891 """Verifies the integrity of file storage.
892
892
893 ``state`` is a dict holding state of the verifier process. It can be
893 ``state`` is a dict holding state of the verifier process. It can be
894 used to communicate data between invocations of multiple storage
894 used to communicate data between invocations of multiple storage
895 primitives.
895 primitives.
896
896
897 If individual revisions cannot have their revision content resolved,
897 If individual revisions cannot have their revision content resolved,
898 the method is expected to set the ``skipread`` key to a set of nodes
898 the method is expected to set the ``skipread`` key to a set of nodes
899 that encountered problems. If set, the method can also add the node(s)
899 that encountered problems. If set, the method can also add the node(s)
900 to ``safe_renamed`` in order to indicate nodes that may perform the
900 to ``safe_renamed`` in order to indicate nodes that may perform the
901 rename checks with currently accessible data.
901 rename checks with currently accessible data.
902
902
903 The method yields objects conforming to the ``iverifyproblem``
903 The method yields objects conforming to the ``iverifyproblem``
904 interface.
904 interface.
905 """
905 """
906
906
907
907
908 class idirs(interfaceutil.Interface):
908 class idirs(interfaceutil.Interface):
909 """Interface representing a collection of directories from paths.
909 """Interface representing a collection of directories from paths.
910
910
911 This interface is essentially a derived data structure representing
911 This interface is essentially a derived data structure representing
912 directories from a collection of paths.
912 directories from a collection of paths.
913 """
913 """
914
914
915 def addpath(path):
915 def addpath(path):
916 """Add a path to the collection.
916 """Add a path to the collection.
917
917
918 All directories in the path will be added to the collection.
918 All directories in the path will be added to the collection.
919 """
919 """
920
920
921 def delpath(path):
921 def delpath(path):
922 """Remove a path from the collection.
922 """Remove a path from the collection.
923
923
924 If the removal was the last path in a particular directory, the
924 If the removal was the last path in a particular directory, the
925 directory is removed from the collection.
925 directory is removed from the collection.
926 """
926 """
927
927
928 def __iter__():
928 def __iter__():
929 """Iterate over the directories in this collection of paths."""
929 """Iterate over the directories in this collection of paths."""
930
930
931 def __contains__(path):
931 def __contains__(path):
932 """Whether a specific directory is in this collection."""
932 """Whether a specific directory is in this collection."""
933
933
934
934
935 class imanifestdict(interfaceutil.Interface):
935 class imanifestdict(interfaceutil.Interface):
936 """Interface representing a manifest data structure.
936 """Interface representing a manifest data structure.
937
937
938 A manifest is effectively a dict mapping paths to entries. Each entry
938 A manifest is effectively a dict mapping paths to entries. Each entry
939 consists of a binary node and extra flags affecting that entry.
939 consists of a binary node and extra flags affecting that entry.
940 """
940 """
941
941
942 def __getitem__(path):
942 def __getitem__(path):
943 """Returns the binary node value for a path in the manifest.
943 """Returns the binary node value for a path in the manifest.
944
944
945 Raises ``KeyError`` if the path does not exist in the manifest.
945 Raises ``KeyError`` if the path does not exist in the manifest.
946
946
947 Equivalent to ``self.find(path)[0]``.
947 Equivalent to ``self.find(path)[0]``.
948 """
948 """
949
949
950 def find(path):
950 def find(path):
951 """Returns the entry for a path in the manifest.
951 """Returns the entry for a path in the manifest.
952
952
953 Returns a 2-tuple of (node, flags).
953 Returns a 2-tuple of (node, flags).
954
954
955 Raises ``KeyError`` if the path does not exist in the manifest.
955 Raises ``KeyError`` if the path does not exist in the manifest.
956 """
956 """
957
957
958 def __len__():
958 def __len__():
959 """Return the number of entries in the manifest."""
959 """Return the number of entries in the manifest."""
960
960
961 def __nonzero__():
961 def __nonzero__():
962 """Returns True if the manifest has entries, False otherwise."""
962 """Returns True if the manifest has entries, False otherwise."""
963
963
964 __bool__ = __nonzero__
964 __bool__ = __nonzero__
965
965
966 def __setitem__(path, node):
966 def __setitem__(path, node):
967 """Define the node value for a path in the manifest.
967 """Define the node value for a path in the manifest.
968
968
969 If the path is already in the manifest, its flags will be copied to
969 If the path is already in the manifest, its flags will be copied to
970 the new entry.
970 the new entry.
971 """
971 """
972
972
973 def __contains__(path):
973 def __contains__(path):
974 """Whether a path exists in the manifest."""
974 """Whether a path exists in the manifest."""
975
975
976 def __delitem__(path):
976 def __delitem__(path):
977 """Remove a path from the manifest.
977 """Remove a path from the manifest.
978
978
979 Raises ``KeyError`` if the path is not in the manifest.
979 Raises ``KeyError`` if the path is not in the manifest.
980 """
980 """
981
981
982 def __iter__():
982 def __iter__():
983 """Iterate over paths in the manifest."""
983 """Iterate over paths in the manifest."""
984
984
985 def iterkeys():
985 def iterkeys():
986 """Iterate over paths in the manifest."""
986 """Iterate over paths in the manifest."""
987
987
988 def keys():
988 def keys():
989 """Obtain a list of paths in the manifest."""
989 """Obtain a list of paths in the manifest."""
990
990
991 def filesnotin(other, match=None):
991 def filesnotin(other, match=None):
992 """Obtain the set of paths in this manifest but not in another.
992 """Obtain the set of paths in this manifest but not in another.
993
993
994 ``match`` is an optional matcher function to be applied to both
994 ``match`` is an optional matcher function to be applied to both
995 manifests.
995 manifests.
996
996
997 Returns a set of paths.
997 Returns a set of paths.
998 """
998 """
999
999
1000 def dirs():
1000 def dirs():
1001 """Returns an object implementing the ``idirs`` interface."""
1001 """Returns an object implementing the ``idirs`` interface."""
1002
1002
1003 def hasdir(dir):
1003 def hasdir(dir):
1004 """Returns a bool indicating if a directory is in this manifest."""
1004 """Returns a bool indicating if a directory is in this manifest."""
1005
1005
1006 def walk(match):
1006 def walk(match):
1007 """Generator of paths in manifest satisfying a matcher.
1007 """Generator of paths in manifest satisfying a matcher.
1008
1008
1009 If the matcher has explicit files listed and they don't exist in
1009 If the matcher has explicit files listed and they don't exist in
1010 the manifest, ``match.bad()`` is called for each missing file.
1010 the manifest, ``match.bad()`` is called for each missing file.
1011 """
1011 """
1012
1012
1013 def diff(other, match=None, clean=False):
1013 def diff(other, match=None, clean=False):
1014 """Find differences between this manifest and another.
1014 """Find differences between this manifest and another.
1015
1015
1016 This manifest is compared to ``other``.
1016 This manifest is compared to ``other``.
1017
1017
1018 If ``match`` is provided, the two manifests are filtered against this
1018 If ``match`` is provided, the two manifests are filtered against this
1019 matcher and only entries satisfying the matcher are compared.
1019 matcher and only entries satisfying the matcher are compared.
1020
1020
1021 If ``clean`` is True, unchanged files are included in the returned
1021 If ``clean`` is True, unchanged files are included in the returned
1022 object.
1022 object.
1023
1023
1024 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1024 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1025 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1025 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1026 represents the node and flags for this manifest and ``(node2, flag2)``
1026 represents the node and flags for this manifest and ``(node2, flag2)``
1027 are the same for the other manifest.
1027 are the same for the other manifest.
1028 """
1028 """
1029
1029
1030 def setflag(path, flag):
1030 def setflag(path, flag):
1031 """Set the flag value for a given path.
1031 """Set the flag value for a given path.
1032
1032
1033 Raises ``KeyError`` if the path is not already in the manifest.
1033 Raises ``KeyError`` if the path is not already in the manifest.
1034 """
1034 """
1035
1035
1036 def get(path, default=None):
1036 def get(path, default=None):
1037 """Obtain the node value for a path or a default value if missing."""
1037 """Obtain the node value for a path or a default value if missing."""
1038
1038
1039 def flags(path):
1039 def flags(path):
1040 """Return the flags value for a path (default: empty bytestring)."""
1040 """Return the flags value for a path (default: empty bytestring)."""
1041
1041
1042 def copy():
1042 def copy():
1043 """Return a copy of this manifest."""
1043 """Return a copy of this manifest."""
1044
1044
1045 def items():
1045 def items():
1046 """Returns an iterable of (path, node) for items in this manifest."""
1046 """Returns an iterable of (path, node) for items in this manifest."""
1047
1047
1048 def iteritems():
1048 def iteritems():
1049 """Identical to items()."""
1049 """Identical to items()."""
1050
1050
1051 def iterentries():
1051 def iterentries():
1052 """Returns an iterable of (path, node, flags) for this manifest.
1052 """Returns an iterable of (path, node, flags) for this manifest.
1053
1053
1054 Similar to ``iteritems()`` except items are a 3-tuple and include
1054 Similar to ``iteritems()`` except items are a 3-tuple and include
1055 flags.
1055 flags.
1056 """
1056 """
1057
1057
1058 def text():
1058 def text():
1059 """Obtain the raw data representation for this manifest.
1059 """Obtain the raw data representation for this manifest.
1060
1060
1061 Result is used to create a manifest revision.
1061 Result is used to create a manifest revision.
1062 """
1062 """
1063
1063
1064 def fastdelta(base, changes):
1064 def fastdelta(base, changes):
1065 """Obtain a delta between this manifest and another given changes.
1065 """Obtain a delta between this manifest and another given changes.
1066
1066
1067 ``base`` in the raw data representation for another manifest.
1067 ``base`` in the raw data representation for another manifest.
1068
1068
1069 ``changes`` is an iterable of ``(path, to_delete)``.
1069 ``changes`` is an iterable of ``(path, to_delete)``.
1070
1070
1071 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1071 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1072 delta between ``base`` and this manifest.
1072 delta between ``base`` and this manifest.
1073
1073
1074 If this manifest implementation can't support ``fastdelta()``,
1074 If this manifest implementation can't support ``fastdelta()``,
1075 raise ``mercurial.manifest.FastdeltaUnavailable``.
1075 raise ``mercurial.manifest.FastdeltaUnavailable``.
1076 """
1076 """
1077
1077
1078
1078
1079 class imanifestrevisionbase(interfaceutil.Interface):
1079 class imanifestrevisionbase(interfaceutil.Interface):
1080 """Base interface representing a single revision of a manifest.
1080 """Base interface representing a single revision of a manifest.
1081
1081
1082 Should not be used as a primary interface: should always be inherited
1082 Should not be used as a primary interface: should always be inherited
1083 as part of a larger interface.
1083 as part of a larger interface.
1084 """
1084 """
1085
1085
1086 def copy():
1086 def copy():
1087 """Obtain a copy of this manifest instance.
1087 """Obtain a copy of this manifest instance.
1088
1088
1089 Returns an object conforming to the ``imanifestrevisionwritable``
1089 Returns an object conforming to the ``imanifestrevisionwritable``
1090 interface. The instance will be associated with the same
1090 interface. The instance will be associated with the same
1091 ``imanifestlog`` collection as this instance.
1091 ``imanifestlog`` collection as this instance.
1092 """
1092 """
1093
1093
1094 def read():
1094 def read():
1095 """Obtain the parsed manifest data structure.
1095 """Obtain the parsed manifest data structure.
1096
1096
1097 The returned object conforms to the ``imanifestdict`` interface.
1097 The returned object conforms to the ``imanifestdict`` interface.
1098 """
1098 """
1099
1099
1100
1100
1101 class imanifestrevisionstored(imanifestrevisionbase):
1101 class imanifestrevisionstored(imanifestrevisionbase):
1102 """Interface representing a manifest revision committed to storage."""
1102 """Interface representing a manifest revision committed to storage."""
1103
1103
1104 def node():
1104 def node():
1105 """The binary node for this manifest."""
1105 """The binary node for this manifest."""
1106
1106
1107 parents = interfaceutil.Attribute(
1107 parents = interfaceutil.Attribute(
1108 """List of binary nodes that are parents for this manifest revision."""
1108 """List of binary nodes that are parents for this manifest revision."""
1109 )
1109 )
1110
1110
1111 def readdelta(shallow=False):
1111 def readdelta(shallow=False):
1112 """Obtain the manifest data structure representing changes from parent.
1112 """Obtain the manifest data structure representing changes from parent.
1113
1113
1114 This manifest is compared to its 1st parent. A new manifest representing
1114 This manifest is compared to its 1st parent. A new manifest representing
1115 those differences is constructed.
1115 those differences is constructed.
1116
1116
1117 The returned object conforms to the ``imanifestdict`` interface.
1117 The returned object conforms to the ``imanifestdict`` interface.
1118 """
1118 """
1119
1119
1120 def readfast(shallow=False):
1120 def readfast(shallow=False):
1121 """Calls either ``read()`` or ``readdelta()``.
1121 """Calls either ``read()`` or ``readdelta()``.
1122
1122
1123 The faster of the two options is called.
1123 The faster of the two options is called.
1124 """
1124 """
1125
1125
1126 def find(key):
1126 def find(key):
1127 """Calls self.read().find(key)``.
1127 """Calls self.read().find(key)``.
1128
1128
1129 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1129 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1130 """
1130 """
1131
1131
1132
1132
1133 class imanifestrevisionwritable(imanifestrevisionbase):
1133 class imanifestrevisionwritable(imanifestrevisionbase):
1134 """Interface representing a manifest revision that can be committed."""
1134 """Interface representing a manifest revision that can be committed."""
1135
1135
1136 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1136 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1137 """Add this revision to storage.
1137 """Add this revision to storage.
1138
1138
1139 Takes a transaction object, the changeset revision number it will
1139 Takes a transaction object, the changeset revision number it will
1140 be associated with, its parent nodes, and lists of added and
1140 be associated with, its parent nodes, and lists of added and
1141 removed paths.
1141 removed paths.
1142
1142
1143 If match is provided, storage can choose not to inspect or write out
1143 If match is provided, storage can choose not to inspect or write out
1144 items that do not match. Storage is still required to be able to provide
1144 items that do not match. Storage is still required to be able to provide
1145 the full manifest in the future for any directories written (these
1145 the full manifest in the future for any directories written (these
1146 manifests should not be "narrowed on disk").
1146 manifests should not be "narrowed on disk").
1147
1147
1148 Returns the binary node of the created revision.
1148 Returns the binary node of the created revision.
1149 """
1149 """
1150
1150
1151
1151
1152 class imanifeststorage(interfaceutil.Interface):
1152 class imanifeststorage(interfaceutil.Interface):
1153 """Storage interface for manifest data."""
1153 """Storage interface for manifest data."""
1154
1154
1155 nodeconstants = interfaceutil.Attribute(
1155 nodeconstants = interfaceutil.Attribute(
1156 """nodeconstants used by the current repository."""
1156 """nodeconstants used by the current repository."""
1157 )
1157 )
1158
1158
1159 tree = interfaceutil.Attribute(
1159 tree = interfaceutil.Attribute(
1160 """The path to the directory this manifest tracks.
1160 """The path to the directory this manifest tracks.
1161
1161
1162 The empty bytestring represents the root manifest.
1162 The empty bytestring represents the root manifest.
1163 """
1163 """
1164 )
1164 )
1165
1165
1166 index = interfaceutil.Attribute(
1166 index = interfaceutil.Attribute(
1167 """An ``ifilerevisionssequence`` instance."""
1167 """An ``ifilerevisionssequence`` instance."""
1168 )
1168 )
1169
1169
1170 indexfile = interfaceutil.Attribute(
1171 """Path of revlog index file.
1172
1173 TODO this is revlog specific and should not be exposed.
1174 """
1175 )
1176
1177 opener = interfaceutil.Attribute(
1170 opener = interfaceutil.Attribute(
1178 """VFS opener to use to access underlying files used for storage.
1171 """VFS opener to use to access underlying files used for storage.
1179
1172
1180 TODO this is revlog specific and should not be exposed.
1173 TODO this is revlog specific and should not be exposed.
1181 """
1174 """
1182 )
1175 )
1183
1176
1184 _generaldelta = interfaceutil.Attribute(
1177 _generaldelta = interfaceutil.Attribute(
1185 """Whether generaldelta storage is being used.
1178 """Whether generaldelta storage is being used.
1186
1179
1187 TODO this is revlog specific and should not be exposed.
1180 TODO this is revlog specific and should not be exposed.
1188 """
1181 """
1189 )
1182 )
1190
1183
1191 fulltextcache = interfaceutil.Attribute(
1184 fulltextcache = interfaceutil.Attribute(
1192 """Dict with cache of fulltexts.
1185 """Dict with cache of fulltexts.
1193
1186
1194 TODO this doesn't feel appropriate for the storage interface.
1187 TODO this doesn't feel appropriate for the storage interface.
1195 """
1188 """
1196 )
1189 )
1197
1190
1198 def __len__():
1191 def __len__():
1199 """Obtain the number of revisions stored for this manifest."""
1192 """Obtain the number of revisions stored for this manifest."""
1200
1193
1201 def __iter__():
1194 def __iter__():
1202 """Iterate over revision numbers for this manifest."""
1195 """Iterate over revision numbers for this manifest."""
1203
1196
1204 def rev(node):
1197 def rev(node):
1205 """Obtain the revision number given a binary node.
1198 """Obtain the revision number given a binary node.
1206
1199
1207 Raises ``error.LookupError`` if the node is not known.
1200 Raises ``error.LookupError`` if the node is not known.
1208 """
1201 """
1209
1202
1210 def node(rev):
1203 def node(rev):
1211 """Obtain the node value given a revision number.
1204 """Obtain the node value given a revision number.
1212
1205
1213 Raises ``error.LookupError`` if the revision is not known.
1206 Raises ``error.LookupError`` if the revision is not known.
1214 """
1207 """
1215
1208
1216 def lookup(value):
1209 def lookup(value):
1217 """Attempt to resolve a value to a node.
1210 """Attempt to resolve a value to a node.
1218
1211
1219 Value can be a binary node, hex node, revision number, or a bytes
1212 Value can be a binary node, hex node, revision number, or a bytes
1220 that can be converted to an integer.
1213 that can be converted to an integer.
1221
1214
1222 Raises ``error.LookupError`` if a ndoe could not be resolved.
1215 Raises ``error.LookupError`` if a ndoe could not be resolved.
1223 """
1216 """
1224
1217
1225 def parents(node):
1218 def parents(node):
1226 """Returns a 2-tuple of parent nodes for a node.
1219 """Returns a 2-tuple of parent nodes for a node.
1227
1220
1228 Values will be ``nullid`` if the parent is empty.
1221 Values will be ``nullid`` if the parent is empty.
1229 """
1222 """
1230
1223
1231 def parentrevs(rev):
1224 def parentrevs(rev):
1232 """Like parents() but operates on revision numbers."""
1225 """Like parents() but operates on revision numbers."""
1233
1226
1234 def linkrev(rev):
1227 def linkrev(rev):
1235 """Obtain the changeset revision number a revision is linked to."""
1228 """Obtain the changeset revision number a revision is linked to."""
1236
1229
1237 def revision(node, _df=None, raw=False):
1230 def revision(node, _df=None, raw=False):
1238 """Obtain fulltext data for a node."""
1231 """Obtain fulltext data for a node."""
1239
1232
1240 def rawdata(node, _df=None):
1233 def rawdata(node, _df=None):
1241 """Obtain raw data for a node."""
1234 """Obtain raw data for a node."""
1242
1235
1243 def revdiff(rev1, rev2):
1236 def revdiff(rev1, rev2):
1244 """Obtain a delta between two revision numbers.
1237 """Obtain a delta between two revision numbers.
1245
1238
1246 The returned data is the result of ``bdiff.bdiff()`` on the raw
1239 The returned data is the result of ``bdiff.bdiff()`` on the raw
1247 revision data.
1240 revision data.
1248 """
1241 """
1249
1242
1250 def cmp(node, fulltext):
1243 def cmp(node, fulltext):
1251 """Compare fulltext to another revision.
1244 """Compare fulltext to another revision.
1252
1245
1253 Returns True if the fulltext is different from what is stored.
1246 Returns True if the fulltext is different from what is stored.
1254 """
1247 """
1255
1248
1256 def emitrevisions(
1249 def emitrevisions(
1257 nodes,
1250 nodes,
1258 nodesorder=None,
1251 nodesorder=None,
1259 revisiondata=False,
1252 revisiondata=False,
1260 assumehaveparentrevisions=False,
1253 assumehaveparentrevisions=False,
1261 ):
1254 ):
1262 """Produce ``irevisiondelta`` describing revisions.
1255 """Produce ``irevisiondelta`` describing revisions.
1263
1256
1264 See the documentation for ``ifiledata`` for more.
1257 See the documentation for ``ifiledata`` for more.
1265 """
1258 """
1266
1259
1267 def addgroup(
1260 def addgroup(
1268 deltas,
1261 deltas,
1269 linkmapper,
1262 linkmapper,
1270 transaction,
1263 transaction,
1271 addrevisioncb=None,
1264 addrevisioncb=None,
1272 duplicaterevisioncb=None,
1265 duplicaterevisioncb=None,
1273 ):
1266 ):
1274 """Process a series of deltas for storage.
1267 """Process a series of deltas for storage.
1275
1268
1276 See the documentation in ``ifilemutation`` for more.
1269 See the documentation in ``ifilemutation`` for more.
1277 """
1270 """
1278
1271
1279 def rawsize(rev):
1272 def rawsize(rev):
1280 """Obtain the size of tracked data.
1273 """Obtain the size of tracked data.
1281
1274
1282 Is equivalent to ``len(m.rawdata(node))``.
1275 Is equivalent to ``len(m.rawdata(node))``.
1283
1276
1284 TODO this method is only used by upgrade code and may be removed.
1277 TODO this method is only used by upgrade code and may be removed.
1285 """
1278 """
1286
1279
1287 def getstrippoint(minlink):
1280 def getstrippoint(minlink):
1288 """Find minimum revision that must be stripped to strip a linkrev.
1281 """Find minimum revision that must be stripped to strip a linkrev.
1289
1282
1290 See the documentation in ``ifilemutation`` for more.
1283 See the documentation in ``ifilemutation`` for more.
1291 """
1284 """
1292
1285
1293 def strip(minlink, transaction):
1286 def strip(minlink, transaction):
1294 """Remove storage of items starting at a linkrev.
1287 """Remove storage of items starting at a linkrev.
1295
1288
1296 See the documentation in ``ifilemutation`` for more.
1289 See the documentation in ``ifilemutation`` for more.
1297 """
1290 """
1298
1291
1299 def checksize():
1292 def checksize():
1300 """Obtain the expected sizes of backing files.
1293 """Obtain the expected sizes of backing files.
1301
1294
1302 TODO this is used by verify and it should not be part of the interface.
1295 TODO this is used by verify and it should not be part of the interface.
1303 """
1296 """
1304
1297
1305 def files():
1298 def files():
1306 """Obtain paths that are backing storage for this manifest.
1299 """Obtain paths that are backing storage for this manifest.
1307
1300
1308 TODO this is used by verify and there should probably be a better API
1301 TODO this is used by verify and there should probably be a better API
1309 for this functionality.
1302 for this functionality.
1310 """
1303 """
1311
1304
1312 def deltaparent(rev):
1305 def deltaparent(rev):
1313 """Obtain the revision that a revision is delta'd against.
1306 """Obtain the revision that a revision is delta'd against.
1314
1307
1315 TODO delta encoding is an implementation detail of storage and should
1308 TODO delta encoding is an implementation detail of storage and should
1316 not be exposed to the storage interface.
1309 not be exposed to the storage interface.
1317 """
1310 """
1318
1311
1319 def clone(tr, dest, **kwargs):
1312 def clone(tr, dest, **kwargs):
1320 """Clone this instance to another."""
1313 """Clone this instance to another."""
1321
1314
1322 def clearcaches(clear_persisted_data=False):
1315 def clearcaches(clear_persisted_data=False):
1323 """Clear any caches associated with this instance."""
1316 """Clear any caches associated with this instance."""
1324
1317
1325 def dirlog(d):
1318 def dirlog(d):
1326 """Obtain a manifest storage instance for a tree."""
1319 """Obtain a manifest storage instance for a tree."""
1327
1320
1328 def add(
1321 def add(
1329 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1322 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1330 ):
1323 ):
1331 """Add a revision to storage.
1324 """Add a revision to storage.
1332
1325
1333 ``m`` is an object conforming to ``imanifestdict``.
1326 ``m`` is an object conforming to ``imanifestdict``.
1334
1327
1335 ``link`` is the linkrev revision number.
1328 ``link`` is the linkrev revision number.
1336
1329
1337 ``p1`` and ``p2`` are the parent revision numbers.
1330 ``p1`` and ``p2`` are the parent revision numbers.
1338
1331
1339 ``added`` and ``removed`` are iterables of added and removed paths,
1332 ``added`` and ``removed`` are iterables of added and removed paths,
1340 respectively.
1333 respectively.
1341
1334
1342 ``readtree`` is a function that can be used to read the child tree(s)
1335 ``readtree`` is a function that can be used to read the child tree(s)
1343 when recursively writing the full tree structure when using
1336 when recursively writing the full tree structure when using
1344 treemanifets.
1337 treemanifets.
1345
1338
1346 ``match`` is a matcher that can be used to hint to storage that not all
1339 ``match`` is a matcher that can be used to hint to storage that not all
1347 paths must be inspected; this is an optimization and can be safely
1340 paths must be inspected; this is an optimization and can be safely
1348 ignored. Note that the storage must still be able to reproduce a full
1341 ignored. Note that the storage must still be able to reproduce a full
1349 manifest including files that did not match.
1342 manifest including files that did not match.
1350 """
1343 """
1351
1344
1352 def storageinfo(
1345 def storageinfo(
1353 exclusivefiles=False,
1346 exclusivefiles=False,
1354 sharedfiles=False,
1347 sharedfiles=False,
1355 revisionscount=False,
1348 revisionscount=False,
1356 trackedsize=False,
1349 trackedsize=False,
1357 storedsize=False,
1350 storedsize=False,
1358 ):
1351 ):
1359 """Obtain information about storage for this manifest's data.
1352 """Obtain information about storage for this manifest's data.
1360
1353
1361 See ``ifilestorage.storageinfo()`` for a description of this method.
1354 See ``ifilestorage.storageinfo()`` for a description of this method.
1362 This one behaves the same way, except for manifest data.
1355 This one behaves the same way, except for manifest data.
1363 """
1356 """
1364
1357
1365
1358
1366 class imanifestlog(interfaceutil.Interface):
1359 class imanifestlog(interfaceutil.Interface):
1367 """Interface representing a collection of manifest snapshots.
1360 """Interface representing a collection of manifest snapshots.
1368
1361
1369 Represents the root manifest in a repository.
1362 Represents the root manifest in a repository.
1370
1363
1371 Also serves as a means to access nested tree manifests and to cache
1364 Also serves as a means to access nested tree manifests and to cache
1372 tree manifests.
1365 tree manifests.
1373 """
1366 """
1374
1367
1375 nodeconstants = interfaceutil.Attribute(
1368 nodeconstants = interfaceutil.Attribute(
1376 """nodeconstants used by the current repository."""
1369 """nodeconstants used by the current repository."""
1377 )
1370 )
1378
1371
1379 def __getitem__(node):
1372 def __getitem__(node):
1380 """Obtain a manifest instance for a given binary node.
1373 """Obtain a manifest instance for a given binary node.
1381
1374
1382 Equivalent to calling ``self.get('', node)``.
1375 Equivalent to calling ``self.get('', node)``.
1383
1376
1384 The returned object conforms to the ``imanifestrevisionstored``
1377 The returned object conforms to the ``imanifestrevisionstored``
1385 interface.
1378 interface.
1386 """
1379 """
1387
1380
1388 def get(tree, node, verify=True):
1381 def get(tree, node, verify=True):
1389 """Retrieve the manifest instance for a given directory and binary node.
1382 """Retrieve the manifest instance for a given directory and binary node.
1390
1383
1391 ``node`` always refers to the node of the root manifest (which will be
1384 ``node`` always refers to the node of the root manifest (which will be
1392 the only manifest if flat manifests are being used).
1385 the only manifest if flat manifests are being used).
1393
1386
1394 If ``tree`` is the empty string, the root manifest is returned.
1387 If ``tree`` is the empty string, the root manifest is returned.
1395 Otherwise the manifest for the specified directory will be returned
1388 Otherwise the manifest for the specified directory will be returned
1396 (requires tree manifests).
1389 (requires tree manifests).
1397
1390
1398 If ``verify`` is True, ``LookupError`` is raised if the node is not
1391 If ``verify`` is True, ``LookupError`` is raised if the node is not
1399 known.
1392 known.
1400
1393
1401 The returned object conforms to the ``imanifestrevisionstored``
1394 The returned object conforms to the ``imanifestrevisionstored``
1402 interface.
1395 interface.
1403 """
1396 """
1404
1397
1405 def getstorage(tree):
1398 def getstorage(tree):
1406 """Retrieve an interface to storage for a particular tree.
1399 """Retrieve an interface to storage for a particular tree.
1407
1400
1408 If ``tree`` is the empty bytestring, storage for the root manifest will
1401 If ``tree`` is the empty bytestring, storage for the root manifest will
1409 be returned. Otherwise storage for a tree manifest is returned.
1402 be returned. Otherwise storage for a tree manifest is returned.
1410
1403
1411 TODO formalize interface for returned object.
1404 TODO formalize interface for returned object.
1412 """
1405 """
1413
1406
1414 def clearcaches():
1407 def clearcaches():
1415 """Clear caches associated with this collection."""
1408 """Clear caches associated with this collection."""
1416
1409
1417 def rev(node):
1410 def rev(node):
1418 """Obtain the revision number for a binary node.
1411 """Obtain the revision number for a binary node.
1419
1412
1420 Raises ``error.LookupError`` if the node is not known.
1413 Raises ``error.LookupError`` if the node is not known.
1421 """
1414 """
1422
1415
1423 def update_caches(transaction):
1416 def update_caches(transaction):
1424 """update whatever cache are relevant for the used storage."""
1417 """update whatever cache are relevant for the used storage."""
1425
1418
1426
1419
1427 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1420 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1428 """Local repository sub-interface providing access to tracked file storage.
1421 """Local repository sub-interface providing access to tracked file storage.
1429
1422
1430 This interface defines how a repository accesses storage for a single
1423 This interface defines how a repository accesses storage for a single
1431 tracked file path.
1424 tracked file path.
1432 """
1425 """
1433
1426
1434 def file(f):
1427 def file(f):
1435 """Obtain a filelog for a tracked path.
1428 """Obtain a filelog for a tracked path.
1436
1429
1437 The returned type conforms to the ``ifilestorage`` interface.
1430 The returned type conforms to the ``ifilestorage`` interface.
1438 """
1431 """
1439
1432
1440
1433
1441 class ilocalrepositorymain(interfaceutil.Interface):
1434 class ilocalrepositorymain(interfaceutil.Interface):
1442 """Main interface for local repositories.
1435 """Main interface for local repositories.
1443
1436
1444 This currently captures the reality of things - not how things should be.
1437 This currently captures the reality of things - not how things should be.
1445 """
1438 """
1446
1439
1447 nodeconstants = interfaceutil.Attribute(
1440 nodeconstants = interfaceutil.Attribute(
1448 """Constant nodes matching the hash function used by the repository."""
1441 """Constant nodes matching the hash function used by the repository."""
1449 )
1442 )
1450 nullid = interfaceutil.Attribute(
1443 nullid = interfaceutil.Attribute(
1451 """null revision for the hash function used by the repository."""
1444 """null revision for the hash function used by the repository."""
1452 )
1445 )
1453
1446
1454 supportedformats = interfaceutil.Attribute(
1447 supportedformats = interfaceutil.Attribute(
1455 """Set of requirements that apply to stream clone.
1448 """Set of requirements that apply to stream clone.
1456
1449
1457 This is actually a class attribute and is shared among all instances.
1450 This is actually a class attribute and is shared among all instances.
1458 """
1451 """
1459 )
1452 )
1460
1453
1461 supported = interfaceutil.Attribute(
1454 supported = interfaceutil.Attribute(
1462 """Set of requirements that this repo is capable of opening."""
1455 """Set of requirements that this repo is capable of opening."""
1463 )
1456 )
1464
1457
1465 requirements = interfaceutil.Attribute(
1458 requirements = interfaceutil.Attribute(
1466 """Set of requirements this repo uses."""
1459 """Set of requirements this repo uses."""
1467 )
1460 )
1468
1461
1469 features = interfaceutil.Attribute(
1462 features = interfaceutil.Attribute(
1470 """Set of "features" this repository supports.
1463 """Set of "features" this repository supports.
1471
1464
1472 A "feature" is a loosely-defined term. It can refer to a feature
1465 A "feature" is a loosely-defined term. It can refer to a feature
1473 in the classical sense or can describe an implementation detail
1466 in the classical sense or can describe an implementation detail
1474 of the repository. For example, a ``readonly`` feature may denote
1467 of the repository. For example, a ``readonly`` feature may denote
1475 the repository as read-only. Or a ``revlogfilestore`` feature may
1468 the repository as read-only. Or a ``revlogfilestore`` feature may
1476 denote that the repository is using revlogs for file storage.
1469 denote that the repository is using revlogs for file storage.
1477
1470
1478 The intent of features is to provide a machine-queryable mechanism
1471 The intent of features is to provide a machine-queryable mechanism
1479 for repo consumers to test for various repository characteristics.
1472 for repo consumers to test for various repository characteristics.
1480
1473
1481 Features are similar to ``requirements``. The main difference is that
1474 Features are similar to ``requirements``. The main difference is that
1482 requirements are stored on-disk and represent requirements to open the
1475 requirements are stored on-disk and represent requirements to open the
1483 repository. Features are more run-time capabilities of the repository
1476 repository. Features are more run-time capabilities of the repository
1484 and more granular capabilities (which may be derived from requirements).
1477 and more granular capabilities (which may be derived from requirements).
1485 """
1478 """
1486 )
1479 )
1487
1480
1488 filtername = interfaceutil.Attribute(
1481 filtername = interfaceutil.Attribute(
1489 """Name of the repoview that is active on this repo."""
1482 """Name of the repoview that is active on this repo."""
1490 )
1483 )
1491
1484
1492 wvfs = interfaceutil.Attribute(
1485 wvfs = interfaceutil.Attribute(
1493 """VFS used to access the working directory."""
1486 """VFS used to access the working directory."""
1494 )
1487 )
1495
1488
1496 vfs = interfaceutil.Attribute(
1489 vfs = interfaceutil.Attribute(
1497 """VFS rooted at the .hg directory.
1490 """VFS rooted at the .hg directory.
1498
1491
1499 Used to access repository data not in the store.
1492 Used to access repository data not in the store.
1500 """
1493 """
1501 )
1494 )
1502
1495
1503 svfs = interfaceutil.Attribute(
1496 svfs = interfaceutil.Attribute(
1504 """VFS rooted at the store.
1497 """VFS rooted at the store.
1505
1498
1506 Used to access repository data in the store. Typically .hg/store.
1499 Used to access repository data in the store. Typically .hg/store.
1507 But can point elsewhere if the store is shared.
1500 But can point elsewhere if the store is shared.
1508 """
1501 """
1509 )
1502 )
1510
1503
1511 root = interfaceutil.Attribute(
1504 root = interfaceutil.Attribute(
1512 """Path to the root of the working directory."""
1505 """Path to the root of the working directory."""
1513 )
1506 )
1514
1507
1515 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1508 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1516
1509
1517 origroot = interfaceutil.Attribute(
1510 origroot = interfaceutil.Attribute(
1518 """The filesystem path that was used to construct the repo."""
1511 """The filesystem path that was used to construct the repo."""
1519 )
1512 )
1520
1513
1521 auditor = interfaceutil.Attribute(
1514 auditor = interfaceutil.Attribute(
1522 """A pathauditor for the working directory.
1515 """A pathauditor for the working directory.
1523
1516
1524 This checks if a path refers to a nested repository.
1517 This checks if a path refers to a nested repository.
1525
1518
1526 Operates on the filesystem.
1519 Operates on the filesystem.
1527 """
1520 """
1528 )
1521 )
1529
1522
1530 nofsauditor = interfaceutil.Attribute(
1523 nofsauditor = interfaceutil.Attribute(
1531 """A pathauditor for the working directory.
1524 """A pathauditor for the working directory.
1532
1525
1533 This is like ``auditor`` except it doesn't do filesystem checks.
1526 This is like ``auditor`` except it doesn't do filesystem checks.
1534 """
1527 """
1535 )
1528 )
1536
1529
1537 baseui = interfaceutil.Attribute(
1530 baseui = interfaceutil.Attribute(
1538 """Original ui instance passed into constructor."""
1531 """Original ui instance passed into constructor."""
1539 )
1532 )
1540
1533
1541 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1534 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1542
1535
1543 sharedpath = interfaceutil.Attribute(
1536 sharedpath = interfaceutil.Attribute(
1544 """Path to the .hg directory of the repo this repo was shared from."""
1537 """Path to the .hg directory of the repo this repo was shared from."""
1545 )
1538 )
1546
1539
1547 store = interfaceutil.Attribute("""A store instance.""")
1540 store = interfaceutil.Attribute("""A store instance.""")
1548
1541
1549 spath = interfaceutil.Attribute("""Path to the store.""")
1542 spath = interfaceutil.Attribute("""Path to the store.""")
1550
1543
1551 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1544 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1552
1545
1553 cachevfs = interfaceutil.Attribute(
1546 cachevfs = interfaceutil.Attribute(
1554 """A VFS used to access the cache directory.
1547 """A VFS used to access the cache directory.
1555
1548
1556 Typically .hg/cache.
1549 Typically .hg/cache.
1557 """
1550 """
1558 )
1551 )
1559
1552
1560 wcachevfs = interfaceutil.Attribute(
1553 wcachevfs = interfaceutil.Attribute(
1561 """A VFS used to access the cache directory dedicated to working copy
1554 """A VFS used to access the cache directory dedicated to working copy
1562
1555
1563 Typically .hg/wcache.
1556 Typically .hg/wcache.
1564 """
1557 """
1565 )
1558 )
1566
1559
1567 filteredrevcache = interfaceutil.Attribute(
1560 filteredrevcache = interfaceutil.Attribute(
1568 """Holds sets of revisions to be filtered."""
1561 """Holds sets of revisions to be filtered."""
1569 )
1562 )
1570
1563
1571 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1564 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1572
1565
1573 filecopiesmode = interfaceutil.Attribute(
1566 filecopiesmode = interfaceutil.Attribute(
1574 """The way files copies should be dealt with in this repo."""
1567 """The way files copies should be dealt with in this repo."""
1575 )
1568 )
1576
1569
1577 def close():
1570 def close():
1578 """Close the handle on this repository."""
1571 """Close the handle on this repository."""
1579
1572
1580 def peer():
1573 def peer():
1581 """Obtain an object conforming to the ``peer`` interface."""
1574 """Obtain an object conforming to the ``peer`` interface."""
1582
1575
1583 def unfiltered():
1576 def unfiltered():
1584 """Obtain an unfiltered/raw view of this repo."""
1577 """Obtain an unfiltered/raw view of this repo."""
1585
1578
1586 def filtered(name, visibilityexceptions=None):
1579 def filtered(name, visibilityexceptions=None):
1587 """Obtain a named view of this repository."""
1580 """Obtain a named view of this repository."""
1588
1581
1589 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1582 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1590
1583
1591 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1584 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1592
1585
1593 manifestlog = interfaceutil.Attribute(
1586 manifestlog = interfaceutil.Attribute(
1594 """An instance conforming to the ``imanifestlog`` interface.
1587 """An instance conforming to the ``imanifestlog`` interface.
1595
1588
1596 Provides access to manifests for the repository.
1589 Provides access to manifests for the repository.
1597 """
1590 """
1598 )
1591 )
1599
1592
1600 dirstate = interfaceutil.Attribute("""Working directory state.""")
1593 dirstate = interfaceutil.Attribute("""Working directory state.""")
1601
1594
1602 narrowpats = interfaceutil.Attribute(
1595 narrowpats = interfaceutil.Attribute(
1603 """Matcher patterns for this repository's narrowspec."""
1596 """Matcher patterns for this repository's narrowspec."""
1604 )
1597 )
1605
1598
1606 def narrowmatch(match=None, includeexact=False):
1599 def narrowmatch(match=None, includeexact=False):
1607 """Obtain a matcher for the narrowspec."""
1600 """Obtain a matcher for the narrowspec."""
1608
1601
1609 def setnarrowpats(newincludes, newexcludes):
1602 def setnarrowpats(newincludes, newexcludes):
1610 """Define the narrowspec for this repository."""
1603 """Define the narrowspec for this repository."""
1611
1604
1612 def __getitem__(changeid):
1605 def __getitem__(changeid):
1613 """Try to resolve a changectx."""
1606 """Try to resolve a changectx."""
1614
1607
1615 def __contains__(changeid):
1608 def __contains__(changeid):
1616 """Whether a changeset exists."""
1609 """Whether a changeset exists."""
1617
1610
1618 def __nonzero__():
1611 def __nonzero__():
1619 """Always returns True."""
1612 """Always returns True."""
1620 return True
1613 return True
1621
1614
1622 __bool__ = __nonzero__
1615 __bool__ = __nonzero__
1623
1616
1624 def __len__():
1617 def __len__():
1625 """Returns the number of changesets in the repo."""
1618 """Returns the number of changesets in the repo."""
1626
1619
1627 def __iter__():
1620 def __iter__():
1628 """Iterate over revisions in the changelog."""
1621 """Iterate over revisions in the changelog."""
1629
1622
1630 def revs(expr, *args):
1623 def revs(expr, *args):
1631 """Evaluate a revset.
1624 """Evaluate a revset.
1632
1625
1633 Emits revisions.
1626 Emits revisions.
1634 """
1627 """
1635
1628
1636 def set(expr, *args):
1629 def set(expr, *args):
1637 """Evaluate a revset.
1630 """Evaluate a revset.
1638
1631
1639 Emits changectx instances.
1632 Emits changectx instances.
1640 """
1633 """
1641
1634
1642 def anyrevs(specs, user=False, localalias=None):
1635 def anyrevs(specs, user=False, localalias=None):
1643 """Find revisions matching one of the given revsets."""
1636 """Find revisions matching one of the given revsets."""
1644
1637
1645 def url():
1638 def url():
1646 """Returns a string representing the location of this repo."""
1639 """Returns a string representing the location of this repo."""
1647
1640
1648 def hook(name, throw=False, **args):
1641 def hook(name, throw=False, **args):
1649 """Call a hook."""
1642 """Call a hook."""
1650
1643
1651 def tags():
1644 def tags():
1652 """Return a mapping of tag to node."""
1645 """Return a mapping of tag to node."""
1653
1646
1654 def tagtype(tagname):
1647 def tagtype(tagname):
1655 """Return the type of a given tag."""
1648 """Return the type of a given tag."""
1656
1649
1657 def tagslist():
1650 def tagslist():
1658 """Return a list of tags ordered by revision."""
1651 """Return a list of tags ordered by revision."""
1659
1652
1660 def nodetags(node):
1653 def nodetags(node):
1661 """Return the tags associated with a node."""
1654 """Return the tags associated with a node."""
1662
1655
1663 def nodebookmarks(node):
1656 def nodebookmarks(node):
1664 """Return the list of bookmarks pointing to the specified node."""
1657 """Return the list of bookmarks pointing to the specified node."""
1665
1658
1666 def branchmap():
1659 def branchmap():
1667 """Return a mapping of branch to heads in that branch."""
1660 """Return a mapping of branch to heads in that branch."""
1668
1661
1669 def revbranchcache():
1662 def revbranchcache():
1670 pass
1663 pass
1671
1664
1672 def register_changeset(rev, changelogrevision):
1665 def register_changeset(rev, changelogrevision):
1673 """Extension point for caches for new nodes.
1666 """Extension point for caches for new nodes.
1674
1667
1675 Multiple consumers are expected to need parts of the changelogrevision,
1668 Multiple consumers are expected to need parts of the changelogrevision,
1676 so it is provided as optimization to avoid duplicate lookups. A simple
1669 so it is provided as optimization to avoid duplicate lookups. A simple
1677 cache would be fragile when other revisions are accessed, too."""
1670 cache would be fragile when other revisions are accessed, too."""
1678 pass
1671 pass
1679
1672
1680 def branchtip(branchtip, ignoremissing=False):
1673 def branchtip(branchtip, ignoremissing=False):
1681 """Return the tip node for a given branch."""
1674 """Return the tip node for a given branch."""
1682
1675
1683 def lookup(key):
1676 def lookup(key):
1684 """Resolve the node for a revision."""
1677 """Resolve the node for a revision."""
1685
1678
1686 def lookupbranch(key):
1679 def lookupbranch(key):
1687 """Look up the branch name of the given revision or branch name."""
1680 """Look up the branch name of the given revision or branch name."""
1688
1681
1689 def known(nodes):
1682 def known(nodes):
1690 """Determine whether a series of nodes is known.
1683 """Determine whether a series of nodes is known.
1691
1684
1692 Returns a list of bools.
1685 Returns a list of bools.
1693 """
1686 """
1694
1687
1695 def local():
1688 def local():
1696 """Whether the repository is local."""
1689 """Whether the repository is local."""
1697 return True
1690 return True
1698
1691
1699 def publishing():
1692 def publishing():
1700 """Whether the repository is a publishing repository."""
1693 """Whether the repository is a publishing repository."""
1701
1694
1702 def cancopy():
1695 def cancopy():
1703 pass
1696 pass
1704
1697
1705 def shared():
1698 def shared():
1706 """The type of shared repository or None."""
1699 """The type of shared repository or None."""
1707
1700
1708 def wjoin(f, *insidef):
1701 def wjoin(f, *insidef):
1709 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1702 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1710
1703
1711 def setparents(p1, p2):
1704 def setparents(p1, p2):
1712 """Set the parent nodes of the working directory."""
1705 """Set the parent nodes of the working directory."""
1713
1706
1714 def filectx(path, changeid=None, fileid=None):
1707 def filectx(path, changeid=None, fileid=None):
1715 """Obtain a filectx for the given file revision."""
1708 """Obtain a filectx for the given file revision."""
1716
1709
1717 def getcwd():
1710 def getcwd():
1718 """Obtain the current working directory from the dirstate."""
1711 """Obtain the current working directory from the dirstate."""
1719
1712
1720 def pathto(f, cwd=None):
1713 def pathto(f, cwd=None):
1721 """Obtain the relative path to a file."""
1714 """Obtain the relative path to a file."""
1722
1715
1723 def adddatafilter(name, fltr):
1716 def adddatafilter(name, fltr):
1724 pass
1717 pass
1725
1718
1726 def wread(filename):
1719 def wread(filename):
1727 """Read a file from wvfs, using data filters."""
1720 """Read a file from wvfs, using data filters."""
1728
1721
1729 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1722 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1730 """Write data to a file in the wvfs, using data filters."""
1723 """Write data to a file in the wvfs, using data filters."""
1731
1724
1732 def wwritedata(filename, data):
1725 def wwritedata(filename, data):
1733 """Resolve data for writing to the wvfs, using data filters."""
1726 """Resolve data for writing to the wvfs, using data filters."""
1734
1727
1735 def currenttransaction():
1728 def currenttransaction():
1736 """Obtain the current transaction instance or None."""
1729 """Obtain the current transaction instance or None."""
1737
1730
1738 def transaction(desc, report=None):
1731 def transaction(desc, report=None):
1739 """Open a new transaction to write to the repository."""
1732 """Open a new transaction to write to the repository."""
1740
1733
1741 def undofiles():
1734 def undofiles():
1742 """Returns a list of (vfs, path) for files to undo transactions."""
1735 """Returns a list of (vfs, path) for files to undo transactions."""
1743
1736
1744 def recover():
1737 def recover():
1745 """Roll back an interrupted transaction."""
1738 """Roll back an interrupted transaction."""
1746
1739
1747 def rollback(dryrun=False, force=False):
1740 def rollback(dryrun=False, force=False):
1748 """Undo the last transaction.
1741 """Undo the last transaction.
1749
1742
1750 DANGEROUS.
1743 DANGEROUS.
1751 """
1744 """
1752
1745
1753 def updatecaches(tr=None, full=False):
1746 def updatecaches(tr=None, full=False):
1754 """Warm repo caches."""
1747 """Warm repo caches."""
1755
1748
1756 def invalidatecaches():
1749 def invalidatecaches():
1757 """Invalidate cached data due to the repository mutating."""
1750 """Invalidate cached data due to the repository mutating."""
1758
1751
1759 def invalidatevolatilesets():
1752 def invalidatevolatilesets():
1760 pass
1753 pass
1761
1754
1762 def invalidatedirstate():
1755 def invalidatedirstate():
1763 """Invalidate the dirstate."""
1756 """Invalidate the dirstate."""
1764
1757
1765 def invalidate(clearfilecache=False):
1758 def invalidate(clearfilecache=False):
1766 pass
1759 pass
1767
1760
1768 def invalidateall():
1761 def invalidateall():
1769 pass
1762 pass
1770
1763
1771 def lock(wait=True):
1764 def lock(wait=True):
1772 """Lock the repository store and return a lock instance."""
1765 """Lock the repository store and return a lock instance."""
1773
1766
1774 def wlock(wait=True):
1767 def wlock(wait=True):
1775 """Lock the non-store parts of the repository."""
1768 """Lock the non-store parts of the repository."""
1776
1769
1777 def currentwlock():
1770 def currentwlock():
1778 """Return the wlock if it's held or None."""
1771 """Return the wlock if it's held or None."""
1779
1772
1780 def checkcommitpatterns(wctx, match, status, fail):
1773 def checkcommitpatterns(wctx, match, status, fail):
1781 pass
1774 pass
1782
1775
1783 def commit(
1776 def commit(
1784 text=b'',
1777 text=b'',
1785 user=None,
1778 user=None,
1786 date=None,
1779 date=None,
1787 match=None,
1780 match=None,
1788 force=False,
1781 force=False,
1789 editor=False,
1782 editor=False,
1790 extra=None,
1783 extra=None,
1791 ):
1784 ):
1792 """Add a new revision to the repository."""
1785 """Add a new revision to the repository."""
1793
1786
1794 def commitctx(ctx, error=False, origctx=None):
1787 def commitctx(ctx, error=False, origctx=None):
1795 """Commit a commitctx instance to the repository."""
1788 """Commit a commitctx instance to the repository."""
1796
1789
1797 def destroying():
1790 def destroying():
1798 """Inform the repository that nodes are about to be destroyed."""
1791 """Inform the repository that nodes are about to be destroyed."""
1799
1792
1800 def destroyed():
1793 def destroyed():
1801 """Inform the repository that nodes have been destroyed."""
1794 """Inform the repository that nodes have been destroyed."""
1802
1795
1803 def status(
1796 def status(
1804 node1=b'.',
1797 node1=b'.',
1805 node2=None,
1798 node2=None,
1806 match=None,
1799 match=None,
1807 ignored=False,
1800 ignored=False,
1808 clean=False,
1801 clean=False,
1809 unknown=False,
1802 unknown=False,
1810 listsubrepos=False,
1803 listsubrepos=False,
1811 ):
1804 ):
1812 """Convenience method to call repo[x].status()."""
1805 """Convenience method to call repo[x].status()."""
1813
1806
1814 def addpostdsstatus(ps):
1807 def addpostdsstatus(ps):
1815 pass
1808 pass
1816
1809
1817 def postdsstatus():
1810 def postdsstatus():
1818 pass
1811 pass
1819
1812
1820 def clearpostdsstatus():
1813 def clearpostdsstatus():
1821 pass
1814 pass
1822
1815
1823 def heads(start=None):
1816 def heads(start=None):
1824 """Obtain list of nodes that are DAG heads."""
1817 """Obtain list of nodes that are DAG heads."""
1825
1818
1826 def branchheads(branch=None, start=None, closed=False):
1819 def branchheads(branch=None, start=None, closed=False):
1827 pass
1820 pass
1828
1821
1829 def branches(nodes):
1822 def branches(nodes):
1830 pass
1823 pass
1831
1824
1832 def between(pairs):
1825 def between(pairs):
1833 pass
1826 pass
1834
1827
1835 def checkpush(pushop):
1828 def checkpush(pushop):
1836 pass
1829 pass
1837
1830
1838 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1831 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1839
1832
1840 def pushkey(namespace, key, old, new):
1833 def pushkey(namespace, key, old, new):
1841 pass
1834 pass
1842
1835
1843 def listkeys(namespace):
1836 def listkeys(namespace):
1844 pass
1837 pass
1845
1838
1846 def debugwireargs(one, two, three=None, four=None, five=None):
1839 def debugwireargs(one, two, three=None, four=None, five=None):
1847 pass
1840 pass
1848
1841
1849 def savecommitmessage(text):
1842 def savecommitmessage(text):
1850 pass
1843 pass
1851
1844
1852 def register_sidedata_computer(
1845 def register_sidedata_computer(
1853 kind, category, keys, computer, flags, replace=False
1846 kind, category, keys, computer, flags, replace=False
1854 ):
1847 ):
1855 pass
1848 pass
1856
1849
1857 def register_wanted_sidedata(category):
1850 def register_wanted_sidedata(category):
1858 pass
1851 pass
1859
1852
1860
1853
1861 class completelocalrepository(
1854 class completelocalrepository(
1862 ilocalrepositorymain, ilocalrepositoryfilestorage
1855 ilocalrepositorymain, ilocalrepositoryfilestorage
1863 ):
1856 ):
1864 """Complete interface for a local repository."""
1857 """Complete interface for a local repository."""
1865
1858
1866
1859
1867 class iwireprotocolcommandcacher(interfaceutil.Interface):
1860 class iwireprotocolcommandcacher(interfaceutil.Interface):
1868 """Represents a caching backend for wire protocol commands.
1861 """Represents a caching backend for wire protocol commands.
1869
1862
1870 Wire protocol version 2 supports transparent caching of many commands.
1863 Wire protocol version 2 supports transparent caching of many commands.
1871 To leverage this caching, servers can activate objects that cache
1864 To leverage this caching, servers can activate objects that cache
1872 command responses. Objects handle both cache writing and reading.
1865 command responses. Objects handle both cache writing and reading.
1873 This interface defines how that response caching mechanism works.
1866 This interface defines how that response caching mechanism works.
1874
1867
1875 Wire protocol version 2 commands emit a series of objects that are
1868 Wire protocol version 2 commands emit a series of objects that are
1876 serialized and sent to the client. The caching layer exists between
1869 serialized and sent to the client. The caching layer exists between
1877 the invocation of the command function and the sending of its output
1870 the invocation of the command function and the sending of its output
1878 objects to an output layer.
1871 objects to an output layer.
1879
1872
1880 Instances of this interface represent a binding to a cache that
1873 Instances of this interface represent a binding to a cache that
1881 can serve a response (in place of calling a command function) and/or
1874 can serve a response (in place of calling a command function) and/or
1882 write responses to a cache for subsequent use.
1875 write responses to a cache for subsequent use.
1883
1876
1884 When a command request arrives, the following happens with regards
1877 When a command request arrives, the following happens with regards
1885 to this interface:
1878 to this interface:
1886
1879
1887 1. The server determines whether the command request is cacheable.
1880 1. The server determines whether the command request is cacheable.
1888 2. If it is, an instance of this interface is spawned.
1881 2. If it is, an instance of this interface is spawned.
1889 3. The cacher is activated in a context manager (``__enter__`` is called).
1882 3. The cacher is activated in a context manager (``__enter__`` is called).
1890 4. A cache *key* for that request is derived. This will call the
1883 4. A cache *key* for that request is derived. This will call the
1891 instance's ``adjustcachekeystate()`` method so the derivation
1884 instance's ``adjustcachekeystate()`` method so the derivation
1892 can be influenced.
1885 can be influenced.
1893 5. The cacher is informed of the derived cache key via a call to
1886 5. The cacher is informed of the derived cache key via a call to
1894 ``setcachekey()``.
1887 ``setcachekey()``.
1895 6. The cacher's ``lookup()`` method is called to test for presence of
1888 6. The cacher's ``lookup()`` method is called to test for presence of
1896 the derived key in the cache.
1889 the derived key in the cache.
1897 7. If ``lookup()`` returns a hit, that cached result is used in place
1890 7. If ``lookup()`` returns a hit, that cached result is used in place
1898 of invoking the command function. ``__exit__`` is called and the instance
1891 of invoking the command function. ``__exit__`` is called and the instance
1899 is discarded.
1892 is discarded.
1900 8. The command function is invoked.
1893 8. The command function is invoked.
1901 9. ``onobject()`` is called for each object emitted by the command
1894 9. ``onobject()`` is called for each object emitted by the command
1902 function.
1895 function.
1903 10. After the final object is seen, ``onfinished()`` is called.
1896 10. After the final object is seen, ``onfinished()`` is called.
1904 11. ``__exit__`` is called to signal the end of use of the instance.
1897 11. ``__exit__`` is called to signal the end of use of the instance.
1905
1898
1906 Cache *key* derivation can be influenced by the instance.
1899 Cache *key* derivation can be influenced by the instance.
1907
1900
1908 Cache keys are initially derived by a deterministic representation of
1901 Cache keys are initially derived by a deterministic representation of
1909 the command request. This includes the command name, arguments, protocol
1902 the command request. This includes the command name, arguments, protocol
1910 version, etc. This initial key derivation is performed by CBOR-encoding a
1903 version, etc. This initial key derivation is performed by CBOR-encoding a
1911 data structure and feeding that output into a hasher.
1904 data structure and feeding that output into a hasher.
1912
1905
1913 Instances of this interface can influence this initial key derivation
1906 Instances of this interface can influence this initial key derivation
1914 via ``adjustcachekeystate()``.
1907 via ``adjustcachekeystate()``.
1915
1908
1916 The instance is informed of the derived cache key via a call to
1909 The instance is informed of the derived cache key via a call to
1917 ``setcachekey()``. The instance must store the key locally so it can
1910 ``setcachekey()``. The instance must store the key locally so it can
1918 be consulted on subsequent operations that may require it.
1911 be consulted on subsequent operations that may require it.
1919
1912
1920 When constructed, the instance has access to a callable that can be used
1913 When constructed, the instance has access to a callable that can be used
1921 for encoding response objects. This callable receives as its single
1914 for encoding response objects. This callable receives as its single
1922 argument an object emitted by a command function. It returns an iterable
1915 argument an object emitted by a command function. It returns an iterable
1923 of bytes chunks representing the encoded object. Unless the cacher is
1916 of bytes chunks representing the encoded object. Unless the cacher is
1924 caching native Python objects in memory or has a way of reconstructing
1917 caching native Python objects in memory or has a way of reconstructing
1925 the original Python objects, implementations typically call this function
1918 the original Python objects, implementations typically call this function
1926 to produce bytes from the output objects and then store those bytes in
1919 to produce bytes from the output objects and then store those bytes in
1927 the cache. When it comes time to re-emit those bytes, they are wrapped
1920 the cache. When it comes time to re-emit those bytes, they are wrapped
1928 in a ``wireprototypes.encodedresponse`` instance to tell the output
1921 in a ``wireprototypes.encodedresponse`` instance to tell the output
1929 layer that they are pre-encoded.
1922 layer that they are pre-encoded.
1930
1923
1931 When receiving the objects emitted by the command function, instances
1924 When receiving the objects emitted by the command function, instances
1932 can choose what to do with those objects. The simplest thing to do is
1925 can choose what to do with those objects. The simplest thing to do is
1933 re-emit the original objects. They will be forwarded to the output
1926 re-emit the original objects. They will be forwarded to the output
1934 layer and will be processed as if the cacher did not exist.
1927 layer and will be processed as if the cacher did not exist.
1935
1928
1936 Implementations could also choose to not emit objects - instead locally
1929 Implementations could also choose to not emit objects - instead locally
1937 buffering objects or their encoded representation. They could then emit
1930 buffering objects or their encoded representation. They could then emit
1938 a single "coalesced" object when ``onfinished()`` is called. In
1931 a single "coalesced" object when ``onfinished()`` is called. In
1939 this way, the implementation would function as a filtering layer of
1932 this way, the implementation would function as a filtering layer of
1940 sorts.
1933 sorts.
1941
1934
1942 When caching objects, typically the encoded form of the object will
1935 When caching objects, typically the encoded form of the object will
1943 be stored. Keep in mind that if the original object is forwarded to
1936 be stored. Keep in mind that if the original object is forwarded to
1944 the output layer, it will need to be encoded there as well. For large
1937 the output layer, it will need to be encoded there as well. For large
1945 output, this redundant encoding could add overhead. Implementations
1938 output, this redundant encoding could add overhead. Implementations
1946 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1939 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1947 instances to avoid this overhead.
1940 instances to avoid this overhead.
1948 """
1941 """
1949
1942
1950 def __enter__():
1943 def __enter__():
1951 """Marks the instance as active.
1944 """Marks the instance as active.
1952
1945
1953 Should return self.
1946 Should return self.
1954 """
1947 """
1955
1948
1956 def __exit__(exctype, excvalue, exctb):
1949 def __exit__(exctype, excvalue, exctb):
1957 """Called when cacher is no longer used.
1950 """Called when cacher is no longer used.
1958
1951
1959 This can be used by implementations to perform cleanup actions (e.g.
1952 This can be used by implementations to perform cleanup actions (e.g.
1960 disconnecting network sockets, aborting a partially cached response.
1953 disconnecting network sockets, aborting a partially cached response.
1961 """
1954 """
1962
1955
1963 def adjustcachekeystate(state):
1956 def adjustcachekeystate(state):
1964 """Influences cache key derivation by adjusting state to derive key.
1957 """Influences cache key derivation by adjusting state to derive key.
1965
1958
1966 A dict defining the state used to derive the cache key is passed.
1959 A dict defining the state used to derive the cache key is passed.
1967
1960
1968 Implementations can modify this dict to record additional state that
1961 Implementations can modify this dict to record additional state that
1969 is wanted to influence key derivation.
1962 is wanted to influence key derivation.
1970
1963
1971 Implementations are *highly* encouraged to not modify or delete
1964 Implementations are *highly* encouraged to not modify or delete
1972 existing keys.
1965 existing keys.
1973 """
1966 """
1974
1967
1975 def setcachekey(key):
1968 def setcachekey(key):
1976 """Record the derived cache key for this request.
1969 """Record the derived cache key for this request.
1977
1970
1978 Instances may mutate the key for internal usage, as desired. e.g.
1971 Instances may mutate the key for internal usage, as desired. e.g.
1979 instances may wish to prepend the repo name, introduce path
1972 instances may wish to prepend the repo name, introduce path
1980 components for filesystem or URL addressing, etc. Behavior is up to
1973 components for filesystem or URL addressing, etc. Behavior is up to
1981 the cache.
1974 the cache.
1982
1975
1983 Returns a bool indicating if the request is cacheable by this
1976 Returns a bool indicating if the request is cacheable by this
1984 instance.
1977 instance.
1985 """
1978 """
1986
1979
1987 def lookup():
1980 def lookup():
1988 """Attempt to resolve an entry in the cache.
1981 """Attempt to resolve an entry in the cache.
1989
1982
1990 The instance is instructed to look for the cache key that it was
1983 The instance is instructed to look for the cache key that it was
1991 informed about via the call to ``setcachekey()``.
1984 informed about via the call to ``setcachekey()``.
1992
1985
1993 If there's no cache hit or the cacher doesn't wish to use the cached
1986 If there's no cache hit or the cacher doesn't wish to use the cached
1994 entry, ``None`` should be returned.
1987 entry, ``None`` should be returned.
1995
1988
1996 Else, a dict defining the cached result should be returned. The
1989 Else, a dict defining the cached result should be returned. The
1997 dict may have the following keys:
1990 dict may have the following keys:
1998
1991
1999 objs
1992 objs
2000 An iterable of objects that should be sent to the client. That
1993 An iterable of objects that should be sent to the client. That
2001 iterable of objects is expected to be what the command function
1994 iterable of objects is expected to be what the command function
2002 would return if invoked or an equivalent representation thereof.
1995 would return if invoked or an equivalent representation thereof.
2003 """
1996 """
2004
1997
2005 def onobject(obj):
1998 def onobject(obj):
2006 """Called when a new object is emitted from the command function.
1999 """Called when a new object is emitted from the command function.
2007
2000
2008 Receives as its argument the object that was emitted from the
2001 Receives as its argument the object that was emitted from the
2009 command function.
2002 command function.
2010
2003
2011 This method returns an iterator of objects to forward to the output
2004 This method returns an iterator of objects to forward to the output
2012 layer. The easiest implementation is a generator that just
2005 layer. The easiest implementation is a generator that just
2013 ``yield obj``.
2006 ``yield obj``.
2014 """
2007 """
2015
2008
2016 def onfinished():
2009 def onfinished():
2017 """Called after all objects have been emitted from the command function.
2010 """Called after all objects have been emitted from the command function.
2018
2011
2019 Implementations should return an iterator of objects to forward to
2012 Implementations should return an iterator of objects to forward to
2020 the output layer.
2013 the output layer.
2021
2014
2022 This method can be a generator.
2015 This method can be a generator.
2023 """
2016 """
@@ -1,2384 +1,2376 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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 nullrev,
19 nullrev,
20 )
20 )
21 from .pycompat import getattr
21 from .pycompat import getattr
22 from . import (
22 from . import (
23 encoding,
23 encoding,
24 error,
24 error,
25 match as matchmod,
25 match as matchmod,
26 mdiff,
26 mdiff,
27 pathutil,
27 pathutil,
28 policy,
28 policy,
29 pycompat,
29 pycompat,
30 revlog,
30 revlog,
31 util,
31 util,
32 )
32 )
33 from .interfaces import (
33 from .interfaces import (
34 repository,
34 repository,
35 util as interfaceutil,
35 util as interfaceutil,
36 )
36 )
37 from .revlogutils import (
37 from .revlogutils import (
38 constants as revlog_constants,
38 constants as revlog_constants,
39 )
39 )
40
40
41 parsers = policy.importmod('parsers')
41 parsers = policy.importmod('parsers')
42 propertycache = util.propertycache
42 propertycache = util.propertycache
43
43
44 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
44 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
45 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
45 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
46
46
47
47
48 def _parse(nodelen, data):
48 def _parse(nodelen, data):
49 # This method does a little bit of excessive-looking
49 # This method does a little bit of excessive-looking
50 # precondition checking. This is so that the behavior of this
50 # precondition checking. This is so that the behavior of this
51 # class exactly matches its C counterpart to try and help
51 # class exactly matches its C counterpart to try and help
52 # prevent surprise breakage for anyone that develops against
52 # prevent surprise breakage for anyone that develops against
53 # the pure version.
53 # the pure version.
54 if data and data[-1:] != b'\n':
54 if data and data[-1:] != b'\n':
55 raise ValueError(b'Manifest did not end in a newline.')
55 raise ValueError(b'Manifest did not end in a newline.')
56 prev = None
56 prev = None
57 for l in data.splitlines():
57 for l in data.splitlines():
58 if prev is not None and prev > l:
58 if prev is not None and prev > l:
59 raise ValueError(b'Manifest lines not in sorted order.')
59 raise ValueError(b'Manifest lines not in sorted order.')
60 prev = l
60 prev = l
61 f, n = l.split(b'\0')
61 f, n = l.split(b'\0')
62 nl = len(n)
62 nl = len(n)
63 flags = n[-1:]
63 flags = n[-1:]
64 if flags in _manifestflags:
64 if flags in _manifestflags:
65 n = n[:-1]
65 n = n[:-1]
66 nl -= 1
66 nl -= 1
67 else:
67 else:
68 flags = b''
68 flags = b''
69 if nl != 2 * nodelen:
69 if nl != 2 * nodelen:
70 raise ValueError(b'Invalid manifest line')
70 raise ValueError(b'Invalid manifest line')
71
71
72 yield f, bin(n), flags
72 yield f, bin(n), flags
73
73
74
74
75 def _text(it):
75 def _text(it):
76 files = []
76 files = []
77 lines = []
77 lines = []
78 for f, n, fl in it:
78 for f, n, fl in it:
79 files.append(f)
79 files.append(f)
80 # if this is changed to support newlines in filenames,
80 # if this is changed to support newlines in filenames,
81 # be sure to check the templates/ dir again (especially *-raw.tmpl)
81 # be sure to check the templates/ dir again (especially *-raw.tmpl)
82 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
82 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
83
83
84 _checkforbidden(files)
84 _checkforbidden(files)
85 return b''.join(lines)
85 return b''.join(lines)
86
86
87
87
88 class lazymanifestiter(object):
88 class lazymanifestiter(object):
89 def __init__(self, lm):
89 def __init__(self, lm):
90 self.pos = 0
90 self.pos = 0
91 self.lm = lm
91 self.lm = lm
92
92
93 def __iter__(self):
93 def __iter__(self):
94 return self
94 return self
95
95
96 def next(self):
96 def next(self):
97 try:
97 try:
98 data, pos = self.lm._get(self.pos)
98 data, pos = self.lm._get(self.pos)
99 except IndexError:
99 except IndexError:
100 raise StopIteration
100 raise StopIteration
101 if pos == -1:
101 if pos == -1:
102 self.pos += 1
102 self.pos += 1
103 return data[0]
103 return data[0]
104 self.pos += 1
104 self.pos += 1
105 zeropos = data.find(b'\x00', pos)
105 zeropos = data.find(b'\x00', pos)
106 return data[pos:zeropos]
106 return data[pos:zeropos]
107
107
108 __next__ = next
108 __next__ = next
109
109
110
110
111 class lazymanifestiterentries(object):
111 class lazymanifestiterentries(object):
112 def __init__(self, lm):
112 def __init__(self, lm):
113 self.lm = lm
113 self.lm = lm
114 self.pos = 0
114 self.pos = 0
115
115
116 def __iter__(self):
116 def __iter__(self):
117 return self
117 return self
118
118
119 def next(self):
119 def next(self):
120 try:
120 try:
121 data, pos = self.lm._get(self.pos)
121 data, pos = self.lm._get(self.pos)
122 except IndexError:
122 except IndexError:
123 raise StopIteration
123 raise StopIteration
124 if pos == -1:
124 if pos == -1:
125 self.pos += 1
125 self.pos += 1
126 return data
126 return data
127 zeropos = data.find(b'\x00', pos)
127 zeropos = data.find(b'\x00', pos)
128 nlpos = data.find(b'\n', pos)
128 nlpos = data.find(b'\n', pos)
129 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
129 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
130 raise error.StorageError(b'Invalid manifest line')
130 raise error.StorageError(b'Invalid manifest line')
131 flags = data[nlpos - 1 : nlpos]
131 flags = data[nlpos - 1 : nlpos]
132 if flags in _manifestflags:
132 if flags in _manifestflags:
133 hlen = nlpos - zeropos - 2
133 hlen = nlpos - zeropos - 2
134 else:
134 else:
135 hlen = nlpos - zeropos - 1
135 hlen = nlpos - zeropos - 1
136 flags = b''
136 flags = b''
137 if hlen != 2 * self.lm._nodelen:
137 if hlen != 2 * self.lm._nodelen:
138 raise error.StorageError(b'Invalid manifest line')
138 raise error.StorageError(b'Invalid manifest line')
139 hashval = unhexlify(
139 hashval = unhexlify(
140 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
140 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
141 )
141 )
142 self.pos += 1
142 self.pos += 1
143 return (data[pos:zeropos], hashval, flags)
143 return (data[pos:zeropos], hashval, flags)
144
144
145 __next__ = next
145 __next__ = next
146
146
147
147
148 def unhexlify(data, extra, pos, length):
148 def unhexlify(data, extra, pos, length):
149 s = bin(data[pos : pos + length])
149 s = bin(data[pos : pos + length])
150 if extra:
150 if extra:
151 s += chr(extra & 0xFF)
151 s += chr(extra & 0xFF)
152 return s
152 return s
153
153
154
154
155 def _cmp(a, b):
155 def _cmp(a, b):
156 return (a > b) - (a < b)
156 return (a > b) - (a < b)
157
157
158
158
159 _manifestflags = {b'', b'l', b't', b'x'}
159 _manifestflags = {b'', b'l', b't', b'x'}
160
160
161
161
162 class _lazymanifest(object):
162 class _lazymanifest(object):
163 """A pure python manifest backed by a byte string. It is supplimented with
163 """A pure python manifest backed by a byte string. It is supplimented with
164 internal lists as it is modified, until it is compacted back to a pure byte
164 internal lists as it is modified, until it is compacted back to a pure byte
165 string.
165 string.
166
166
167 ``data`` is the initial manifest data.
167 ``data`` is the initial manifest data.
168
168
169 ``positions`` is a list of offsets, one per manifest entry. Positive
169 ``positions`` is a list of offsets, one per manifest entry. Positive
170 values are offsets into ``data``, negative values are offsets into the
170 values are offsets into ``data``, negative values are offsets into the
171 ``extradata`` list. When an entry is removed, its entry is dropped from
171 ``extradata`` list. When an entry is removed, its entry is dropped from
172 ``positions``. The values are encoded such that when walking the list and
172 ``positions``. The values are encoded such that when walking the list and
173 indexing into ``data`` or ``extradata`` as appropriate, the entries are
173 indexing into ``data`` or ``extradata`` as appropriate, the entries are
174 sorted by filename.
174 sorted by filename.
175
175
176 ``extradata`` is a list of (key, hash, flags) for entries that were added or
176 ``extradata`` is a list of (key, hash, flags) for entries that were added or
177 modified since the manifest was created or compacted.
177 modified since the manifest was created or compacted.
178 """
178 """
179
179
180 def __init__(
180 def __init__(
181 self,
181 self,
182 nodelen,
182 nodelen,
183 data,
183 data,
184 positions=None,
184 positions=None,
185 extrainfo=None,
185 extrainfo=None,
186 extradata=None,
186 extradata=None,
187 hasremovals=False,
187 hasremovals=False,
188 ):
188 ):
189 self._nodelen = nodelen
189 self._nodelen = nodelen
190 if positions is None:
190 if positions is None:
191 self.positions = self.findlines(data)
191 self.positions = self.findlines(data)
192 self.extrainfo = [0] * len(self.positions)
192 self.extrainfo = [0] * len(self.positions)
193 self.data = data
193 self.data = data
194 self.extradata = []
194 self.extradata = []
195 self.hasremovals = False
195 self.hasremovals = False
196 else:
196 else:
197 self.positions = positions[:]
197 self.positions = positions[:]
198 self.extrainfo = extrainfo[:]
198 self.extrainfo = extrainfo[:]
199 self.extradata = extradata[:]
199 self.extradata = extradata[:]
200 self.data = data
200 self.data = data
201 self.hasremovals = hasremovals
201 self.hasremovals = hasremovals
202
202
203 def findlines(self, data):
203 def findlines(self, data):
204 if not data:
204 if not data:
205 return []
205 return []
206 pos = data.find(b"\n")
206 pos = data.find(b"\n")
207 if pos == -1 or data[-1:] != b'\n':
207 if pos == -1 or data[-1:] != b'\n':
208 raise ValueError(b"Manifest did not end in a newline.")
208 raise ValueError(b"Manifest did not end in a newline.")
209 positions = [0]
209 positions = [0]
210 prev = data[: data.find(b'\x00')]
210 prev = data[: data.find(b'\x00')]
211 while pos < len(data) - 1 and pos != -1:
211 while pos < len(data) - 1 and pos != -1:
212 positions.append(pos + 1)
212 positions.append(pos + 1)
213 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
213 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
214 if nexts < prev:
214 if nexts < prev:
215 raise ValueError(b"Manifest lines not in sorted order.")
215 raise ValueError(b"Manifest lines not in sorted order.")
216 prev = nexts
216 prev = nexts
217 pos = data.find(b"\n", pos + 1)
217 pos = data.find(b"\n", pos + 1)
218 return positions
218 return positions
219
219
220 def _get(self, index):
220 def _get(self, index):
221 # get the position encoded in pos:
221 # get the position encoded in pos:
222 # positive number is an index in 'data'
222 # positive number is an index in 'data'
223 # negative number is in extrapieces
223 # negative number is in extrapieces
224 pos = self.positions[index]
224 pos = self.positions[index]
225 if pos >= 0:
225 if pos >= 0:
226 return self.data, pos
226 return self.data, pos
227 return self.extradata[-pos - 1], -1
227 return self.extradata[-pos - 1], -1
228
228
229 def _getkey(self, pos):
229 def _getkey(self, pos):
230 if pos >= 0:
230 if pos >= 0:
231 return self.data[pos : self.data.find(b'\x00', pos + 1)]
231 return self.data[pos : self.data.find(b'\x00', pos + 1)]
232 return self.extradata[-pos - 1][0]
232 return self.extradata[-pos - 1][0]
233
233
234 def bsearch(self, key):
234 def bsearch(self, key):
235 first = 0
235 first = 0
236 last = len(self.positions) - 1
236 last = len(self.positions) - 1
237
237
238 while first <= last:
238 while first <= last:
239 midpoint = (first + last) // 2
239 midpoint = (first + last) // 2
240 nextpos = self.positions[midpoint]
240 nextpos = self.positions[midpoint]
241 candidate = self._getkey(nextpos)
241 candidate = self._getkey(nextpos)
242 r = _cmp(key, candidate)
242 r = _cmp(key, candidate)
243 if r == 0:
243 if r == 0:
244 return midpoint
244 return midpoint
245 else:
245 else:
246 if r < 0:
246 if r < 0:
247 last = midpoint - 1
247 last = midpoint - 1
248 else:
248 else:
249 first = midpoint + 1
249 first = midpoint + 1
250 return -1
250 return -1
251
251
252 def bsearch2(self, key):
252 def bsearch2(self, key):
253 # same as the above, but will always return the position
253 # same as the above, but will always return the position
254 # done for performance reasons
254 # done for performance reasons
255 first = 0
255 first = 0
256 last = len(self.positions) - 1
256 last = len(self.positions) - 1
257
257
258 while first <= last:
258 while first <= last:
259 midpoint = (first + last) // 2
259 midpoint = (first + last) // 2
260 nextpos = self.positions[midpoint]
260 nextpos = self.positions[midpoint]
261 candidate = self._getkey(nextpos)
261 candidate = self._getkey(nextpos)
262 r = _cmp(key, candidate)
262 r = _cmp(key, candidate)
263 if r == 0:
263 if r == 0:
264 return (midpoint, True)
264 return (midpoint, True)
265 else:
265 else:
266 if r < 0:
266 if r < 0:
267 last = midpoint - 1
267 last = midpoint - 1
268 else:
268 else:
269 first = midpoint + 1
269 first = midpoint + 1
270 return (first, False)
270 return (first, False)
271
271
272 def __contains__(self, key):
272 def __contains__(self, key):
273 return self.bsearch(key) != -1
273 return self.bsearch(key) != -1
274
274
275 def __getitem__(self, key):
275 def __getitem__(self, key):
276 if not isinstance(key, bytes):
276 if not isinstance(key, bytes):
277 raise TypeError(b"getitem: manifest keys must be a bytes.")
277 raise TypeError(b"getitem: manifest keys must be a bytes.")
278 needle = self.bsearch(key)
278 needle = self.bsearch(key)
279 if needle == -1:
279 if needle == -1:
280 raise KeyError
280 raise KeyError
281 data, pos = self._get(needle)
281 data, pos = self._get(needle)
282 if pos == -1:
282 if pos == -1:
283 return (data[1], data[2])
283 return (data[1], data[2])
284 zeropos = data.find(b'\x00', pos)
284 zeropos = data.find(b'\x00', pos)
285 nlpos = data.find(b'\n', zeropos)
285 nlpos = data.find(b'\n', zeropos)
286 assert 0 <= needle <= len(self.positions)
286 assert 0 <= needle <= len(self.positions)
287 assert len(self.extrainfo) == len(self.positions)
287 assert len(self.extrainfo) == len(self.positions)
288 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
288 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
289 raise error.StorageError(b'Invalid manifest line')
289 raise error.StorageError(b'Invalid manifest line')
290 hlen = nlpos - zeropos - 1
290 hlen = nlpos - zeropos - 1
291 flags = data[nlpos - 1 : nlpos]
291 flags = data[nlpos - 1 : nlpos]
292 if flags in _manifestflags:
292 if flags in _manifestflags:
293 hlen -= 1
293 hlen -= 1
294 else:
294 else:
295 flags = b''
295 flags = b''
296 if hlen != 2 * self._nodelen:
296 if hlen != 2 * self._nodelen:
297 raise error.StorageError(b'Invalid manifest line')
297 raise error.StorageError(b'Invalid manifest line')
298 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
298 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
299 return (hashval, flags)
299 return (hashval, flags)
300
300
301 def __delitem__(self, key):
301 def __delitem__(self, key):
302 needle, found = self.bsearch2(key)
302 needle, found = self.bsearch2(key)
303 if not found:
303 if not found:
304 raise KeyError
304 raise KeyError
305 cur = self.positions[needle]
305 cur = self.positions[needle]
306 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
306 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
307 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
307 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
308 if cur >= 0:
308 if cur >= 0:
309 # This does NOT unsort the list as far as the search functions are
309 # This does NOT unsort the list as far as the search functions are
310 # concerned, as they only examine lines mapped by self.positions.
310 # concerned, as they only examine lines mapped by self.positions.
311 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
311 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
312 self.hasremovals = True
312 self.hasremovals = True
313
313
314 def __setitem__(self, key, value):
314 def __setitem__(self, key, value):
315 if not isinstance(key, bytes):
315 if not isinstance(key, bytes):
316 raise TypeError(b"setitem: manifest keys must be a byte string.")
316 raise TypeError(b"setitem: manifest keys must be a byte string.")
317 if not isinstance(value, tuple) or len(value) != 2:
317 if not isinstance(value, tuple) or len(value) != 2:
318 raise TypeError(
318 raise TypeError(
319 b"Manifest values must be a tuple of (node, flags)."
319 b"Manifest values must be a tuple of (node, flags)."
320 )
320 )
321 hashval = value[0]
321 hashval = value[0]
322 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
322 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
323 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
323 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
324 flags = value[1]
324 flags = value[1]
325 if not isinstance(flags, bytes) or len(flags) > 1:
325 if not isinstance(flags, bytes) or len(flags) > 1:
326 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
326 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
327 needle, found = self.bsearch2(key)
327 needle, found = self.bsearch2(key)
328 if found:
328 if found:
329 # put the item
329 # put the item
330 pos = self.positions[needle]
330 pos = self.positions[needle]
331 if pos < 0:
331 if pos < 0:
332 self.extradata[-pos - 1] = (key, hashval, value[1])
332 self.extradata[-pos - 1] = (key, hashval, value[1])
333 else:
333 else:
334 # just don't bother
334 # just don't bother
335 self.extradata.append((key, hashval, value[1]))
335 self.extradata.append((key, hashval, value[1]))
336 self.positions[needle] = -len(self.extradata)
336 self.positions[needle] = -len(self.extradata)
337 else:
337 else:
338 # not found, put it in with extra positions
338 # not found, put it in with extra positions
339 self.extradata.append((key, hashval, value[1]))
339 self.extradata.append((key, hashval, value[1]))
340 self.positions = (
340 self.positions = (
341 self.positions[:needle]
341 self.positions[:needle]
342 + [-len(self.extradata)]
342 + [-len(self.extradata)]
343 + self.positions[needle:]
343 + self.positions[needle:]
344 )
344 )
345 self.extrainfo = (
345 self.extrainfo = (
346 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
346 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
347 )
347 )
348
348
349 def copy(self):
349 def copy(self):
350 # XXX call _compact like in C?
350 # XXX call _compact like in C?
351 return _lazymanifest(
351 return _lazymanifest(
352 self._nodelen,
352 self._nodelen,
353 self.data,
353 self.data,
354 self.positions,
354 self.positions,
355 self.extrainfo,
355 self.extrainfo,
356 self.extradata,
356 self.extradata,
357 self.hasremovals,
357 self.hasremovals,
358 )
358 )
359
359
360 def _compact(self):
360 def _compact(self):
361 # hopefully not called TOO often
361 # hopefully not called TOO often
362 if len(self.extradata) == 0 and not self.hasremovals:
362 if len(self.extradata) == 0 and not self.hasremovals:
363 return
363 return
364 l = []
364 l = []
365 i = 0
365 i = 0
366 offset = 0
366 offset = 0
367 self.extrainfo = [0] * len(self.positions)
367 self.extrainfo = [0] * len(self.positions)
368 while i < len(self.positions):
368 while i < len(self.positions):
369 if self.positions[i] >= 0:
369 if self.positions[i] >= 0:
370 cur = self.positions[i]
370 cur = self.positions[i]
371 last_cut = cur
371 last_cut = cur
372
372
373 # Collect all contiguous entries in the buffer at the current
373 # Collect all contiguous entries in the buffer at the current
374 # offset, breaking out only for added/modified items held in
374 # offset, breaking out only for added/modified items held in
375 # extradata, or a deleted line prior to the next position.
375 # extradata, or a deleted line prior to the next position.
376 while True:
376 while True:
377 self.positions[i] = offset
377 self.positions[i] = offset
378 i += 1
378 i += 1
379 if i == len(self.positions) or self.positions[i] < 0:
379 if i == len(self.positions) or self.positions[i] < 0:
380 break
380 break
381
381
382 # A removed file has no positions[] entry, but does have an
382 # A removed file has no positions[] entry, but does have an
383 # overwritten first byte. Break out and find the end of the
383 # overwritten first byte. Break out and find the end of the
384 # current good entry/entries if there is a removed file
384 # current good entry/entries if there is a removed file
385 # before the next position.
385 # before the next position.
386 if (
386 if (
387 self.hasremovals
387 self.hasremovals
388 and self.data.find(b'\n\x00', cur, self.positions[i])
388 and self.data.find(b'\n\x00', cur, self.positions[i])
389 != -1
389 != -1
390 ):
390 ):
391 break
391 break
392
392
393 offset += self.positions[i] - cur
393 offset += self.positions[i] - cur
394 cur = self.positions[i]
394 cur = self.positions[i]
395 end_cut = self.data.find(b'\n', cur)
395 end_cut = self.data.find(b'\n', cur)
396 if end_cut != -1:
396 if end_cut != -1:
397 end_cut += 1
397 end_cut += 1
398 offset += end_cut - cur
398 offset += end_cut - cur
399 l.append(self.data[last_cut:end_cut])
399 l.append(self.data[last_cut:end_cut])
400 else:
400 else:
401 while i < len(self.positions) and self.positions[i] < 0:
401 while i < len(self.positions) and self.positions[i] < 0:
402 cur = self.positions[i]
402 cur = self.positions[i]
403 t = self.extradata[-cur - 1]
403 t = self.extradata[-cur - 1]
404 l.append(self._pack(t))
404 l.append(self._pack(t))
405 self.positions[i] = offset
405 self.positions[i] = offset
406 # Hashes are either 20 bytes (old sha1s) or 32
406 # Hashes are either 20 bytes (old sha1s) or 32
407 # bytes (new non-sha1).
407 # bytes (new non-sha1).
408 hlen = 20
408 hlen = 20
409 if len(t[1]) > 25:
409 if len(t[1]) > 25:
410 hlen = 32
410 hlen = 32
411 if len(t[1]) > hlen:
411 if len(t[1]) > hlen:
412 self.extrainfo[i] = ord(t[1][hlen + 1])
412 self.extrainfo[i] = ord(t[1][hlen + 1])
413 offset += len(l[-1])
413 offset += len(l[-1])
414 i += 1
414 i += 1
415 self.data = b''.join(l)
415 self.data = b''.join(l)
416 self.hasremovals = False
416 self.hasremovals = False
417 self.extradata = []
417 self.extradata = []
418
418
419 def _pack(self, d):
419 def _pack(self, d):
420 n = d[1]
420 n = d[1]
421 assert len(n) in (20, 32)
421 assert len(n) in (20, 32)
422 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
422 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
423
423
424 def text(self):
424 def text(self):
425 self._compact()
425 self._compact()
426 return self.data
426 return self.data
427
427
428 def diff(self, m2, clean=False):
428 def diff(self, m2, clean=False):
429 '''Finds changes between the current manifest and m2.'''
429 '''Finds changes between the current manifest and m2.'''
430 # XXX think whether efficiency matters here
430 # XXX think whether efficiency matters here
431 diff = {}
431 diff = {}
432
432
433 for fn, e1, flags in self.iterentries():
433 for fn, e1, flags in self.iterentries():
434 if fn not in m2:
434 if fn not in m2:
435 diff[fn] = (e1, flags), (None, b'')
435 diff[fn] = (e1, flags), (None, b'')
436 else:
436 else:
437 e2 = m2[fn]
437 e2 = m2[fn]
438 if (e1, flags) != e2:
438 if (e1, flags) != e2:
439 diff[fn] = (e1, flags), e2
439 diff[fn] = (e1, flags), e2
440 elif clean:
440 elif clean:
441 diff[fn] = None
441 diff[fn] = None
442
442
443 for fn, e2, flags in m2.iterentries():
443 for fn, e2, flags in m2.iterentries():
444 if fn not in self:
444 if fn not in self:
445 diff[fn] = (None, b''), (e2, flags)
445 diff[fn] = (None, b''), (e2, flags)
446
446
447 return diff
447 return diff
448
448
449 def iterentries(self):
449 def iterentries(self):
450 return lazymanifestiterentries(self)
450 return lazymanifestiterentries(self)
451
451
452 def iterkeys(self):
452 def iterkeys(self):
453 return lazymanifestiter(self)
453 return lazymanifestiter(self)
454
454
455 def __iter__(self):
455 def __iter__(self):
456 return lazymanifestiter(self)
456 return lazymanifestiter(self)
457
457
458 def __len__(self):
458 def __len__(self):
459 return len(self.positions)
459 return len(self.positions)
460
460
461 def filtercopy(self, filterfn):
461 def filtercopy(self, filterfn):
462 # XXX should be optimized
462 # XXX should be optimized
463 c = _lazymanifest(self._nodelen, b'')
463 c = _lazymanifest(self._nodelen, b'')
464 for f, n, fl in self.iterentries():
464 for f, n, fl in self.iterentries():
465 if filterfn(f):
465 if filterfn(f):
466 c[f] = n, fl
466 c[f] = n, fl
467 return c
467 return c
468
468
469
469
470 try:
470 try:
471 _lazymanifest = parsers.lazymanifest
471 _lazymanifest = parsers.lazymanifest
472 except AttributeError:
472 except AttributeError:
473 pass
473 pass
474
474
475
475
476 @interfaceutil.implementer(repository.imanifestdict)
476 @interfaceutil.implementer(repository.imanifestdict)
477 class manifestdict(object):
477 class manifestdict(object):
478 def __init__(self, nodelen, data=b''):
478 def __init__(self, nodelen, data=b''):
479 self._nodelen = nodelen
479 self._nodelen = nodelen
480 self._lm = _lazymanifest(nodelen, data)
480 self._lm = _lazymanifest(nodelen, data)
481
481
482 def __getitem__(self, key):
482 def __getitem__(self, key):
483 return self._lm[key][0]
483 return self._lm[key][0]
484
484
485 def find(self, key):
485 def find(self, key):
486 return self._lm[key]
486 return self._lm[key]
487
487
488 def __len__(self):
488 def __len__(self):
489 return len(self._lm)
489 return len(self._lm)
490
490
491 def __nonzero__(self):
491 def __nonzero__(self):
492 # nonzero is covered by the __len__ function, but implementing it here
492 # nonzero is covered by the __len__ function, but implementing it here
493 # makes it easier for extensions to override.
493 # makes it easier for extensions to override.
494 return len(self._lm) != 0
494 return len(self._lm) != 0
495
495
496 __bool__ = __nonzero__
496 __bool__ = __nonzero__
497
497
498 def __setitem__(self, key, node):
498 def __setitem__(self, key, node):
499 self._lm[key] = node, self.flags(key)
499 self._lm[key] = node, self.flags(key)
500
500
501 def __contains__(self, key):
501 def __contains__(self, key):
502 if key is None:
502 if key is None:
503 return False
503 return False
504 return key in self._lm
504 return key in self._lm
505
505
506 def __delitem__(self, key):
506 def __delitem__(self, key):
507 del self._lm[key]
507 del self._lm[key]
508
508
509 def __iter__(self):
509 def __iter__(self):
510 return self._lm.__iter__()
510 return self._lm.__iter__()
511
511
512 def iterkeys(self):
512 def iterkeys(self):
513 return self._lm.iterkeys()
513 return self._lm.iterkeys()
514
514
515 def keys(self):
515 def keys(self):
516 return list(self.iterkeys())
516 return list(self.iterkeys())
517
517
518 def filesnotin(self, m2, match=None):
518 def filesnotin(self, m2, match=None):
519 '''Set of files in this manifest that are not in the other'''
519 '''Set of files in this manifest that are not in the other'''
520 if match is not None:
520 if match is not None:
521 match = matchmod.badmatch(match, lambda path, msg: None)
521 match = matchmod.badmatch(match, lambda path, msg: None)
522 sm2 = set(m2.walk(match))
522 sm2 = set(m2.walk(match))
523 return {f for f in self.walk(match) if f not in sm2}
523 return {f for f in self.walk(match) if f not in sm2}
524 return {f for f in self if f not in m2}
524 return {f for f in self if f not in m2}
525
525
526 @propertycache
526 @propertycache
527 def _dirs(self):
527 def _dirs(self):
528 return pathutil.dirs(self)
528 return pathutil.dirs(self)
529
529
530 def dirs(self):
530 def dirs(self):
531 return self._dirs
531 return self._dirs
532
532
533 def hasdir(self, dir):
533 def hasdir(self, dir):
534 return dir in self._dirs
534 return dir in self._dirs
535
535
536 def _filesfastpath(self, match):
536 def _filesfastpath(self, match):
537 """Checks whether we can correctly and quickly iterate over matcher
537 """Checks whether we can correctly and quickly iterate over matcher
538 files instead of over manifest files."""
538 files instead of over manifest files."""
539 files = match.files()
539 files = match.files()
540 return len(files) < 100 and (
540 return len(files) < 100 and (
541 match.isexact()
541 match.isexact()
542 or (match.prefix() and all(fn in self for fn in files))
542 or (match.prefix() and all(fn in self for fn in files))
543 )
543 )
544
544
545 def walk(self, match):
545 def walk(self, match):
546 """Generates matching file names.
546 """Generates matching file names.
547
547
548 Equivalent to manifest.matches(match).iterkeys(), but without creating
548 Equivalent to manifest.matches(match).iterkeys(), but without creating
549 an entirely new manifest.
549 an entirely new manifest.
550
550
551 It also reports nonexistent files by marking them bad with match.bad().
551 It also reports nonexistent files by marking them bad with match.bad().
552 """
552 """
553 if match.always():
553 if match.always():
554 for f in iter(self):
554 for f in iter(self):
555 yield f
555 yield f
556 return
556 return
557
557
558 fset = set(match.files())
558 fset = set(match.files())
559
559
560 # avoid the entire walk if we're only looking for specific files
560 # avoid the entire walk if we're only looking for specific files
561 if self._filesfastpath(match):
561 if self._filesfastpath(match):
562 for fn in sorted(fset):
562 for fn in sorted(fset):
563 if fn in self:
563 if fn in self:
564 yield fn
564 yield fn
565 return
565 return
566
566
567 for fn in self:
567 for fn in self:
568 if fn in fset:
568 if fn in fset:
569 # specified pattern is the exact name
569 # specified pattern is the exact name
570 fset.remove(fn)
570 fset.remove(fn)
571 if match(fn):
571 if match(fn):
572 yield fn
572 yield fn
573
573
574 # for dirstate.walk, files=[''] means "walk the whole tree".
574 # for dirstate.walk, files=[''] means "walk the whole tree".
575 # follow that here, too
575 # follow that here, too
576 fset.discard(b'')
576 fset.discard(b'')
577
577
578 for fn in sorted(fset):
578 for fn in sorted(fset):
579 if not self.hasdir(fn):
579 if not self.hasdir(fn):
580 match.bad(fn, None)
580 match.bad(fn, None)
581
581
582 def _matches(self, match):
582 def _matches(self, match):
583 '''generate a new manifest filtered by the match argument'''
583 '''generate a new manifest filtered by the match argument'''
584 if match.always():
584 if match.always():
585 return self.copy()
585 return self.copy()
586
586
587 if self._filesfastpath(match):
587 if self._filesfastpath(match):
588 m = manifestdict(self._nodelen)
588 m = manifestdict(self._nodelen)
589 lm = self._lm
589 lm = self._lm
590 for fn in match.files():
590 for fn in match.files():
591 if fn in lm:
591 if fn in lm:
592 m._lm[fn] = lm[fn]
592 m._lm[fn] = lm[fn]
593 return m
593 return m
594
594
595 m = manifestdict(self._nodelen)
595 m = manifestdict(self._nodelen)
596 m._lm = self._lm.filtercopy(match)
596 m._lm = self._lm.filtercopy(match)
597 return m
597 return m
598
598
599 def diff(self, m2, match=None, clean=False):
599 def diff(self, m2, match=None, clean=False):
600 """Finds changes between the current manifest and m2.
600 """Finds changes between the current manifest and m2.
601
601
602 Args:
602 Args:
603 m2: the manifest to which this manifest should be compared.
603 m2: the manifest to which this manifest should be compared.
604 clean: if true, include files unchanged between these manifests
604 clean: if true, include files unchanged between these manifests
605 with a None value in the returned dictionary.
605 with a None value in the returned dictionary.
606
606
607 The result is returned as a dict with filename as key and
607 The result is returned as a dict with filename as key and
608 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
608 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
609 nodeid in the current/other manifest and fl1/fl2 is the flag
609 nodeid in the current/other manifest and fl1/fl2 is the flag
610 in the current/other manifest. Where the file does not exist,
610 in the current/other manifest. Where the file does not exist,
611 the nodeid will be None and the flags will be the empty
611 the nodeid will be None and the flags will be the empty
612 string.
612 string.
613 """
613 """
614 if match:
614 if match:
615 m1 = self._matches(match)
615 m1 = self._matches(match)
616 m2 = m2._matches(match)
616 m2 = m2._matches(match)
617 return m1.diff(m2, clean=clean)
617 return m1.diff(m2, clean=clean)
618 return self._lm.diff(m2._lm, clean)
618 return self._lm.diff(m2._lm, clean)
619
619
620 def setflag(self, key, flag):
620 def setflag(self, key, flag):
621 if flag not in _manifestflags:
621 if flag not in _manifestflags:
622 raise TypeError(b"Invalid manifest flag set.")
622 raise TypeError(b"Invalid manifest flag set.")
623 self._lm[key] = self[key], flag
623 self._lm[key] = self[key], flag
624
624
625 def get(self, key, default=None):
625 def get(self, key, default=None):
626 try:
626 try:
627 return self._lm[key][0]
627 return self._lm[key][0]
628 except KeyError:
628 except KeyError:
629 return default
629 return default
630
630
631 def flags(self, key):
631 def flags(self, key):
632 try:
632 try:
633 return self._lm[key][1]
633 return self._lm[key][1]
634 except KeyError:
634 except KeyError:
635 return b''
635 return b''
636
636
637 def copy(self):
637 def copy(self):
638 c = manifestdict(self._nodelen)
638 c = manifestdict(self._nodelen)
639 c._lm = self._lm.copy()
639 c._lm = self._lm.copy()
640 return c
640 return c
641
641
642 def items(self):
642 def items(self):
643 return (x[:2] for x in self._lm.iterentries())
643 return (x[:2] for x in self._lm.iterentries())
644
644
645 def iteritems(self):
645 def iteritems(self):
646 return (x[:2] for x in self._lm.iterentries())
646 return (x[:2] for x in self._lm.iterentries())
647
647
648 def iterentries(self):
648 def iterentries(self):
649 return self._lm.iterentries()
649 return self._lm.iterentries()
650
650
651 def text(self):
651 def text(self):
652 # most likely uses native version
652 # most likely uses native version
653 return self._lm.text()
653 return self._lm.text()
654
654
655 def fastdelta(self, base, changes):
655 def fastdelta(self, base, changes):
656 """Given a base manifest text as a bytearray and a list of changes
656 """Given a base manifest text as a bytearray and a list of changes
657 relative to that text, compute a delta that can be used by revlog.
657 relative to that text, compute a delta that can be used by revlog.
658 """
658 """
659 delta = []
659 delta = []
660 dstart = None
660 dstart = None
661 dend = None
661 dend = None
662 dline = [b""]
662 dline = [b""]
663 start = 0
663 start = 0
664 # zero copy representation of base as a buffer
664 # zero copy representation of base as a buffer
665 addbuf = util.buffer(base)
665 addbuf = util.buffer(base)
666
666
667 changes = list(changes)
667 changes = list(changes)
668 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
668 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
669 # start with a readonly loop that finds the offset of
669 # start with a readonly loop that finds the offset of
670 # each line and creates the deltas
670 # each line and creates the deltas
671 for f, todelete in changes:
671 for f, todelete in changes:
672 # bs will either be the index of the item or the insert point
672 # bs will either be the index of the item or the insert point
673 start, end = _msearch(addbuf, f, start)
673 start, end = _msearch(addbuf, f, start)
674 if not todelete:
674 if not todelete:
675 h, fl = self._lm[f]
675 h, fl = self._lm[f]
676 l = b"%s\0%s%s\n" % (f, hex(h), fl)
676 l = b"%s\0%s%s\n" % (f, hex(h), fl)
677 else:
677 else:
678 if start == end:
678 if start == end:
679 # item we want to delete was not found, error out
679 # item we want to delete was not found, error out
680 raise AssertionError(
680 raise AssertionError(
681 _(b"failed to remove %s from manifest") % f
681 _(b"failed to remove %s from manifest") % f
682 )
682 )
683 l = b""
683 l = b""
684 if dstart is not None and dstart <= start and dend >= start:
684 if dstart is not None and dstart <= start and dend >= start:
685 if dend < end:
685 if dend < end:
686 dend = end
686 dend = end
687 if l:
687 if l:
688 dline.append(l)
688 dline.append(l)
689 else:
689 else:
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 dstart = start
692 dstart = start
693 dend = end
693 dend = end
694 dline = [l]
694 dline = [l]
695
695
696 if dstart is not None:
696 if dstart is not None:
697 delta.append([dstart, dend, b"".join(dline)])
697 delta.append([dstart, dend, b"".join(dline)])
698 # apply the delta to the base, and get a delta for addrevision
698 # apply the delta to the base, and get a delta for addrevision
699 deltatext, arraytext = _addlistdelta(base, delta)
699 deltatext, arraytext = _addlistdelta(base, delta)
700 else:
700 else:
701 # For large changes, it's much cheaper to just build the text and
701 # For large changes, it's much cheaper to just build the text and
702 # diff it.
702 # diff it.
703 arraytext = bytearray(self.text())
703 arraytext = bytearray(self.text())
704 deltatext = mdiff.textdiff(
704 deltatext = mdiff.textdiff(
705 util.buffer(base), util.buffer(arraytext)
705 util.buffer(base), util.buffer(arraytext)
706 )
706 )
707
707
708 return arraytext, deltatext
708 return arraytext, deltatext
709
709
710
710
711 def _msearch(m, s, lo=0, hi=None):
711 def _msearch(m, s, lo=0, hi=None):
712 """return a tuple (start, end) that says where to find s within m.
712 """return a tuple (start, end) that says where to find s within m.
713
713
714 If the string is found m[start:end] are the line containing
714 If the string is found m[start:end] are the line containing
715 that string. If start == end the string was not found and
715 that string. If start == end the string was not found and
716 they indicate the proper sorted insertion point.
716 they indicate the proper sorted insertion point.
717
717
718 m should be a buffer, a memoryview or a byte string.
718 m should be a buffer, a memoryview or a byte string.
719 s is a byte string"""
719 s is a byte string"""
720
720
721 def advance(i, c):
721 def advance(i, c):
722 while i < lenm and m[i : i + 1] != c:
722 while i < lenm and m[i : i + 1] != c:
723 i += 1
723 i += 1
724 return i
724 return i
725
725
726 if not s:
726 if not s:
727 return (lo, lo)
727 return (lo, lo)
728 lenm = len(m)
728 lenm = len(m)
729 if not hi:
729 if not hi:
730 hi = lenm
730 hi = lenm
731 while lo < hi:
731 while lo < hi:
732 mid = (lo + hi) // 2
732 mid = (lo + hi) // 2
733 start = mid
733 start = mid
734 while start > 0 and m[start - 1 : start] != b'\n':
734 while start > 0 and m[start - 1 : start] != b'\n':
735 start -= 1
735 start -= 1
736 end = advance(start, b'\0')
736 end = advance(start, b'\0')
737 if bytes(m[start:end]) < s:
737 if bytes(m[start:end]) < s:
738 # we know that after the null there are 40 bytes of sha1
738 # we know that after the null there are 40 bytes of sha1
739 # this translates to the bisect lo = mid + 1
739 # this translates to the bisect lo = mid + 1
740 lo = advance(end + 40, b'\n') + 1
740 lo = advance(end + 40, b'\n') + 1
741 else:
741 else:
742 # this translates to the bisect hi = mid
742 # this translates to the bisect hi = mid
743 hi = start
743 hi = start
744 end = advance(lo, b'\0')
744 end = advance(lo, b'\0')
745 found = m[lo:end]
745 found = m[lo:end]
746 if s == found:
746 if s == found:
747 # we know that after the null there are 40 bytes of sha1
747 # we know that after the null there are 40 bytes of sha1
748 end = advance(end + 40, b'\n')
748 end = advance(end + 40, b'\n')
749 return (lo, end + 1)
749 return (lo, end + 1)
750 else:
750 else:
751 return (lo, lo)
751 return (lo, lo)
752
752
753
753
754 def _checkforbidden(l):
754 def _checkforbidden(l):
755 """Check filenames for illegal characters."""
755 """Check filenames for illegal characters."""
756 for f in l:
756 for f in l:
757 if b'\n' in f or b'\r' in f:
757 if b'\n' in f or b'\r' in f:
758 raise error.StorageError(
758 raise error.StorageError(
759 _(b"'\\n' and '\\r' disallowed in filenames: %r")
759 _(b"'\\n' and '\\r' disallowed in filenames: %r")
760 % pycompat.bytestr(f)
760 % pycompat.bytestr(f)
761 )
761 )
762
762
763
763
764 # apply the changes collected during the bisect loop to our addlist
764 # apply the changes collected during the bisect loop to our addlist
765 # return a delta suitable for addrevision
765 # return a delta suitable for addrevision
766 def _addlistdelta(addlist, x):
766 def _addlistdelta(addlist, x):
767 # for large addlist arrays, building a new array is cheaper
767 # for large addlist arrays, building a new array is cheaper
768 # than repeatedly modifying the existing one
768 # than repeatedly modifying the existing one
769 currentposition = 0
769 currentposition = 0
770 newaddlist = bytearray()
770 newaddlist = bytearray()
771
771
772 for start, end, content in x:
772 for start, end, content in x:
773 newaddlist += addlist[currentposition:start]
773 newaddlist += addlist[currentposition:start]
774 if content:
774 if content:
775 newaddlist += bytearray(content)
775 newaddlist += bytearray(content)
776
776
777 currentposition = end
777 currentposition = end
778
778
779 newaddlist += addlist[currentposition:]
779 newaddlist += addlist[currentposition:]
780
780
781 deltatext = b"".join(
781 deltatext = b"".join(
782 struct.pack(b">lll", start, end, len(content)) + content
782 struct.pack(b">lll", start, end, len(content)) + content
783 for start, end, content in x
783 for start, end, content in x
784 )
784 )
785 return deltatext, newaddlist
785 return deltatext, newaddlist
786
786
787
787
788 def _splittopdir(f):
788 def _splittopdir(f):
789 if b'/' in f:
789 if b'/' in f:
790 dir, subpath = f.split(b'/', 1)
790 dir, subpath = f.split(b'/', 1)
791 return dir + b'/', subpath
791 return dir + b'/', subpath
792 else:
792 else:
793 return b'', f
793 return b'', f
794
794
795
795
796 _noop = lambda s: None
796 _noop = lambda s: None
797
797
798
798
799 @interfaceutil.implementer(repository.imanifestdict)
799 @interfaceutil.implementer(repository.imanifestdict)
800 class treemanifest(object):
800 class treemanifest(object):
801 def __init__(self, nodeconstants, dir=b'', text=b''):
801 def __init__(self, nodeconstants, dir=b'', text=b''):
802 self._dir = dir
802 self._dir = dir
803 self.nodeconstants = nodeconstants
803 self.nodeconstants = nodeconstants
804 self._node = self.nodeconstants.nullid
804 self._node = self.nodeconstants.nullid
805 self._nodelen = self.nodeconstants.nodelen
805 self._nodelen = self.nodeconstants.nodelen
806 self._loadfunc = _noop
806 self._loadfunc = _noop
807 self._copyfunc = _noop
807 self._copyfunc = _noop
808 self._dirty = False
808 self._dirty = False
809 self._dirs = {}
809 self._dirs = {}
810 self._lazydirs = {}
810 self._lazydirs = {}
811 # Using _lazymanifest here is a little slower than plain old dicts
811 # Using _lazymanifest here is a little slower than plain old dicts
812 self._files = {}
812 self._files = {}
813 self._flags = {}
813 self._flags = {}
814 if text:
814 if text:
815
815
816 def readsubtree(subdir, subm):
816 def readsubtree(subdir, subm):
817 raise AssertionError(
817 raise AssertionError(
818 b'treemanifest constructor only accepts flat manifests'
818 b'treemanifest constructor only accepts flat manifests'
819 )
819 )
820
820
821 self.parse(text, readsubtree)
821 self.parse(text, readsubtree)
822 self._dirty = True # Mark flat manifest dirty after parsing
822 self._dirty = True # Mark flat manifest dirty after parsing
823
823
824 def _subpath(self, path):
824 def _subpath(self, path):
825 return self._dir + path
825 return self._dir + path
826
826
827 def _loadalllazy(self):
827 def _loadalllazy(self):
828 selfdirs = self._dirs
828 selfdirs = self._dirs
829 subpath = self._subpath
829 subpath = self._subpath
830 for d, (node, readsubtree, docopy) in pycompat.iteritems(
830 for d, (node, readsubtree, docopy) in pycompat.iteritems(
831 self._lazydirs
831 self._lazydirs
832 ):
832 ):
833 if docopy:
833 if docopy:
834 selfdirs[d] = readsubtree(subpath(d), node).copy()
834 selfdirs[d] = readsubtree(subpath(d), node).copy()
835 else:
835 else:
836 selfdirs[d] = readsubtree(subpath(d), node)
836 selfdirs[d] = readsubtree(subpath(d), node)
837 self._lazydirs = {}
837 self._lazydirs = {}
838
838
839 def _loadlazy(self, d):
839 def _loadlazy(self, d):
840 v = self._lazydirs.get(d)
840 v = self._lazydirs.get(d)
841 if v:
841 if v:
842 node, readsubtree, docopy = v
842 node, readsubtree, docopy = v
843 if docopy:
843 if docopy:
844 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
844 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
845 else:
845 else:
846 self._dirs[d] = readsubtree(self._subpath(d), node)
846 self._dirs[d] = readsubtree(self._subpath(d), node)
847 del self._lazydirs[d]
847 del self._lazydirs[d]
848
848
849 def _loadchildrensetlazy(self, visit):
849 def _loadchildrensetlazy(self, visit):
850 if not visit:
850 if not visit:
851 return None
851 return None
852 if visit == b'all' or visit == b'this':
852 if visit == b'all' or visit == b'this':
853 self._loadalllazy()
853 self._loadalllazy()
854 return None
854 return None
855
855
856 loadlazy = self._loadlazy
856 loadlazy = self._loadlazy
857 for k in visit:
857 for k in visit:
858 loadlazy(k + b'/')
858 loadlazy(k + b'/')
859 return visit
859 return visit
860
860
861 def _loaddifflazy(self, t1, t2):
861 def _loaddifflazy(self, t1, t2):
862 """load items in t1 and t2 if they're needed for diffing.
862 """load items in t1 and t2 if they're needed for diffing.
863
863
864 The criteria currently is:
864 The criteria currently is:
865 - if it's not present in _lazydirs in either t1 or t2, load it in the
865 - if it's not present in _lazydirs in either t1 or t2, load it in the
866 other (it may already be loaded or it may not exist, doesn't matter)
866 other (it may already be loaded or it may not exist, doesn't matter)
867 - if it's present in _lazydirs in both, compare the nodeid; if it
867 - if it's present in _lazydirs in both, compare the nodeid; if it
868 differs, load it in both
868 differs, load it in both
869 """
869 """
870 toloadlazy = []
870 toloadlazy = []
871 for d, v1 in pycompat.iteritems(t1._lazydirs):
871 for d, v1 in pycompat.iteritems(t1._lazydirs):
872 v2 = t2._lazydirs.get(d)
872 v2 = t2._lazydirs.get(d)
873 if not v2 or v2[0] != v1[0]:
873 if not v2 or v2[0] != v1[0]:
874 toloadlazy.append(d)
874 toloadlazy.append(d)
875 for d, v1 in pycompat.iteritems(t2._lazydirs):
875 for d, v1 in pycompat.iteritems(t2._lazydirs):
876 if d not in t1._lazydirs:
876 if d not in t1._lazydirs:
877 toloadlazy.append(d)
877 toloadlazy.append(d)
878
878
879 for d in toloadlazy:
879 for d in toloadlazy:
880 t1._loadlazy(d)
880 t1._loadlazy(d)
881 t2._loadlazy(d)
881 t2._loadlazy(d)
882
882
883 def __len__(self):
883 def __len__(self):
884 self._load()
884 self._load()
885 size = len(self._files)
885 size = len(self._files)
886 self._loadalllazy()
886 self._loadalllazy()
887 for m in self._dirs.values():
887 for m in self._dirs.values():
888 size += m.__len__()
888 size += m.__len__()
889 return size
889 return size
890
890
891 def __nonzero__(self):
891 def __nonzero__(self):
892 # Faster than "__len() != 0" since it avoids loading sub-manifests
892 # Faster than "__len() != 0" since it avoids loading sub-manifests
893 return not self._isempty()
893 return not self._isempty()
894
894
895 __bool__ = __nonzero__
895 __bool__ = __nonzero__
896
896
897 def _isempty(self):
897 def _isempty(self):
898 self._load() # for consistency; already loaded by all callers
898 self._load() # for consistency; already loaded by all callers
899 # See if we can skip loading everything.
899 # See if we can skip loading everything.
900 if self._files or (
900 if self._files or (
901 self._dirs and any(not m._isempty() for m in self._dirs.values())
901 self._dirs and any(not m._isempty() for m in self._dirs.values())
902 ):
902 ):
903 return False
903 return False
904 self._loadalllazy()
904 self._loadalllazy()
905 return not self._dirs or all(m._isempty() for m in self._dirs.values())
905 return not self._dirs or all(m._isempty() for m in self._dirs.values())
906
906
907 @encoding.strmethod
907 @encoding.strmethod
908 def __repr__(self):
908 def __repr__(self):
909 return (
909 return (
910 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
910 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
911 % (
911 % (
912 self._dir,
912 self._dir,
913 hex(self._node),
913 hex(self._node),
914 bool(self._loadfunc is _noop),
914 bool(self._loadfunc is _noop),
915 self._dirty,
915 self._dirty,
916 id(self),
916 id(self),
917 )
917 )
918 )
918 )
919
919
920 def dir(self):
920 def dir(self):
921 """The directory that this tree manifest represents, including a
921 """The directory that this tree manifest represents, including a
922 trailing '/'. Empty string for the repo root directory."""
922 trailing '/'. Empty string for the repo root directory."""
923 return self._dir
923 return self._dir
924
924
925 def node(self):
925 def node(self):
926 """This node of this instance. nullid for unsaved instances. Should
926 """This node of this instance. nullid for unsaved instances. Should
927 be updated when the instance is read or written from a revlog.
927 be updated when the instance is read or written from a revlog.
928 """
928 """
929 assert not self._dirty
929 assert not self._dirty
930 return self._node
930 return self._node
931
931
932 def setnode(self, node):
932 def setnode(self, node):
933 self._node = node
933 self._node = node
934 self._dirty = False
934 self._dirty = False
935
935
936 def iterentries(self):
936 def iterentries(self):
937 self._load()
937 self._load()
938 self._loadalllazy()
938 self._loadalllazy()
939 for p, n in sorted(
939 for p, n in sorted(
940 itertools.chain(self._dirs.items(), self._files.items())
940 itertools.chain(self._dirs.items(), self._files.items())
941 ):
941 ):
942 if p in self._files:
942 if p in self._files:
943 yield self._subpath(p), n, self._flags.get(p, b'')
943 yield self._subpath(p), n, self._flags.get(p, b'')
944 else:
944 else:
945 for x in n.iterentries():
945 for x in n.iterentries():
946 yield x
946 yield x
947
947
948 def items(self):
948 def items(self):
949 self._load()
949 self._load()
950 self._loadalllazy()
950 self._loadalllazy()
951 for p, n in sorted(
951 for p, n in sorted(
952 itertools.chain(self._dirs.items(), self._files.items())
952 itertools.chain(self._dirs.items(), self._files.items())
953 ):
953 ):
954 if p in self._files:
954 if p in self._files:
955 yield self._subpath(p), n
955 yield self._subpath(p), n
956 else:
956 else:
957 for f, sn in pycompat.iteritems(n):
957 for f, sn in pycompat.iteritems(n):
958 yield f, sn
958 yield f, sn
959
959
960 iteritems = items
960 iteritems = items
961
961
962 def iterkeys(self):
962 def iterkeys(self):
963 self._load()
963 self._load()
964 self._loadalllazy()
964 self._loadalllazy()
965 for p in sorted(itertools.chain(self._dirs, self._files)):
965 for p in sorted(itertools.chain(self._dirs, self._files)):
966 if p in self._files:
966 if p in self._files:
967 yield self._subpath(p)
967 yield self._subpath(p)
968 else:
968 else:
969 for f in self._dirs[p]:
969 for f in self._dirs[p]:
970 yield f
970 yield f
971
971
972 def keys(self):
972 def keys(self):
973 return list(self.iterkeys())
973 return list(self.iterkeys())
974
974
975 def __iter__(self):
975 def __iter__(self):
976 return self.iterkeys()
976 return self.iterkeys()
977
977
978 def __contains__(self, f):
978 def __contains__(self, f):
979 if f is None:
979 if f is None:
980 return False
980 return False
981 self._load()
981 self._load()
982 dir, subpath = _splittopdir(f)
982 dir, subpath = _splittopdir(f)
983 if dir:
983 if dir:
984 self._loadlazy(dir)
984 self._loadlazy(dir)
985
985
986 if dir not in self._dirs:
986 if dir not in self._dirs:
987 return False
987 return False
988
988
989 return self._dirs[dir].__contains__(subpath)
989 return self._dirs[dir].__contains__(subpath)
990 else:
990 else:
991 return f in self._files
991 return f in self._files
992
992
993 def get(self, f, default=None):
993 def get(self, f, default=None):
994 self._load()
994 self._load()
995 dir, subpath = _splittopdir(f)
995 dir, subpath = _splittopdir(f)
996 if dir:
996 if dir:
997 self._loadlazy(dir)
997 self._loadlazy(dir)
998
998
999 if dir not in self._dirs:
999 if dir not in self._dirs:
1000 return default
1000 return default
1001 return self._dirs[dir].get(subpath, default)
1001 return self._dirs[dir].get(subpath, default)
1002 else:
1002 else:
1003 return self._files.get(f, default)
1003 return self._files.get(f, default)
1004
1004
1005 def __getitem__(self, f):
1005 def __getitem__(self, f):
1006 self._load()
1006 self._load()
1007 dir, subpath = _splittopdir(f)
1007 dir, subpath = _splittopdir(f)
1008 if dir:
1008 if dir:
1009 self._loadlazy(dir)
1009 self._loadlazy(dir)
1010
1010
1011 return self._dirs[dir].__getitem__(subpath)
1011 return self._dirs[dir].__getitem__(subpath)
1012 else:
1012 else:
1013 return self._files[f]
1013 return self._files[f]
1014
1014
1015 def flags(self, f):
1015 def flags(self, f):
1016 self._load()
1016 self._load()
1017 dir, subpath = _splittopdir(f)
1017 dir, subpath = _splittopdir(f)
1018 if dir:
1018 if dir:
1019 self._loadlazy(dir)
1019 self._loadlazy(dir)
1020
1020
1021 if dir not in self._dirs:
1021 if dir not in self._dirs:
1022 return b''
1022 return b''
1023 return self._dirs[dir].flags(subpath)
1023 return self._dirs[dir].flags(subpath)
1024 else:
1024 else:
1025 if f in self._lazydirs or f in self._dirs:
1025 if f in self._lazydirs or f in self._dirs:
1026 return b''
1026 return b''
1027 return self._flags.get(f, b'')
1027 return self._flags.get(f, b'')
1028
1028
1029 def find(self, f):
1029 def find(self, f):
1030 self._load()
1030 self._load()
1031 dir, subpath = _splittopdir(f)
1031 dir, subpath = _splittopdir(f)
1032 if dir:
1032 if dir:
1033 self._loadlazy(dir)
1033 self._loadlazy(dir)
1034
1034
1035 return self._dirs[dir].find(subpath)
1035 return self._dirs[dir].find(subpath)
1036 else:
1036 else:
1037 return self._files[f], self._flags.get(f, b'')
1037 return self._files[f], self._flags.get(f, b'')
1038
1038
1039 def __delitem__(self, f):
1039 def __delitem__(self, f):
1040 self._load()
1040 self._load()
1041 dir, subpath = _splittopdir(f)
1041 dir, subpath = _splittopdir(f)
1042 if dir:
1042 if dir:
1043 self._loadlazy(dir)
1043 self._loadlazy(dir)
1044
1044
1045 self._dirs[dir].__delitem__(subpath)
1045 self._dirs[dir].__delitem__(subpath)
1046 # If the directory is now empty, remove it
1046 # If the directory is now empty, remove it
1047 if self._dirs[dir]._isempty():
1047 if self._dirs[dir]._isempty():
1048 del self._dirs[dir]
1048 del self._dirs[dir]
1049 else:
1049 else:
1050 del self._files[f]
1050 del self._files[f]
1051 if f in self._flags:
1051 if f in self._flags:
1052 del self._flags[f]
1052 del self._flags[f]
1053 self._dirty = True
1053 self._dirty = True
1054
1054
1055 def __setitem__(self, f, n):
1055 def __setitem__(self, f, n):
1056 assert n is not None
1056 assert n is not None
1057 self._load()
1057 self._load()
1058 dir, subpath = _splittopdir(f)
1058 dir, subpath = _splittopdir(f)
1059 if dir:
1059 if dir:
1060 self._loadlazy(dir)
1060 self._loadlazy(dir)
1061 if dir not in self._dirs:
1061 if dir not in self._dirs:
1062 self._dirs[dir] = treemanifest(
1062 self._dirs[dir] = treemanifest(
1063 self.nodeconstants, self._subpath(dir)
1063 self.nodeconstants, self._subpath(dir)
1064 )
1064 )
1065 self._dirs[dir].__setitem__(subpath, n)
1065 self._dirs[dir].__setitem__(subpath, n)
1066 else:
1066 else:
1067 # manifest nodes are either 20 bytes or 32 bytes,
1067 # manifest nodes are either 20 bytes or 32 bytes,
1068 # depending on the hash in use. Assert this as historically
1068 # depending on the hash in use. Assert this as historically
1069 # sometimes extra bytes were added.
1069 # sometimes extra bytes were added.
1070 assert len(n) in (20, 32)
1070 assert len(n) in (20, 32)
1071 self._files[f] = n
1071 self._files[f] = n
1072 self._dirty = True
1072 self._dirty = True
1073
1073
1074 def _load(self):
1074 def _load(self):
1075 if self._loadfunc is not _noop:
1075 if self._loadfunc is not _noop:
1076 lf, self._loadfunc = self._loadfunc, _noop
1076 lf, self._loadfunc = self._loadfunc, _noop
1077 lf(self)
1077 lf(self)
1078 elif self._copyfunc is not _noop:
1078 elif self._copyfunc is not _noop:
1079 cf, self._copyfunc = self._copyfunc, _noop
1079 cf, self._copyfunc = self._copyfunc, _noop
1080 cf(self)
1080 cf(self)
1081
1081
1082 def setflag(self, f, flags):
1082 def setflag(self, f, flags):
1083 """Set the flags (symlink, executable) for path f."""
1083 """Set the flags (symlink, executable) for path f."""
1084 if flags not in _manifestflags:
1084 if flags not in _manifestflags:
1085 raise TypeError(b"Invalid manifest flag set.")
1085 raise TypeError(b"Invalid manifest flag set.")
1086 self._load()
1086 self._load()
1087 dir, subpath = _splittopdir(f)
1087 dir, subpath = _splittopdir(f)
1088 if dir:
1088 if dir:
1089 self._loadlazy(dir)
1089 self._loadlazy(dir)
1090 if dir not in self._dirs:
1090 if dir not in self._dirs:
1091 self._dirs[dir] = treemanifest(
1091 self._dirs[dir] = treemanifest(
1092 self.nodeconstants, self._subpath(dir)
1092 self.nodeconstants, self._subpath(dir)
1093 )
1093 )
1094 self._dirs[dir].setflag(subpath, flags)
1094 self._dirs[dir].setflag(subpath, flags)
1095 else:
1095 else:
1096 self._flags[f] = flags
1096 self._flags[f] = flags
1097 self._dirty = True
1097 self._dirty = True
1098
1098
1099 def copy(self):
1099 def copy(self):
1100 copy = treemanifest(self.nodeconstants, self._dir)
1100 copy = treemanifest(self.nodeconstants, self._dir)
1101 copy._node = self._node
1101 copy._node = self._node
1102 copy._dirty = self._dirty
1102 copy._dirty = self._dirty
1103 if self._copyfunc is _noop:
1103 if self._copyfunc is _noop:
1104
1104
1105 def _copyfunc(s):
1105 def _copyfunc(s):
1106 self._load()
1106 self._load()
1107 s._lazydirs = {
1107 s._lazydirs = {
1108 d: (n, r, True)
1108 d: (n, r, True)
1109 for d, (n, r, c) in pycompat.iteritems(self._lazydirs)
1109 for d, (n, r, c) in pycompat.iteritems(self._lazydirs)
1110 }
1110 }
1111 sdirs = s._dirs
1111 sdirs = s._dirs
1112 for d, v in pycompat.iteritems(self._dirs):
1112 for d, v in pycompat.iteritems(self._dirs):
1113 sdirs[d] = v.copy()
1113 sdirs[d] = v.copy()
1114 s._files = dict.copy(self._files)
1114 s._files = dict.copy(self._files)
1115 s._flags = dict.copy(self._flags)
1115 s._flags = dict.copy(self._flags)
1116
1116
1117 if self._loadfunc is _noop:
1117 if self._loadfunc is _noop:
1118 _copyfunc(copy)
1118 _copyfunc(copy)
1119 else:
1119 else:
1120 copy._copyfunc = _copyfunc
1120 copy._copyfunc = _copyfunc
1121 else:
1121 else:
1122 copy._copyfunc = self._copyfunc
1122 copy._copyfunc = self._copyfunc
1123 return copy
1123 return copy
1124
1124
1125 def filesnotin(self, m2, match=None):
1125 def filesnotin(self, m2, match=None):
1126 '''Set of files in this manifest that are not in the other'''
1126 '''Set of files in this manifest that are not in the other'''
1127 if match and not match.always():
1127 if match and not match.always():
1128 m1 = self._matches(match)
1128 m1 = self._matches(match)
1129 m2 = m2._matches(match)
1129 m2 = m2._matches(match)
1130 return m1.filesnotin(m2)
1130 return m1.filesnotin(m2)
1131
1131
1132 files = set()
1132 files = set()
1133
1133
1134 def _filesnotin(t1, t2):
1134 def _filesnotin(t1, t2):
1135 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1135 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1136 return
1136 return
1137 t1._load()
1137 t1._load()
1138 t2._load()
1138 t2._load()
1139 self._loaddifflazy(t1, t2)
1139 self._loaddifflazy(t1, t2)
1140 for d, m1 in pycompat.iteritems(t1._dirs):
1140 for d, m1 in pycompat.iteritems(t1._dirs):
1141 if d in t2._dirs:
1141 if d in t2._dirs:
1142 m2 = t2._dirs[d]
1142 m2 = t2._dirs[d]
1143 _filesnotin(m1, m2)
1143 _filesnotin(m1, m2)
1144 else:
1144 else:
1145 files.update(m1.iterkeys())
1145 files.update(m1.iterkeys())
1146
1146
1147 for fn in t1._files:
1147 for fn in t1._files:
1148 if fn not in t2._files:
1148 if fn not in t2._files:
1149 files.add(t1._subpath(fn))
1149 files.add(t1._subpath(fn))
1150
1150
1151 _filesnotin(self, m2)
1151 _filesnotin(self, m2)
1152 return files
1152 return files
1153
1153
1154 @propertycache
1154 @propertycache
1155 def _alldirs(self):
1155 def _alldirs(self):
1156 return pathutil.dirs(self)
1156 return pathutil.dirs(self)
1157
1157
1158 def dirs(self):
1158 def dirs(self):
1159 return self._alldirs
1159 return self._alldirs
1160
1160
1161 def hasdir(self, dir):
1161 def hasdir(self, dir):
1162 self._load()
1162 self._load()
1163 topdir, subdir = _splittopdir(dir)
1163 topdir, subdir = _splittopdir(dir)
1164 if topdir:
1164 if topdir:
1165 self._loadlazy(topdir)
1165 self._loadlazy(topdir)
1166 if topdir in self._dirs:
1166 if topdir in self._dirs:
1167 return self._dirs[topdir].hasdir(subdir)
1167 return self._dirs[topdir].hasdir(subdir)
1168 return False
1168 return False
1169 dirslash = dir + b'/'
1169 dirslash = dir + b'/'
1170 return dirslash in self._dirs or dirslash in self._lazydirs
1170 return dirslash in self._dirs or dirslash in self._lazydirs
1171
1171
1172 def walk(self, match):
1172 def walk(self, match):
1173 """Generates matching file names.
1173 """Generates matching file names.
1174
1174
1175 It also reports nonexistent files by marking them bad with match.bad().
1175 It also reports nonexistent files by marking them bad with match.bad().
1176 """
1176 """
1177 if match.always():
1177 if match.always():
1178 for f in iter(self):
1178 for f in iter(self):
1179 yield f
1179 yield f
1180 return
1180 return
1181
1181
1182 fset = set(match.files())
1182 fset = set(match.files())
1183
1183
1184 for fn in self._walk(match):
1184 for fn in self._walk(match):
1185 if fn in fset:
1185 if fn in fset:
1186 # specified pattern is the exact name
1186 # specified pattern is the exact name
1187 fset.remove(fn)
1187 fset.remove(fn)
1188 yield fn
1188 yield fn
1189
1189
1190 # for dirstate.walk, files=[''] means "walk the whole tree".
1190 # for dirstate.walk, files=[''] means "walk the whole tree".
1191 # follow that here, too
1191 # follow that here, too
1192 fset.discard(b'')
1192 fset.discard(b'')
1193
1193
1194 for fn in sorted(fset):
1194 for fn in sorted(fset):
1195 if not self.hasdir(fn):
1195 if not self.hasdir(fn):
1196 match.bad(fn, None)
1196 match.bad(fn, None)
1197
1197
1198 def _walk(self, match):
1198 def _walk(self, match):
1199 '''Recursively generates matching file names for walk().'''
1199 '''Recursively generates matching file names for walk().'''
1200 visit = match.visitchildrenset(self._dir[:-1])
1200 visit = match.visitchildrenset(self._dir[:-1])
1201 if not visit:
1201 if not visit:
1202 return
1202 return
1203
1203
1204 # yield this dir's files and walk its submanifests
1204 # yield this dir's files and walk its submanifests
1205 self._load()
1205 self._load()
1206 visit = self._loadchildrensetlazy(visit)
1206 visit = self._loadchildrensetlazy(visit)
1207 for p in sorted(list(self._dirs) + list(self._files)):
1207 for p in sorted(list(self._dirs) + list(self._files)):
1208 if p in self._files:
1208 if p in self._files:
1209 fullp = self._subpath(p)
1209 fullp = self._subpath(p)
1210 if match(fullp):
1210 if match(fullp):
1211 yield fullp
1211 yield fullp
1212 else:
1212 else:
1213 if not visit or p[:-1] in visit:
1213 if not visit or p[:-1] in visit:
1214 for f in self._dirs[p]._walk(match):
1214 for f in self._dirs[p]._walk(match):
1215 yield f
1215 yield f
1216
1216
1217 def _matches(self, match):
1217 def _matches(self, match):
1218 """recursively generate a new manifest filtered by the match argument."""
1218 """recursively generate a new manifest filtered by the match argument."""
1219 if match.always():
1219 if match.always():
1220 return self.copy()
1220 return self.copy()
1221 return self._matches_inner(match)
1221 return self._matches_inner(match)
1222
1222
1223 def _matches_inner(self, match):
1223 def _matches_inner(self, match):
1224 if match.always():
1224 if match.always():
1225 return self.copy()
1225 return self.copy()
1226
1226
1227 visit = match.visitchildrenset(self._dir[:-1])
1227 visit = match.visitchildrenset(self._dir[:-1])
1228 if visit == b'all':
1228 if visit == b'all':
1229 return self.copy()
1229 return self.copy()
1230 ret = treemanifest(self.nodeconstants, self._dir)
1230 ret = treemanifest(self.nodeconstants, self._dir)
1231 if not visit:
1231 if not visit:
1232 return ret
1232 return ret
1233
1233
1234 self._load()
1234 self._load()
1235 for fn in self._files:
1235 for fn in self._files:
1236 # While visitchildrenset *usually* lists only subdirs, this is
1236 # While visitchildrenset *usually* lists only subdirs, this is
1237 # actually up to the matcher and may have some files in the set().
1237 # actually up to the matcher and may have some files in the set().
1238 # If visit == 'this', we should obviously look at the files in this
1238 # If visit == 'this', we should obviously look at the files in this
1239 # directory; if visit is a set, and fn is in it, we should inspect
1239 # directory; if visit is a set, and fn is in it, we should inspect
1240 # fn (but no need to inspect things not in the set).
1240 # fn (but no need to inspect things not in the set).
1241 if visit != b'this' and fn not in visit:
1241 if visit != b'this' and fn not in visit:
1242 continue
1242 continue
1243 fullp = self._subpath(fn)
1243 fullp = self._subpath(fn)
1244 # visitchildrenset isn't perfect, we still need to call the regular
1244 # visitchildrenset isn't perfect, we still need to call the regular
1245 # matcher code to further filter results.
1245 # matcher code to further filter results.
1246 if not match(fullp):
1246 if not match(fullp):
1247 continue
1247 continue
1248 ret._files[fn] = self._files[fn]
1248 ret._files[fn] = self._files[fn]
1249 if fn in self._flags:
1249 if fn in self._flags:
1250 ret._flags[fn] = self._flags[fn]
1250 ret._flags[fn] = self._flags[fn]
1251
1251
1252 visit = self._loadchildrensetlazy(visit)
1252 visit = self._loadchildrensetlazy(visit)
1253 for dir, subm in pycompat.iteritems(self._dirs):
1253 for dir, subm in pycompat.iteritems(self._dirs):
1254 if visit and dir[:-1] not in visit:
1254 if visit and dir[:-1] not in visit:
1255 continue
1255 continue
1256 m = subm._matches_inner(match)
1256 m = subm._matches_inner(match)
1257 if not m._isempty():
1257 if not m._isempty():
1258 ret._dirs[dir] = m
1258 ret._dirs[dir] = m
1259
1259
1260 if not ret._isempty():
1260 if not ret._isempty():
1261 ret._dirty = True
1261 ret._dirty = True
1262 return ret
1262 return ret
1263
1263
1264 def fastdelta(self, base, changes):
1264 def fastdelta(self, base, changes):
1265 raise FastdeltaUnavailable()
1265 raise FastdeltaUnavailable()
1266
1266
1267 def diff(self, m2, match=None, clean=False):
1267 def diff(self, m2, match=None, clean=False):
1268 """Finds changes between the current manifest and m2.
1268 """Finds changes between the current manifest and m2.
1269
1269
1270 Args:
1270 Args:
1271 m2: the manifest to which this manifest should be compared.
1271 m2: the manifest to which this manifest should be compared.
1272 clean: if true, include files unchanged between these manifests
1272 clean: if true, include files unchanged between these manifests
1273 with a None value in the returned dictionary.
1273 with a None value in the returned dictionary.
1274
1274
1275 The result is returned as a dict with filename as key and
1275 The result is returned as a dict with filename as key and
1276 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1276 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1277 nodeid in the current/other manifest and fl1/fl2 is the flag
1277 nodeid in the current/other manifest and fl1/fl2 is the flag
1278 in the current/other manifest. Where the file does not exist,
1278 in the current/other manifest. Where the file does not exist,
1279 the nodeid will be None and the flags will be the empty
1279 the nodeid will be None and the flags will be the empty
1280 string.
1280 string.
1281 """
1281 """
1282 if match and not match.always():
1282 if match and not match.always():
1283 m1 = self._matches(match)
1283 m1 = self._matches(match)
1284 m2 = m2._matches(match)
1284 m2 = m2._matches(match)
1285 return m1.diff(m2, clean=clean)
1285 return m1.diff(m2, clean=clean)
1286 result = {}
1286 result = {}
1287 emptytree = treemanifest(self.nodeconstants)
1287 emptytree = treemanifest(self.nodeconstants)
1288
1288
1289 def _iterativediff(t1, t2, stack):
1289 def _iterativediff(t1, t2, stack):
1290 """compares two tree manifests and append new tree-manifests which
1290 """compares two tree manifests and append new tree-manifests which
1291 needs to be compared to stack"""
1291 needs to be compared to stack"""
1292 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1292 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1293 return
1293 return
1294 t1._load()
1294 t1._load()
1295 t2._load()
1295 t2._load()
1296 self._loaddifflazy(t1, t2)
1296 self._loaddifflazy(t1, t2)
1297
1297
1298 for d, m1 in pycompat.iteritems(t1._dirs):
1298 for d, m1 in pycompat.iteritems(t1._dirs):
1299 m2 = t2._dirs.get(d, emptytree)
1299 m2 = t2._dirs.get(d, emptytree)
1300 stack.append((m1, m2))
1300 stack.append((m1, m2))
1301
1301
1302 for d, m2 in pycompat.iteritems(t2._dirs):
1302 for d, m2 in pycompat.iteritems(t2._dirs):
1303 if d not in t1._dirs:
1303 if d not in t1._dirs:
1304 stack.append((emptytree, m2))
1304 stack.append((emptytree, m2))
1305
1305
1306 for fn, n1 in pycompat.iteritems(t1._files):
1306 for fn, n1 in pycompat.iteritems(t1._files):
1307 fl1 = t1._flags.get(fn, b'')
1307 fl1 = t1._flags.get(fn, b'')
1308 n2 = t2._files.get(fn, None)
1308 n2 = t2._files.get(fn, None)
1309 fl2 = t2._flags.get(fn, b'')
1309 fl2 = t2._flags.get(fn, b'')
1310 if n1 != n2 or fl1 != fl2:
1310 if n1 != n2 or fl1 != fl2:
1311 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1311 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1312 elif clean:
1312 elif clean:
1313 result[t1._subpath(fn)] = None
1313 result[t1._subpath(fn)] = None
1314
1314
1315 for fn, n2 in pycompat.iteritems(t2._files):
1315 for fn, n2 in pycompat.iteritems(t2._files):
1316 if fn not in t1._files:
1316 if fn not in t1._files:
1317 fl2 = t2._flags.get(fn, b'')
1317 fl2 = t2._flags.get(fn, b'')
1318 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1318 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1319
1319
1320 stackls = []
1320 stackls = []
1321 _iterativediff(self, m2, stackls)
1321 _iterativediff(self, m2, stackls)
1322 while stackls:
1322 while stackls:
1323 t1, t2 = stackls.pop()
1323 t1, t2 = stackls.pop()
1324 # stackls is populated in the function call
1324 # stackls is populated in the function call
1325 _iterativediff(t1, t2, stackls)
1325 _iterativediff(t1, t2, stackls)
1326 return result
1326 return result
1327
1327
1328 def unmodifiedsince(self, m2):
1328 def unmodifiedsince(self, m2):
1329 return not self._dirty and not m2._dirty and self._node == m2._node
1329 return not self._dirty and not m2._dirty and self._node == m2._node
1330
1330
1331 def parse(self, text, readsubtree):
1331 def parse(self, text, readsubtree):
1332 selflazy = self._lazydirs
1332 selflazy = self._lazydirs
1333 for f, n, fl in _parse(self._nodelen, text):
1333 for f, n, fl in _parse(self._nodelen, text):
1334 if fl == b't':
1334 if fl == b't':
1335 f = f + b'/'
1335 f = f + b'/'
1336 # False below means "doesn't need to be copied" and can use the
1336 # False below means "doesn't need to be copied" and can use the
1337 # cached value from readsubtree directly.
1337 # cached value from readsubtree directly.
1338 selflazy[f] = (n, readsubtree, False)
1338 selflazy[f] = (n, readsubtree, False)
1339 elif b'/' in f:
1339 elif b'/' in f:
1340 # This is a flat manifest, so use __setitem__ and setflag rather
1340 # This is a flat manifest, so use __setitem__ and setflag rather
1341 # than assigning directly to _files and _flags, so we can
1341 # than assigning directly to _files and _flags, so we can
1342 # assign a path in a subdirectory, and to mark dirty (compared
1342 # assign a path in a subdirectory, and to mark dirty (compared
1343 # to nullid).
1343 # to nullid).
1344 self[f] = n
1344 self[f] = n
1345 if fl:
1345 if fl:
1346 self.setflag(f, fl)
1346 self.setflag(f, fl)
1347 else:
1347 else:
1348 # Assigning to _files and _flags avoids marking as dirty,
1348 # Assigning to _files and _flags avoids marking as dirty,
1349 # and should be a little faster.
1349 # and should be a little faster.
1350 self._files[f] = n
1350 self._files[f] = n
1351 if fl:
1351 if fl:
1352 self._flags[f] = fl
1352 self._flags[f] = fl
1353
1353
1354 def text(self):
1354 def text(self):
1355 """Get the full data of this manifest as a bytestring."""
1355 """Get the full data of this manifest as a bytestring."""
1356 self._load()
1356 self._load()
1357 return _text(self.iterentries())
1357 return _text(self.iterentries())
1358
1358
1359 def dirtext(self):
1359 def dirtext(self):
1360 """Get the full data of this directory as a bytestring. Make sure that
1360 """Get the full data of this directory as a bytestring. Make sure that
1361 any submanifests have been written first, so their nodeids are correct.
1361 any submanifests have been written first, so their nodeids are correct.
1362 """
1362 """
1363 self._load()
1363 self._load()
1364 flags = self.flags
1364 flags = self.flags
1365 lazydirs = [
1365 lazydirs = [
1366 (d[:-1], v[0], b't') for d, v in pycompat.iteritems(self._lazydirs)
1366 (d[:-1], v[0], b't') for d, v in pycompat.iteritems(self._lazydirs)
1367 ]
1367 ]
1368 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1368 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1369 files = [(f, self._files[f], flags(f)) for f in self._files]
1369 files = [(f, self._files[f], flags(f)) for f in self._files]
1370 return _text(sorted(dirs + files + lazydirs))
1370 return _text(sorted(dirs + files + lazydirs))
1371
1371
1372 def read(self, gettext, readsubtree):
1372 def read(self, gettext, readsubtree):
1373 def _load_for_read(s):
1373 def _load_for_read(s):
1374 s.parse(gettext(), readsubtree)
1374 s.parse(gettext(), readsubtree)
1375 s._dirty = False
1375 s._dirty = False
1376
1376
1377 self._loadfunc = _load_for_read
1377 self._loadfunc = _load_for_read
1378
1378
1379 def writesubtrees(self, m1, m2, writesubtree, match):
1379 def writesubtrees(self, m1, m2, writesubtree, match):
1380 self._load() # for consistency; should never have any effect here
1380 self._load() # for consistency; should never have any effect here
1381 m1._load()
1381 m1._load()
1382 m2._load()
1382 m2._load()
1383 emptytree = treemanifest(self.nodeconstants)
1383 emptytree = treemanifest(self.nodeconstants)
1384
1384
1385 def getnode(m, d):
1385 def getnode(m, d):
1386 ld = m._lazydirs.get(d)
1386 ld = m._lazydirs.get(d)
1387 if ld:
1387 if ld:
1388 return ld[0]
1388 return ld[0]
1389 return m._dirs.get(d, emptytree)._node
1389 return m._dirs.get(d, emptytree)._node
1390
1390
1391 # let's skip investigating things that `match` says we do not need.
1391 # let's skip investigating things that `match` says we do not need.
1392 visit = match.visitchildrenset(self._dir[:-1])
1392 visit = match.visitchildrenset(self._dir[:-1])
1393 visit = self._loadchildrensetlazy(visit)
1393 visit = self._loadchildrensetlazy(visit)
1394 if visit == b'this' or visit == b'all':
1394 if visit == b'this' or visit == b'all':
1395 visit = None
1395 visit = None
1396 for d, subm in pycompat.iteritems(self._dirs):
1396 for d, subm in pycompat.iteritems(self._dirs):
1397 if visit and d[:-1] not in visit:
1397 if visit and d[:-1] not in visit:
1398 continue
1398 continue
1399 subp1 = getnode(m1, d)
1399 subp1 = getnode(m1, d)
1400 subp2 = getnode(m2, d)
1400 subp2 = getnode(m2, d)
1401 if subp1 == self.nodeconstants.nullid:
1401 if subp1 == self.nodeconstants.nullid:
1402 subp1, subp2 = subp2, subp1
1402 subp1, subp2 = subp2, subp1
1403 writesubtree(subm, subp1, subp2, match)
1403 writesubtree(subm, subp1, subp2, match)
1404
1404
1405 def walksubtrees(self, matcher=None):
1405 def walksubtrees(self, matcher=None):
1406 """Returns an iterator of the subtrees of this manifest, including this
1406 """Returns an iterator of the subtrees of this manifest, including this
1407 manifest itself.
1407 manifest itself.
1408
1408
1409 If `matcher` is provided, it only returns subtrees that match.
1409 If `matcher` is provided, it only returns subtrees that match.
1410 """
1410 """
1411 if matcher and not matcher.visitdir(self._dir[:-1]):
1411 if matcher and not matcher.visitdir(self._dir[:-1]):
1412 return
1412 return
1413 if not matcher or matcher(self._dir[:-1]):
1413 if not matcher or matcher(self._dir[:-1]):
1414 yield self
1414 yield self
1415
1415
1416 self._load()
1416 self._load()
1417 # OPT: use visitchildrenset to avoid loading everything.
1417 # OPT: use visitchildrenset to avoid loading everything.
1418 self._loadalllazy()
1418 self._loadalllazy()
1419 for d, subm in pycompat.iteritems(self._dirs):
1419 for d, subm in pycompat.iteritems(self._dirs):
1420 for subtree in subm.walksubtrees(matcher=matcher):
1420 for subtree in subm.walksubtrees(matcher=matcher):
1421 yield subtree
1421 yield subtree
1422
1422
1423
1423
1424 class manifestfulltextcache(util.lrucachedict):
1424 class manifestfulltextcache(util.lrucachedict):
1425 """File-backed LRU cache for the manifest cache
1425 """File-backed LRU cache for the manifest cache
1426
1426
1427 File consists of entries, up to EOF:
1427 File consists of entries, up to EOF:
1428
1428
1429 - 20 bytes node, 4 bytes length, <length> manifest data
1429 - 20 bytes node, 4 bytes length, <length> manifest data
1430
1430
1431 These are written in reverse cache order (oldest to newest).
1431 These are written in reverse cache order (oldest to newest).
1432
1432
1433 """
1433 """
1434
1434
1435 _file = b'manifestfulltextcache'
1435 _file = b'manifestfulltextcache'
1436
1436
1437 def __init__(self, max):
1437 def __init__(self, max):
1438 super(manifestfulltextcache, self).__init__(max)
1438 super(manifestfulltextcache, self).__init__(max)
1439 self._dirty = False
1439 self._dirty = False
1440 self._read = False
1440 self._read = False
1441 self._opener = None
1441 self._opener = None
1442
1442
1443 def read(self):
1443 def read(self):
1444 if self._read or self._opener is None:
1444 if self._read or self._opener is None:
1445 return
1445 return
1446
1446
1447 try:
1447 try:
1448 with self._opener(self._file) as fp:
1448 with self._opener(self._file) as fp:
1449 set = super(manifestfulltextcache, self).__setitem__
1449 set = super(manifestfulltextcache, self).__setitem__
1450 # ignore trailing data, this is a cache, corruption is skipped
1450 # ignore trailing data, this is a cache, corruption is skipped
1451 while True:
1451 while True:
1452 # TODO do we need to do work here for sha1 portability?
1452 # TODO do we need to do work here for sha1 portability?
1453 node = fp.read(20)
1453 node = fp.read(20)
1454 if len(node) < 20:
1454 if len(node) < 20:
1455 break
1455 break
1456 try:
1456 try:
1457 size = struct.unpack(b'>L', fp.read(4))[0]
1457 size = struct.unpack(b'>L', fp.read(4))[0]
1458 except struct.error:
1458 except struct.error:
1459 break
1459 break
1460 value = bytearray(fp.read(size))
1460 value = bytearray(fp.read(size))
1461 if len(value) != size:
1461 if len(value) != size:
1462 break
1462 break
1463 set(node, value)
1463 set(node, value)
1464 except IOError:
1464 except IOError:
1465 # the file is allowed to be missing
1465 # the file is allowed to be missing
1466 pass
1466 pass
1467
1467
1468 self._read = True
1468 self._read = True
1469 self._dirty = False
1469 self._dirty = False
1470
1470
1471 def write(self):
1471 def write(self):
1472 if not self._dirty or self._opener is None:
1472 if not self._dirty or self._opener is None:
1473 return
1473 return
1474 # rotate backwards to the first used node
1474 # rotate backwards to the first used node
1475 try:
1475 try:
1476 with self._opener(
1476 with self._opener(
1477 self._file, b'w', atomictemp=True, checkambig=True
1477 self._file, b'w', atomictemp=True, checkambig=True
1478 ) as fp:
1478 ) as fp:
1479 node = self._head.prev
1479 node = self._head.prev
1480 while True:
1480 while True:
1481 if node.key in self._cache:
1481 if node.key in self._cache:
1482 fp.write(node.key)
1482 fp.write(node.key)
1483 fp.write(struct.pack(b'>L', len(node.value)))
1483 fp.write(struct.pack(b'>L', len(node.value)))
1484 fp.write(node.value)
1484 fp.write(node.value)
1485 if node is self._head:
1485 if node is self._head:
1486 break
1486 break
1487 node = node.prev
1487 node = node.prev
1488 except IOError:
1488 except IOError:
1489 # We could not write the cache (eg: permission error)
1489 # We could not write the cache (eg: permission error)
1490 # the content can be missing.
1490 # the content can be missing.
1491 #
1491 #
1492 # We could try harder and see if we could recreate a wcache
1492 # We could try harder and see if we could recreate a wcache
1493 # directory were we coudl write too.
1493 # directory were we coudl write too.
1494 #
1494 #
1495 # XXX the error pass silently, having some way to issue an error
1495 # XXX the error pass silently, having some way to issue an error
1496 # log `ui.log` would be nice.
1496 # log `ui.log` would be nice.
1497 pass
1497 pass
1498
1498
1499 def __len__(self):
1499 def __len__(self):
1500 if not self._read:
1500 if not self._read:
1501 self.read()
1501 self.read()
1502 return super(manifestfulltextcache, self).__len__()
1502 return super(manifestfulltextcache, self).__len__()
1503
1503
1504 def __contains__(self, k):
1504 def __contains__(self, k):
1505 if not self._read:
1505 if not self._read:
1506 self.read()
1506 self.read()
1507 return super(manifestfulltextcache, self).__contains__(k)
1507 return super(manifestfulltextcache, self).__contains__(k)
1508
1508
1509 def __iter__(self):
1509 def __iter__(self):
1510 if not self._read:
1510 if not self._read:
1511 self.read()
1511 self.read()
1512 return super(manifestfulltextcache, self).__iter__()
1512 return super(manifestfulltextcache, self).__iter__()
1513
1513
1514 def __getitem__(self, k):
1514 def __getitem__(self, k):
1515 if not self._read:
1515 if not self._read:
1516 self.read()
1516 self.read()
1517 # the cache lru order can change on read
1517 # the cache lru order can change on read
1518 setdirty = self._cache.get(k) is not self._head
1518 setdirty = self._cache.get(k) is not self._head
1519 value = super(manifestfulltextcache, self).__getitem__(k)
1519 value = super(manifestfulltextcache, self).__getitem__(k)
1520 if setdirty:
1520 if setdirty:
1521 self._dirty = True
1521 self._dirty = True
1522 return value
1522 return value
1523
1523
1524 def __setitem__(self, k, v):
1524 def __setitem__(self, k, v):
1525 if not self._read:
1525 if not self._read:
1526 self.read()
1526 self.read()
1527 super(manifestfulltextcache, self).__setitem__(k, v)
1527 super(manifestfulltextcache, self).__setitem__(k, v)
1528 self._dirty = True
1528 self._dirty = True
1529
1529
1530 def __delitem__(self, k):
1530 def __delitem__(self, k):
1531 if not self._read:
1531 if not self._read:
1532 self.read()
1532 self.read()
1533 super(manifestfulltextcache, self).__delitem__(k)
1533 super(manifestfulltextcache, self).__delitem__(k)
1534 self._dirty = True
1534 self._dirty = True
1535
1535
1536 def get(self, k, default=None):
1536 def get(self, k, default=None):
1537 if not self._read:
1537 if not self._read:
1538 self.read()
1538 self.read()
1539 return super(manifestfulltextcache, self).get(k, default=default)
1539 return super(manifestfulltextcache, self).get(k, default=default)
1540
1540
1541 def clear(self, clear_persisted_data=False):
1541 def clear(self, clear_persisted_data=False):
1542 super(manifestfulltextcache, self).clear()
1542 super(manifestfulltextcache, self).clear()
1543 if clear_persisted_data:
1543 if clear_persisted_data:
1544 self._dirty = True
1544 self._dirty = True
1545 self.write()
1545 self.write()
1546 self._read = False
1546 self._read = False
1547
1547
1548
1548
1549 # and upper bound of what we expect from compression
1549 # and upper bound of what we expect from compression
1550 # (real live value seems to be "3")
1550 # (real live value seems to be "3")
1551 MAXCOMPRESSION = 3
1551 MAXCOMPRESSION = 3
1552
1552
1553
1553
1554 class FastdeltaUnavailable(Exception):
1554 class FastdeltaUnavailable(Exception):
1555 """Exception raised when fastdelta isn't usable on a manifest."""
1555 """Exception raised when fastdelta isn't usable on a manifest."""
1556
1556
1557
1557
1558 @interfaceutil.implementer(repository.imanifeststorage)
1558 @interfaceutil.implementer(repository.imanifeststorage)
1559 class manifestrevlog(object):
1559 class manifestrevlog(object):
1560 """A revlog that stores manifest texts. This is responsible for caching the
1560 """A revlog that stores manifest texts. This is responsible for caching the
1561 full-text manifest contents.
1561 full-text manifest contents.
1562 """
1562 """
1563
1563
1564 def __init__(
1564 def __init__(
1565 self,
1565 self,
1566 nodeconstants,
1566 nodeconstants,
1567 opener,
1567 opener,
1568 tree=b'',
1568 tree=b'',
1569 dirlogcache=None,
1569 dirlogcache=None,
1570 indexfile=None,
1570 indexfile=None,
1571 treemanifest=False,
1571 treemanifest=False,
1572 ):
1572 ):
1573 """Constructs a new manifest revlog
1573 """Constructs a new manifest revlog
1574
1574
1575 `indexfile` - used by extensions to have two manifests at once, like
1575 `indexfile` - used by extensions to have two manifests at once, like
1576 when transitioning between flatmanifeset and treemanifests.
1576 when transitioning between flatmanifeset and treemanifests.
1577
1577
1578 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1578 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1579 options can also be used to make this a tree manifest revlog. The opener
1579 options can also be used to make this a tree manifest revlog. The opener
1580 option takes precedence, so if it is set to True, we ignore whatever
1580 option takes precedence, so if it is set to True, we ignore whatever
1581 value is passed in to the constructor.
1581 value is passed in to the constructor.
1582 """
1582 """
1583 self.nodeconstants = nodeconstants
1583 self.nodeconstants = nodeconstants
1584 # During normal operations, we expect to deal with not more than four
1584 # During normal operations, we expect to deal with not more than four
1585 # revs at a time (such as during commit --amend). When rebasing large
1585 # revs at a time (such as during commit --amend). When rebasing large
1586 # stacks of commits, the number can go up, hence the config knob below.
1586 # stacks of commits, the number can go up, hence the config knob below.
1587 cachesize = 4
1587 cachesize = 4
1588 optiontreemanifest = False
1588 optiontreemanifest = False
1589 opts = getattr(opener, 'options', None)
1589 opts = getattr(opener, 'options', None)
1590 if opts is not None:
1590 if opts is not None:
1591 cachesize = opts.get(b'manifestcachesize', cachesize)
1591 cachesize = opts.get(b'manifestcachesize', cachesize)
1592 optiontreemanifest = opts.get(b'treemanifest', False)
1592 optiontreemanifest = opts.get(b'treemanifest', False)
1593
1593
1594 self._treeondisk = optiontreemanifest or treemanifest
1594 self._treeondisk = optiontreemanifest or treemanifest
1595
1595
1596 self._fulltextcache = manifestfulltextcache(cachesize)
1596 self._fulltextcache = manifestfulltextcache(cachesize)
1597
1597
1598 if tree:
1598 if tree:
1599 assert self._treeondisk, b'opts is %r' % opts
1599 assert self._treeondisk, b'opts is %r' % opts
1600
1600
1601 if indexfile is None:
1601 if indexfile is None:
1602 indexfile = b'00manifest.i'
1602 indexfile = b'00manifest.i'
1603 if tree:
1603 if tree:
1604 indexfile = b"meta/" + tree + indexfile
1604 indexfile = b"meta/" + tree + indexfile
1605
1605
1606 self.tree = tree
1606 self.tree = tree
1607
1607
1608 # The dirlogcache is kept on the root manifest log
1608 # The dirlogcache is kept on the root manifest log
1609 if tree:
1609 if tree:
1610 self._dirlogcache = dirlogcache
1610 self._dirlogcache = dirlogcache
1611 else:
1611 else:
1612 self._dirlogcache = {b'': self}
1612 self._dirlogcache = {b'': self}
1613
1613
1614 self._revlog = revlog.revlog(
1614 self._revlog = revlog.revlog(
1615 opener,
1615 opener,
1616 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1616 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1617 indexfile=indexfile,
1617 indexfile=indexfile,
1618 # only root indexfile is cached
1618 # only root indexfile is cached
1619 checkambig=not bool(tree),
1619 checkambig=not bool(tree),
1620 mmaplargeindex=True,
1620 mmaplargeindex=True,
1621 upperboundcomp=MAXCOMPRESSION,
1621 upperboundcomp=MAXCOMPRESSION,
1622 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
1622 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
1623 )
1623 )
1624
1624
1625 self.index = self._revlog.index
1625 self.index = self._revlog.index
1626 self._generaldelta = self._revlog._generaldelta
1626 self._generaldelta = self._revlog._generaldelta
1627
1627
1628 def _setupmanifestcachehooks(self, repo):
1628 def _setupmanifestcachehooks(self, repo):
1629 """Persist the manifestfulltextcache on lock release"""
1629 """Persist the manifestfulltextcache on lock release"""
1630 if not util.safehasattr(repo, b'_wlockref'):
1630 if not util.safehasattr(repo, b'_wlockref'):
1631 return
1631 return
1632
1632
1633 self._fulltextcache._opener = repo.wcachevfs
1633 self._fulltextcache._opener = repo.wcachevfs
1634 if repo._currentlock(repo._wlockref) is None:
1634 if repo._currentlock(repo._wlockref) is None:
1635 return
1635 return
1636
1636
1637 reporef = weakref.ref(repo)
1637 reporef = weakref.ref(repo)
1638 manifestrevlogref = weakref.ref(self)
1638 manifestrevlogref = weakref.ref(self)
1639
1639
1640 def persistmanifestcache(success):
1640 def persistmanifestcache(success):
1641 # Repo is in an unknown state, do not persist.
1641 # Repo is in an unknown state, do not persist.
1642 if not success:
1642 if not success:
1643 return
1643 return
1644
1644
1645 repo = reporef()
1645 repo = reporef()
1646 self = manifestrevlogref()
1646 self = manifestrevlogref()
1647 if repo is None or self is None:
1647 if repo is None or self is None:
1648 return
1648 return
1649 if repo.manifestlog.getstorage(b'') is not self:
1649 if repo.manifestlog.getstorage(b'') is not self:
1650 # there's a different manifest in play now, abort
1650 # there's a different manifest in play now, abort
1651 return
1651 return
1652 self._fulltextcache.write()
1652 self._fulltextcache.write()
1653
1653
1654 repo._afterlock(persistmanifestcache)
1654 repo._afterlock(persistmanifestcache)
1655
1655
1656 @property
1656 @property
1657 def fulltextcache(self):
1657 def fulltextcache(self):
1658 return self._fulltextcache
1658 return self._fulltextcache
1659
1659
1660 def clearcaches(self, clear_persisted_data=False):
1660 def clearcaches(self, clear_persisted_data=False):
1661 self._revlog.clearcaches()
1661 self._revlog.clearcaches()
1662 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1662 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1663 self._dirlogcache = {self.tree: self}
1663 self._dirlogcache = {self.tree: self}
1664
1664
1665 def dirlog(self, d):
1665 def dirlog(self, d):
1666 if d:
1666 if d:
1667 assert self._treeondisk
1667 assert self._treeondisk
1668 if d not in self._dirlogcache:
1668 if d not in self._dirlogcache:
1669 mfrevlog = manifestrevlog(
1669 mfrevlog = manifestrevlog(
1670 self.nodeconstants,
1670 self.nodeconstants,
1671 self.opener,
1671 self.opener,
1672 d,
1672 d,
1673 self._dirlogcache,
1673 self._dirlogcache,
1674 treemanifest=self._treeondisk,
1674 treemanifest=self._treeondisk,
1675 )
1675 )
1676 self._dirlogcache[d] = mfrevlog
1676 self._dirlogcache[d] = mfrevlog
1677 return self._dirlogcache[d]
1677 return self._dirlogcache[d]
1678
1678
1679 def add(
1679 def add(
1680 self,
1680 self,
1681 m,
1681 m,
1682 transaction,
1682 transaction,
1683 link,
1683 link,
1684 p1,
1684 p1,
1685 p2,
1685 p2,
1686 added,
1686 added,
1687 removed,
1687 removed,
1688 readtree=None,
1688 readtree=None,
1689 match=None,
1689 match=None,
1690 ):
1690 ):
1691 """add some manifest entry in to the manifest log
1691 """add some manifest entry in to the manifest log
1692
1692
1693 input:
1693 input:
1694
1694
1695 m: the manifest dict we want to store
1695 m: the manifest dict we want to store
1696 transaction: the open transaction
1696 transaction: the open transaction
1697 p1: manifest-node of p1
1697 p1: manifest-node of p1
1698 p2: manifest-node of p2
1698 p2: manifest-node of p2
1699 added: file added/changed compared to parent
1699 added: file added/changed compared to parent
1700 removed: file removed compared to parent
1700 removed: file removed compared to parent
1701
1701
1702 tree manifest input:
1702 tree manifest input:
1703
1703
1704 readtree: a function to read a subtree
1704 readtree: a function to read a subtree
1705 match: a filematcher for the subpart of the tree manifest
1705 match: a filematcher for the subpart of the tree manifest
1706 """
1706 """
1707 try:
1707 try:
1708 if p1 not in self.fulltextcache:
1708 if p1 not in self.fulltextcache:
1709 raise FastdeltaUnavailable()
1709 raise FastdeltaUnavailable()
1710 # If our first parent is in the manifest cache, we can
1710 # If our first parent is in the manifest cache, we can
1711 # compute a delta here using properties we know about the
1711 # compute a delta here using properties we know about the
1712 # manifest up-front, which may save time later for the
1712 # manifest up-front, which may save time later for the
1713 # revlog layer.
1713 # revlog layer.
1714
1714
1715 _checkforbidden(added)
1715 _checkforbidden(added)
1716 # combine the changed lists into one sorted iterator
1716 # combine the changed lists into one sorted iterator
1717 work = heapq.merge(
1717 work = heapq.merge(
1718 [(x, False) for x in sorted(added)],
1718 [(x, False) for x in sorted(added)],
1719 [(x, True) for x in sorted(removed)],
1719 [(x, True) for x in sorted(removed)],
1720 )
1720 )
1721
1721
1722 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1722 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1723 cachedelta = self._revlog.rev(p1), deltatext
1723 cachedelta = self._revlog.rev(p1), deltatext
1724 text = util.buffer(arraytext)
1724 text = util.buffer(arraytext)
1725 rev = self._revlog.addrevision(
1725 rev = self._revlog.addrevision(
1726 text, transaction, link, p1, p2, cachedelta
1726 text, transaction, link, p1, p2, cachedelta
1727 )
1727 )
1728 n = self._revlog.node(rev)
1728 n = self._revlog.node(rev)
1729 except FastdeltaUnavailable:
1729 except FastdeltaUnavailable:
1730 # The first parent manifest isn't already loaded or the
1730 # The first parent manifest isn't already loaded or the
1731 # manifest implementation doesn't support fastdelta, so
1731 # manifest implementation doesn't support fastdelta, so
1732 # we'll just encode a fulltext of the manifest and pass
1732 # we'll just encode a fulltext of the manifest and pass
1733 # that through to the revlog layer, and let it handle the
1733 # that through to the revlog layer, and let it handle the
1734 # delta process.
1734 # delta process.
1735 if self._treeondisk:
1735 if self._treeondisk:
1736 assert readtree, b"readtree must be set for treemanifest writes"
1736 assert readtree, b"readtree must be set for treemanifest writes"
1737 assert match, b"match must be specified for treemanifest writes"
1737 assert match, b"match must be specified for treemanifest writes"
1738 m1 = readtree(self.tree, p1)
1738 m1 = readtree(self.tree, p1)
1739 m2 = readtree(self.tree, p2)
1739 m2 = readtree(self.tree, p2)
1740 n = self._addtree(
1740 n = self._addtree(
1741 m, transaction, link, m1, m2, readtree, match=match
1741 m, transaction, link, m1, m2, readtree, match=match
1742 )
1742 )
1743 arraytext = None
1743 arraytext = None
1744 else:
1744 else:
1745 text = m.text()
1745 text = m.text()
1746 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1746 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1747 n = self._revlog.node(rev)
1747 n = self._revlog.node(rev)
1748 arraytext = bytearray(text)
1748 arraytext = bytearray(text)
1749
1749
1750 if arraytext is not None:
1750 if arraytext is not None:
1751 self.fulltextcache[n] = arraytext
1751 self.fulltextcache[n] = arraytext
1752
1752
1753 return n
1753 return n
1754
1754
1755 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1755 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1756 # If the manifest is unchanged compared to one parent,
1756 # If the manifest is unchanged compared to one parent,
1757 # don't write a new revision
1757 # don't write a new revision
1758 if self.tree != b'' and (
1758 if self.tree != b'' and (
1759 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1759 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1760 ):
1760 ):
1761 return m.node()
1761 return m.node()
1762
1762
1763 def writesubtree(subm, subp1, subp2, match):
1763 def writesubtree(subm, subp1, subp2, match):
1764 sublog = self.dirlog(subm.dir())
1764 sublog = self.dirlog(subm.dir())
1765 sublog.add(
1765 sublog.add(
1766 subm,
1766 subm,
1767 transaction,
1767 transaction,
1768 link,
1768 link,
1769 subp1,
1769 subp1,
1770 subp2,
1770 subp2,
1771 None,
1771 None,
1772 None,
1772 None,
1773 readtree=readtree,
1773 readtree=readtree,
1774 match=match,
1774 match=match,
1775 )
1775 )
1776
1776
1777 m.writesubtrees(m1, m2, writesubtree, match)
1777 m.writesubtrees(m1, m2, writesubtree, match)
1778 text = m.dirtext()
1778 text = m.dirtext()
1779 n = None
1779 n = None
1780 if self.tree != b'':
1780 if self.tree != b'':
1781 # Double-check whether contents are unchanged to one parent
1781 # Double-check whether contents are unchanged to one parent
1782 if text == m1.dirtext():
1782 if text == m1.dirtext():
1783 n = m1.node()
1783 n = m1.node()
1784 elif text == m2.dirtext():
1784 elif text == m2.dirtext():
1785 n = m2.node()
1785 n = m2.node()
1786
1786
1787 if not n:
1787 if not n:
1788 rev = self._revlog.addrevision(
1788 rev = self._revlog.addrevision(
1789 text, transaction, link, m1.node(), m2.node()
1789 text, transaction, link, m1.node(), m2.node()
1790 )
1790 )
1791 n = self._revlog.node(rev)
1791 n = self._revlog.node(rev)
1792
1792
1793 # Save nodeid so parent manifest can calculate its nodeid
1793 # Save nodeid so parent manifest can calculate its nodeid
1794 m.setnode(n)
1794 m.setnode(n)
1795 return n
1795 return n
1796
1796
1797 def __len__(self):
1797 def __len__(self):
1798 return len(self._revlog)
1798 return len(self._revlog)
1799
1799
1800 def __iter__(self):
1800 def __iter__(self):
1801 return self._revlog.__iter__()
1801 return self._revlog.__iter__()
1802
1802
1803 def rev(self, node):
1803 def rev(self, node):
1804 return self._revlog.rev(node)
1804 return self._revlog.rev(node)
1805
1805
1806 def node(self, rev):
1806 def node(self, rev):
1807 return self._revlog.node(rev)
1807 return self._revlog.node(rev)
1808
1808
1809 def lookup(self, value):
1809 def lookup(self, value):
1810 return self._revlog.lookup(value)
1810 return self._revlog.lookup(value)
1811
1811
1812 def parentrevs(self, rev):
1812 def parentrevs(self, rev):
1813 return self._revlog.parentrevs(rev)
1813 return self._revlog.parentrevs(rev)
1814
1814
1815 def parents(self, node):
1815 def parents(self, node):
1816 return self._revlog.parents(node)
1816 return self._revlog.parents(node)
1817
1817
1818 def linkrev(self, rev):
1818 def linkrev(self, rev):
1819 return self._revlog.linkrev(rev)
1819 return self._revlog.linkrev(rev)
1820
1820
1821 def checksize(self):
1821 def checksize(self):
1822 return self._revlog.checksize()
1822 return self._revlog.checksize()
1823
1823
1824 def revision(self, node, _df=None, raw=False):
1824 def revision(self, node, _df=None, raw=False):
1825 return self._revlog.revision(node, _df=_df, raw=raw)
1825 return self._revlog.revision(node, _df=_df, raw=raw)
1826
1826
1827 def rawdata(self, node, _df=None):
1827 def rawdata(self, node, _df=None):
1828 return self._revlog.rawdata(node, _df=_df)
1828 return self._revlog.rawdata(node, _df=_df)
1829
1829
1830 def revdiff(self, rev1, rev2):
1830 def revdiff(self, rev1, rev2):
1831 return self._revlog.revdiff(rev1, rev2)
1831 return self._revlog.revdiff(rev1, rev2)
1832
1832
1833 def cmp(self, node, text):
1833 def cmp(self, node, text):
1834 return self._revlog.cmp(node, text)
1834 return self._revlog.cmp(node, text)
1835
1835
1836 def deltaparent(self, rev):
1836 def deltaparent(self, rev):
1837 return self._revlog.deltaparent(rev)
1837 return self._revlog.deltaparent(rev)
1838
1838
1839 def emitrevisions(
1839 def emitrevisions(
1840 self,
1840 self,
1841 nodes,
1841 nodes,
1842 nodesorder=None,
1842 nodesorder=None,
1843 revisiondata=False,
1843 revisiondata=False,
1844 assumehaveparentrevisions=False,
1844 assumehaveparentrevisions=False,
1845 deltamode=repository.CG_DELTAMODE_STD,
1845 deltamode=repository.CG_DELTAMODE_STD,
1846 sidedata_helpers=None,
1846 sidedata_helpers=None,
1847 ):
1847 ):
1848 return self._revlog.emitrevisions(
1848 return self._revlog.emitrevisions(
1849 nodes,
1849 nodes,
1850 nodesorder=nodesorder,
1850 nodesorder=nodesorder,
1851 revisiondata=revisiondata,
1851 revisiondata=revisiondata,
1852 assumehaveparentrevisions=assumehaveparentrevisions,
1852 assumehaveparentrevisions=assumehaveparentrevisions,
1853 deltamode=deltamode,
1853 deltamode=deltamode,
1854 sidedata_helpers=sidedata_helpers,
1854 sidedata_helpers=sidedata_helpers,
1855 )
1855 )
1856
1856
1857 def addgroup(
1857 def addgroup(
1858 self,
1858 self,
1859 deltas,
1859 deltas,
1860 linkmapper,
1860 linkmapper,
1861 transaction,
1861 transaction,
1862 alwayscache=False,
1862 alwayscache=False,
1863 addrevisioncb=None,
1863 addrevisioncb=None,
1864 duplicaterevisioncb=None,
1864 duplicaterevisioncb=None,
1865 ):
1865 ):
1866 return self._revlog.addgroup(
1866 return self._revlog.addgroup(
1867 deltas,
1867 deltas,
1868 linkmapper,
1868 linkmapper,
1869 transaction,
1869 transaction,
1870 alwayscache=alwayscache,
1870 alwayscache=alwayscache,
1871 addrevisioncb=addrevisioncb,
1871 addrevisioncb=addrevisioncb,
1872 duplicaterevisioncb=duplicaterevisioncb,
1872 duplicaterevisioncb=duplicaterevisioncb,
1873 )
1873 )
1874
1874
1875 def rawsize(self, rev):
1875 def rawsize(self, rev):
1876 return self._revlog.rawsize(rev)
1876 return self._revlog.rawsize(rev)
1877
1877
1878 def getstrippoint(self, minlink):
1878 def getstrippoint(self, minlink):
1879 return self._revlog.getstrippoint(minlink)
1879 return self._revlog.getstrippoint(minlink)
1880
1880
1881 def strip(self, minlink, transaction):
1881 def strip(self, minlink, transaction):
1882 return self._revlog.strip(minlink, transaction)
1882 return self._revlog.strip(minlink, transaction)
1883
1883
1884 def files(self):
1884 def files(self):
1885 return self._revlog.files()
1885 return self._revlog.files()
1886
1886
1887 def clone(self, tr, destrevlog, **kwargs):
1887 def clone(self, tr, destrevlog, **kwargs):
1888 if not isinstance(destrevlog, manifestrevlog):
1888 if not isinstance(destrevlog, manifestrevlog):
1889 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1889 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1890
1890
1891 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1891 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1892
1892
1893 def storageinfo(
1893 def storageinfo(
1894 self,
1894 self,
1895 exclusivefiles=False,
1895 exclusivefiles=False,
1896 sharedfiles=False,
1896 sharedfiles=False,
1897 revisionscount=False,
1897 revisionscount=False,
1898 trackedsize=False,
1898 trackedsize=False,
1899 storedsize=False,
1899 storedsize=False,
1900 ):
1900 ):
1901 return self._revlog.storageinfo(
1901 return self._revlog.storageinfo(
1902 exclusivefiles=exclusivefiles,
1902 exclusivefiles=exclusivefiles,
1903 sharedfiles=sharedfiles,
1903 sharedfiles=sharedfiles,
1904 revisionscount=revisionscount,
1904 revisionscount=revisionscount,
1905 trackedsize=trackedsize,
1905 trackedsize=trackedsize,
1906 storedsize=storedsize,
1906 storedsize=storedsize,
1907 )
1907 )
1908
1908
1909 @property
1909 @property
1910 def indexfile(self):
1911 return self._revlog.indexfile
1912
1913 @indexfile.setter
1914 def indexfile(self, value):
1915 self._revlog.indexfile = value
1916
1917 @property
1918 def opener(self):
1910 def opener(self):
1919 return self._revlog.opener
1911 return self._revlog.opener
1920
1912
1921 @opener.setter
1913 @opener.setter
1922 def opener(self, value):
1914 def opener(self, value):
1923 self._revlog.opener = value
1915 self._revlog.opener = value
1924
1916
1925
1917
1926 @interfaceutil.implementer(repository.imanifestlog)
1918 @interfaceutil.implementer(repository.imanifestlog)
1927 class manifestlog(object):
1919 class manifestlog(object):
1928 """A collection class representing the collection of manifest snapshots
1920 """A collection class representing the collection of manifest snapshots
1929 referenced by commits in the repository.
1921 referenced by commits in the repository.
1930
1922
1931 In this situation, 'manifest' refers to the abstract concept of a snapshot
1923 In this situation, 'manifest' refers to the abstract concept of a snapshot
1932 of the list of files in the given commit. Consumers of the output of this
1924 of the list of files in the given commit. Consumers of the output of this
1933 class do not care about the implementation details of the actual manifests
1925 class do not care about the implementation details of the actual manifests
1934 they receive (i.e. tree or flat or lazily loaded, etc)."""
1926 they receive (i.e. tree or flat or lazily loaded, etc)."""
1935
1927
1936 def __init__(self, opener, repo, rootstore, narrowmatch):
1928 def __init__(self, opener, repo, rootstore, narrowmatch):
1937 self.nodeconstants = repo.nodeconstants
1929 self.nodeconstants = repo.nodeconstants
1938 usetreemanifest = False
1930 usetreemanifest = False
1939 cachesize = 4
1931 cachesize = 4
1940
1932
1941 opts = getattr(opener, 'options', None)
1933 opts = getattr(opener, 'options', None)
1942 if opts is not None:
1934 if opts is not None:
1943 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1935 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1944 cachesize = opts.get(b'manifestcachesize', cachesize)
1936 cachesize = opts.get(b'manifestcachesize', cachesize)
1945
1937
1946 self._treemanifests = usetreemanifest
1938 self._treemanifests = usetreemanifest
1947
1939
1948 self._rootstore = rootstore
1940 self._rootstore = rootstore
1949 self._rootstore._setupmanifestcachehooks(repo)
1941 self._rootstore._setupmanifestcachehooks(repo)
1950 self._narrowmatch = narrowmatch
1942 self._narrowmatch = narrowmatch
1951
1943
1952 # A cache of the manifestctx or treemanifestctx for each directory
1944 # A cache of the manifestctx or treemanifestctx for each directory
1953 self._dirmancache = {}
1945 self._dirmancache = {}
1954 self._dirmancache[b''] = util.lrucachedict(cachesize)
1946 self._dirmancache[b''] = util.lrucachedict(cachesize)
1955
1947
1956 self._cachesize = cachesize
1948 self._cachesize = cachesize
1957
1949
1958 def __getitem__(self, node):
1950 def __getitem__(self, node):
1959 """Retrieves the manifest instance for the given node. Throws a
1951 """Retrieves the manifest instance for the given node. Throws a
1960 LookupError if not found.
1952 LookupError if not found.
1961 """
1953 """
1962 return self.get(b'', node)
1954 return self.get(b'', node)
1963
1955
1964 def get(self, tree, node, verify=True):
1956 def get(self, tree, node, verify=True):
1965 """Retrieves the manifest instance for the given node. Throws a
1957 """Retrieves the manifest instance for the given node. Throws a
1966 LookupError if not found.
1958 LookupError if not found.
1967
1959
1968 `verify` - if True an exception will be thrown if the node is not in
1960 `verify` - if True an exception will be thrown if the node is not in
1969 the revlog
1961 the revlog
1970 """
1962 """
1971 if node in self._dirmancache.get(tree, ()):
1963 if node in self._dirmancache.get(tree, ()):
1972 return self._dirmancache[tree][node]
1964 return self._dirmancache[tree][node]
1973
1965
1974 if not self._narrowmatch.always():
1966 if not self._narrowmatch.always():
1975 if not self._narrowmatch.visitdir(tree[:-1]):
1967 if not self._narrowmatch.visitdir(tree[:-1]):
1976 return excludeddirmanifestctx(self.nodeconstants, tree, node)
1968 return excludeddirmanifestctx(self.nodeconstants, tree, node)
1977 if tree:
1969 if tree:
1978 if self._rootstore._treeondisk:
1970 if self._rootstore._treeondisk:
1979 if verify:
1971 if verify:
1980 # Side-effect is LookupError is raised if node doesn't
1972 # Side-effect is LookupError is raised if node doesn't
1981 # exist.
1973 # exist.
1982 self.getstorage(tree).rev(node)
1974 self.getstorage(tree).rev(node)
1983
1975
1984 m = treemanifestctx(self, tree, node)
1976 m = treemanifestctx(self, tree, node)
1985 else:
1977 else:
1986 raise error.Abort(
1978 raise error.Abort(
1987 _(
1979 _(
1988 b"cannot ask for manifest directory '%s' in a flat "
1980 b"cannot ask for manifest directory '%s' in a flat "
1989 b"manifest"
1981 b"manifest"
1990 )
1982 )
1991 % tree
1983 % tree
1992 )
1984 )
1993 else:
1985 else:
1994 if verify:
1986 if verify:
1995 # Side-effect is LookupError is raised if node doesn't exist.
1987 # Side-effect is LookupError is raised if node doesn't exist.
1996 self._rootstore.rev(node)
1988 self._rootstore.rev(node)
1997
1989
1998 if self._treemanifests:
1990 if self._treemanifests:
1999 m = treemanifestctx(self, b'', node)
1991 m = treemanifestctx(self, b'', node)
2000 else:
1992 else:
2001 m = manifestctx(self, node)
1993 m = manifestctx(self, node)
2002
1994
2003 if node != self.nodeconstants.nullid:
1995 if node != self.nodeconstants.nullid:
2004 mancache = self._dirmancache.get(tree)
1996 mancache = self._dirmancache.get(tree)
2005 if not mancache:
1997 if not mancache:
2006 mancache = util.lrucachedict(self._cachesize)
1998 mancache = util.lrucachedict(self._cachesize)
2007 self._dirmancache[tree] = mancache
1999 self._dirmancache[tree] = mancache
2008 mancache[node] = m
2000 mancache[node] = m
2009 return m
2001 return m
2010
2002
2011 def getstorage(self, tree):
2003 def getstorage(self, tree):
2012 return self._rootstore.dirlog(tree)
2004 return self._rootstore.dirlog(tree)
2013
2005
2014 def clearcaches(self, clear_persisted_data=False):
2006 def clearcaches(self, clear_persisted_data=False):
2015 self._dirmancache.clear()
2007 self._dirmancache.clear()
2016 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2008 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2017
2009
2018 def rev(self, node):
2010 def rev(self, node):
2019 return self._rootstore.rev(node)
2011 return self._rootstore.rev(node)
2020
2012
2021 def update_caches(self, transaction):
2013 def update_caches(self, transaction):
2022 return self._rootstore._revlog.update_caches(transaction=transaction)
2014 return self._rootstore._revlog.update_caches(transaction=transaction)
2023
2015
2024
2016
2025 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2017 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2026 class memmanifestctx(object):
2018 class memmanifestctx(object):
2027 def __init__(self, manifestlog):
2019 def __init__(self, manifestlog):
2028 self._manifestlog = manifestlog
2020 self._manifestlog = manifestlog
2029 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2021 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2030
2022
2031 def _storage(self):
2023 def _storage(self):
2032 return self._manifestlog.getstorage(b'')
2024 return self._manifestlog.getstorage(b'')
2033
2025
2034 def copy(self):
2026 def copy(self):
2035 memmf = memmanifestctx(self._manifestlog)
2027 memmf = memmanifestctx(self._manifestlog)
2036 memmf._manifestdict = self.read().copy()
2028 memmf._manifestdict = self.read().copy()
2037 return memmf
2029 return memmf
2038
2030
2039 def read(self):
2031 def read(self):
2040 return self._manifestdict
2032 return self._manifestdict
2041
2033
2042 def write(self, transaction, link, p1, p2, added, removed, match=None):
2034 def write(self, transaction, link, p1, p2, added, removed, match=None):
2043 return self._storage().add(
2035 return self._storage().add(
2044 self._manifestdict,
2036 self._manifestdict,
2045 transaction,
2037 transaction,
2046 link,
2038 link,
2047 p1,
2039 p1,
2048 p2,
2040 p2,
2049 added,
2041 added,
2050 removed,
2042 removed,
2051 match=match,
2043 match=match,
2052 )
2044 )
2053
2045
2054
2046
2055 @interfaceutil.implementer(repository.imanifestrevisionstored)
2047 @interfaceutil.implementer(repository.imanifestrevisionstored)
2056 class manifestctx(object):
2048 class manifestctx(object):
2057 """A class representing a single revision of a manifest, including its
2049 """A class representing a single revision of a manifest, including its
2058 contents, its parent revs, and its linkrev.
2050 contents, its parent revs, and its linkrev.
2059 """
2051 """
2060
2052
2061 def __init__(self, manifestlog, node):
2053 def __init__(self, manifestlog, node):
2062 self._manifestlog = manifestlog
2054 self._manifestlog = manifestlog
2063 self._data = None
2055 self._data = None
2064
2056
2065 self._node = node
2057 self._node = node
2066
2058
2067 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2059 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2068 # but let's add it later when something needs it and we can load it
2060 # but let's add it later when something needs it and we can load it
2069 # lazily.
2061 # lazily.
2070 # self.p1, self.p2 = store.parents(node)
2062 # self.p1, self.p2 = store.parents(node)
2071 # rev = store.rev(node)
2063 # rev = store.rev(node)
2072 # self.linkrev = store.linkrev(rev)
2064 # self.linkrev = store.linkrev(rev)
2073
2065
2074 def _storage(self):
2066 def _storage(self):
2075 return self._manifestlog.getstorage(b'')
2067 return self._manifestlog.getstorage(b'')
2076
2068
2077 def node(self):
2069 def node(self):
2078 return self._node
2070 return self._node
2079
2071
2080 def copy(self):
2072 def copy(self):
2081 memmf = memmanifestctx(self._manifestlog)
2073 memmf = memmanifestctx(self._manifestlog)
2082 memmf._manifestdict = self.read().copy()
2074 memmf._manifestdict = self.read().copy()
2083 return memmf
2075 return memmf
2084
2076
2085 @propertycache
2077 @propertycache
2086 def parents(self):
2078 def parents(self):
2087 return self._storage().parents(self._node)
2079 return self._storage().parents(self._node)
2088
2080
2089 def read(self):
2081 def read(self):
2090 if self._data is None:
2082 if self._data is None:
2091 nc = self._manifestlog.nodeconstants
2083 nc = self._manifestlog.nodeconstants
2092 if self._node == nc.nullid:
2084 if self._node == nc.nullid:
2093 self._data = manifestdict(nc.nodelen)
2085 self._data = manifestdict(nc.nodelen)
2094 else:
2086 else:
2095 store = self._storage()
2087 store = self._storage()
2096 if self._node in store.fulltextcache:
2088 if self._node in store.fulltextcache:
2097 text = pycompat.bytestr(store.fulltextcache[self._node])
2089 text = pycompat.bytestr(store.fulltextcache[self._node])
2098 else:
2090 else:
2099 text = store.revision(self._node)
2091 text = store.revision(self._node)
2100 arraytext = bytearray(text)
2092 arraytext = bytearray(text)
2101 store.fulltextcache[self._node] = arraytext
2093 store.fulltextcache[self._node] = arraytext
2102 self._data = manifestdict(nc.nodelen, text)
2094 self._data = manifestdict(nc.nodelen, text)
2103 return self._data
2095 return self._data
2104
2096
2105 def readfast(self, shallow=False):
2097 def readfast(self, shallow=False):
2106 """Calls either readdelta or read, based on which would be less work.
2098 """Calls either readdelta or read, based on which would be less work.
2107 readdelta is called if the delta is against the p1, and therefore can be
2099 readdelta is called if the delta is against the p1, and therefore can be
2108 read quickly.
2100 read quickly.
2109
2101
2110 If `shallow` is True, nothing changes since this is a flat manifest.
2102 If `shallow` is True, nothing changes since this is a flat manifest.
2111 """
2103 """
2112 store = self._storage()
2104 store = self._storage()
2113 r = store.rev(self._node)
2105 r = store.rev(self._node)
2114 deltaparent = store.deltaparent(r)
2106 deltaparent = store.deltaparent(r)
2115 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2107 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2116 return self.readdelta()
2108 return self.readdelta()
2117 return self.read()
2109 return self.read()
2118
2110
2119 def readdelta(self, shallow=False):
2111 def readdelta(self, shallow=False):
2120 """Returns a manifest containing just the entries that are present
2112 """Returns a manifest containing just the entries that are present
2121 in this manifest, but not in its p1 manifest. This is efficient to read
2113 in this manifest, but not in its p1 manifest. This is efficient to read
2122 if the revlog delta is already p1.
2114 if the revlog delta is already p1.
2123
2115
2124 Changing the value of `shallow` has no effect on flat manifests.
2116 Changing the value of `shallow` has no effect on flat manifests.
2125 """
2117 """
2126 store = self._storage()
2118 store = self._storage()
2127 r = store.rev(self._node)
2119 r = store.rev(self._node)
2128 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2120 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2129 return manifestdict(store.nodeconstants.nodelen, d)
2121 return manifestdict(store.nodeconstants.nodelen, d)
2130
2122
2131 def find(self, key):
2123 def find(self, key):
2132 return self.read().find(key)
2124 return self.read().find(key)
2133
2125
2134
2126
2135 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2127 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2136 class memtreemanifestctx(object):
2128 class memtreemanifestctx(object):
2137 def __init__(self, manifestlog, dir=b''):
2129 def __init__(self, manifestlog, dir=b''):
2138 self._manifestlog = manifestlog
2130 self._manifestlog = manifestlog
2139 self._dir = dir
2131 self._dir = dir
2140 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2132 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2141
2133
2142 def _storage(self):
2134 def _storage(self):
2143 return self._manifestlog.getstorage(b'')
2135 return self._manifestlog.getstorage(b'')
2144
2136
2145 def copy(self):
2137 def copy(self):
2146 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2138 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2147 memmf._treemanifest = self._treemanifest.copy()
2139 memmf._treemanifest = self._treemanifest.copy()
2148 return memmf
2140 return memmf
2149
2141
2150 def read(self):
2142 def read(self):
2151 return self._treemanifest
2143 return self._treemanifest
2152
2144
2153 def write(self, transaction, link, p1, p2, added, removed, match=None):
2145 def write(self, transaction, link, p1, p2, added, removed, match=None):
2154 def readtree(dir, node):
2146 def readtree(dir, node):
2155 return self._manifestlog.get(dir, node).read()
2147 return self._manifestlog.get(dir, node).read()
2156
2148
2157 return self._storage().add(
2149 return self._storage().add(
2158 self._treemanifest,
2150 self._treemanifest,
2159 transaction,
2151 transaction,
2160 link,
2152 link,
2161 p1,
2153 p1,
2162 p2,
2154 p2,
2163 added,
2155 added,
2164 removed,
2156 removed,
2165 readtree=readtree,
2157 readtree=readtree,
2166 match=match,
2158 match=match,
2167 )
2159 )
2168
2160
2169
2161
2170 @interfaceutil.implementer(repository.imanifestrevisionstored)
2162 @interfaceutil.implementer(repository.imanifestrevisionstored)
2171 class treemanifestctx(object):
2163 class treemanifestctx(object):
2172 def __init__(self, manifestlog, dir, node):
2164 def __init__(self, manifestlog, dir, node):
2173 self._manifestlog = manifestlog
2165 self._manifestlog = manifestlog
2174 self._dir = dir
2166 self._dir = dir
2175 self._data = None
2167 self._data = None
2176
2168
2177 self._node = node
2169 self._node = node
2178
2170
2179 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2171 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2180 # we can instantiate treemanifestctx objects for directories we don't
2172 # we can instantiate treemanifestctx objects for directories we don't
2181 # have on disk.
2173 # have on disk.
2182 # self.p1, self.p2 = store.parents(node)
2174 # self.p1, self.p2 = store.parents(node)
2183 # rev = store.rev(node)
2175 # rev = store.rev(node)
2184 # self.linkrev = store.linkrev(rev)
2176 # self.linkrev = store.linkrev(rev)
2185
2177
2186 def _storage(self):
2178 def _storage(self):
2187 narrowmatch = self._manifestlog._narrowmatch
2179 narrowmatch = self._manifestlog._narrowmatch
2188 if not narrowmatch.always():
2180 if not narrowmatch.always():
2189 if not narrowmatch.visitdir(self._dir[:-1]):
2181 if not narrowmatch.visitdir(self._dir[:-1]):
2190 return excludedmanifestrevlog(
2182 return excludedmanifestrevlog(
2191 self._manifestlog.nodeconstants, self._dir
2183 self._manifestlog.nodeconstants, self._dir
2192 )
2184 )
2193 return self._manifestlog.getstorage(self._dir)
2185 return self._manifestlog.getstorage(self._dir)
2194
2186
2195 def read(self):
2187 def read(self):
2196 if self._data is None:
2188 if self._data is None:
2197 store = self._storage()
2189 store = self._storage()
2198 if self._node == self._manifestlog.nodeconstants.nullid:
2190 if self._node == self._manifestlog.nodeconstants.nullid:
2199 self._data = treemanifest(self._manifestlog.nodeconstants)
2191 self._data = treemanifest(self._manifestlog.nodeconstants)
2200 # TODO accessing non-public API
2192 # TODO accessing non-public API
2201 elif store._treeondisk:
2193 elif store._treeondisk:
2202 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2194 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2203
2195
2204 def gettext():
2196 def gettext():
2205 return store.revision(self._node)
2197 return store.revision(self._node)
2206
2198
2207 def readsubtree(dir, subm):
2199 def readsubtree(dir, subm):
2208 # Set verify to False since we need to be able to create
2200 # Set verify to False since we need to be able to create
2209 # subtrees for trees that don't exist on disk.
2201 # subtrees for trees that don't exist on disk.
2210 return self._manifestlog.get(dir, subm, verify=False).read()
2202 return self._manifestlog.get(dir, subm, verify=False).read()
2211
2203
2212 m.read(gettext, readsubtree)
2204 m.read(gettext, readsubtree)
2213 m.setnode(self._node)
2205 m.setnode(self._node)
2214 self._data = m
2206 self._data = m
2215 else:
2207 else:
2216 if self._node in store.fulltextcache:
2208 if self._node in store.fulltextcache:
2217 text = pycompat.bytestr(store.fulltextcache[self._node])
2209 text = pycompat.bytestr(store.fulltextcache[self._node])
2218 else:
2210 else:
2219 text = store.revision(self._node)
2211 text = store.revision(self._node)
2220 arraytext = bytearray(text)
2212 arraytext = bytearray(text)
2221 store.fulltextcache[self._node] = arraytext
2213 store.fulltextcache[self._node] = arraytext
2222 self._data = treemanifest(
2214 self._data = treemanifest(
2223 self._manifestlog.nodeconstants, dir=self._dir, text=text
2215 self._manifestlog.nodeconstants, dir=self._dir, text=text
2224 )
2216 )
2225
2217
2226 return self._data
2218 return self._data
2227
2219
2228 def node(self):
2220 def node(self):
2229 return self._node
2221 return self._node
2230
2222
2231 def copy(self):
2223 def copy(self):
2232 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2224 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2233 memmf._treemanifest = self.read().copy()
2225 memmf._treemanifest = self.read().copy()
2234 return memmf
2226 return memmf
2235
2227
2236 @propertycache
2228 @propertycache
2237 def parents(self):
2229 def parents(self):
2238 return self._storage().parents(self._node)
2230 return self._storage().parents(self._node)
2239
2231
2240 def readdelta(self, shallow=False):
2232 def readdelta(self, shallow=False):
2241 """Returns a manifest containing just the entries that are present
2233 """Returns a manifest containing just the entries that are present
2242 in this manifest, but not in its p1 manifest. This is efficient to read
2234 in this manifest, but not in its p1 manifest. This is efficient to read
2243 if the revlog delta is already p1.
2235 if the revlog delta is already p1.
2244
2236
2245 If `shallow` is True, this will read the delta for this directory,
2237 If `shallow` is True, this will read the delta for this directory,
2246 without recursively reading subdirectory manifests. Instead, any
2238 without recursively reading subdirectory manifests. Instead, any
2247 subdirectory entry will be reported as it appears in the manifest, i.e.
2239 subdirectory entry will be reported as it appears in the manifest, i.e.
2248 the subdirectory will be reported among files and distinguished only by
2240 the subdirectory will be reported among files and distinguished only by
2249 its 't' flag.
2241 its 't' flag.
2250 """
2242 """
2251 store = self._storage()
2243 store = self._storage()
2252 if shallow:
2244 if shallow:
2253 r = store.rev(self._node)
2245 r = store.rev(self._node)
2254 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2246 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2255 return manifestdict(store.nodeconstants.nodelen, d)
2247 return manifestdict(store.nodeconstants.nodelen, d)
2256 else:
2248 else:
2257 # Need to perform a slow delta
2249 # Need to perform a slow delta
2258 r0 = store.deltaparent(store.rev(self._node))
2250 r0 = store.deltaparent(store.rev(self._node))
2259 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2251 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2260 m1 = self.read()
2252 m1 = self.read()
2261 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2253 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2262 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2254 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2263 if n1:
2255 if n1:
2264 md[f] = n1
2256 md[f] = n1
2265 if fl1:
2257 if fl1:
2266 md.setflag(f, fl1)
2258 md.setflag(f, fl1)
2267 return md
2259 return md
2268
2260
2269 def readfast(self, shallow=False):
2261 def readfast(self, shallow=False):
2270 """Calls either readdelta or read, based on which would be less work.
2262 """Calls either readdelta or read, based on which would be less work.
2271 readdelta is called if the delta is against the p1, and therefore can be
2263 readdelta is called if the delta is against the p1, and therefore can be
2272 read quickly.
2264 read quickly.
2273
2265
2274 If `shallow` is True, it only returns the entries from this manifest,
2266 If `shallow` is True, it only returns the entries from this manifest,
2275 and not any submanifests.
2267 and not any submanifests.
2276 """
2268 """
2277 store = self._storage()
2269 store = self._storage()
2278 r = store.rev(self._node)
2270 r = store.rev(self._node)
2279 deltaparent = store.deltaparent(r)
2271 deltaparent = store.deltaparent(r)
2280 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2272 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2281 return self.readdelta(shallow=shallow)
2273 return self.readdelta(shallow=shallow)
2282
2274
2283 if shallow:
2275 if shallow:
2284 return manifestdict(
2276 return manifestdict(
2285 store.nodeconstants.nodelen, store.revision(self._node)
2277 store.nodeconstants.nodelen, store.revision(self._node)
2286 )
2278 )
2287 else:
2279 else:
2288 return self.read()
2280 return self.read()
2289
2281
2290 def find(self, key):
2282 def find(self, key):
2291 return self.read().find(key)
2283 return self.read().find(key)
2292
2284
2293
2285
2294 class excludeddir(treemanifest):
2286 class excludeddir(treemanifest):
2295 """Stand-in for a directory that is excluded from the repository.
2287 """Stand-in for a directory that is excluded from the repository.
2296
2288
2297 With narrowing active on a repository that uses treemanifests,
2289 With narrowing active on a repository that uses treemanifests,
2298 some of the directory revlogs will be excluded from the resulting
2290 some of the directory revlogs will be excluded from the resulting
2299 clone. This is a huge storage win for clients, but means we need
2291 clone. This is a huge storage win for clients, but means we need
2300 some sort of pseudo-manifest to surface to internals so we can
2292 some sort of pseudo-manifest to surface to internals so we can
2301 detect a merge conflict outside the narrowspec. That's what this
2293 detect a merge conflict outside the narrowspec. That's what this
2302 class is: it stands in for a directory whose node is known, but
2294 class is: it stands in for a directory whose node is known, but
2303 whose contents are unknown.
2295 whose contents are unknown.
2304 """
2296 """
2305
2297
2306 def __init__(self, nodeconstants, dir, node):
2298 def __init__(self, nodeconstants, dir, node):
2307 super(excludeddir, self).__init__(nodeconstants, dir)
2299 super(excludeddir, self).__init__(nodeconstants, dir)
2308 self._node = node
2300 self._node = node
2309 # Add an empty file, which will be included by iterators and such,
2301 # Add an empty file, which will be included by iterators and such,
2310 # appearing as the directory itself (i.e. something like "dir/")
2302 # appearing as the directory itself (i.e. something like "dir/")
2311 self._files[b''] = node
2303 self._files[b''] = node
2312 self._flags[b''] = b't'
2304 self._flags[b''] = b't'
2313
2305
2314 # Manifests outside the narrowspec should never be modified, so avoid
2306 # Manifests outside the narrowspec should never be modified, so avoid
2315 # copying. This makes a noticeable difference when there are very many
2307 # copying. This makes a noticeable difference when there are very many
2316 # directories outside the narrowspec. Also, it makes sense for the copy to
2308 # directories outside the narrowspec. Also, it makes sense for the copy to
2317 # be of the same type as the original, which would not happen with the
2309 # be of the same type as the original, which would not happen with the
2318 # super type's copy().
2310 # super type's copy().
2319 def copy(self):
2311 def copy(self):
2320 return self
2312 return self
2321
2313
2322
2314
2323 class excludeddirmanifestctx(treemanifestctx):
2315 class excludeddirmanifestctx(treemanifestctx):
2324 """context wrapper for excludeddir - see that docstring for rationale"""
2316 """context wrapper for excludeddir - see that docstring for rationale"""
2325
2317
2326 def __init__(self, nodeconstants, dir, node):
2318 def __init__(self, nodeconstants, dir, node):
2327 self.nodeconstants = nodeconstants
2319 self.nodeconstants = nodeconstants
2328 self._dir = dir
2320 self._dir = dir
2329 self._node = node
2321 self._node = node
2330
2322
2331 def read(self):
2323 def read(self):
2332 return excludeddir(self.nodeconstants, self._dir, self._node)
2324 return excludeddir(self.nodeconstants, self._dir, self._node)
2333
2325
2334 def readfast(self, shallow=False):
2326 def readfast(self, shallow=False):
2335 # special version of readfast since we don't have underlying storage
2327 # special version of readfast since we don't have underlying storage
2336 return self.read()
2328 return self.read()
2337
2329
2338 def write(self, *args):
2330 def write(self, *args):
2339 raise error.ProgrammingError(
2331 raise error.ProgrammingError(
2340 b'attempt to write manifest from excluded dir %s' % self._dir
2332 b'attempt to write manifest from excluded dir %s' % self._dir
2341 )
2333 )
2342
2334
2343
2335
2344 class excludedmanifestrevlog(manifestrevlog):
2336 class excludedmanifestrevlog(manifestrevlog):
2345 """Stand-in for excluded treemanifest revlogs.
2337 """Stand-in for excluded treemanifest revlogs.
2346
2338
2347 When narrowing is active on a treemanifest repository, we'll have
2339 When narrowing is active on a treemanifest repository, we'll have
2348 references to directories we can't see due to the revlog being
2340 references to directories we can't see due to the revlog being
2349 skipped. This class exists to conform to the manifestrevlog
2341 skipped. This class exists to conform to the manifestrevlog
2350 interface for those directories and proactively prevent writes to
2342 interface for those directories and proactively prevent writes to
2351 outside the narrowspec.
2343 outside the narrowspec.
2352 """
2344 """
2353
2345
2354 def __init__(self, nodeconstants, dir):
2346 def __init__(self, nodeconstants, dir):
2355 self.nodeconstants = nodeconstants
2347 self.nodeconstants = nodeconstants
2356 self._dir = dir
2348 self._dir = dir
2357
2349
2358 def __len__(self):
2350 def __len__(self):
2359 raise error.ProgrammingError(
2351 raise error.ProgrammingError(
2360 b'attempt to get length of excluded dir %s' % self._dir
2352 b'attempt to get length of excluded dir %s' % self._dir
2361 )
2353 )
2362
2354
2363 def rev(self, node):
2355 def rev(self, node):
2364 raise error.ProgrammingError(
2356 raise error.ProgrammingError(
2365 b'attempt to get rev from excluded dir %s' % self._dir
2357 b'attempt to get rev from excluded dir %s' % self._dir
2366 )
2358 )
2367
2359
2368 def linkrev(self, node):
2360 def linkrev(self, node):
2369 raise error.ProgrammingError(
2361 raise error.ProgrammingError(
2370 b'attempt to get linkrev from excluded dir %s' % self._dir
2362 b'attempt to get linkrev from excluded dir %s' % self._dir
2371 )
2363 )
2372
2364
2373 def node(self, rev):
2365 def node(self, rev):
2374 raise error.ProgrammingError(
2366 raise error.ProgrammingError(
2375 b'attempt to get node from excluded dir %s' % self._dir
2367 b'attempt to get node from excluded dir %s' % self._dir
2376 )
2368 )
2377
2369
2378 def add(self, *args, **kwargs):
2370 def add(self, *args, **kwargs):
2379 # We should never write entries in dirlogs outside the narrow clone.
2371 # We should never write entries in dirlogs outside the narrow clone.
2380 # However, the method still gets called from writesubtree() in
2372 # However, the method still gets called from writesubtree() in
2381 # _addtree(), so we need to handle it. We should possibly make that
2373 # _addtree(), so we need to handle it. We should possibly make that
2382 # avoid calling add() with a clean manifest (_dirty is always False
2374 # avoid calling add() with a clean manifest (_dirty is always False
2383 # in excludeddir instances).
2375 # in excludeddir instances).
2384 pass
2376 pass
@@ -1,308 +1,308 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 target = getattr(revlog2, 'target', None)
44 target = getattr(revlog2, 'target', None)
45 if target is None:
45 if target is None:
46 # a revlog wrapper, eg: the manifestlog that is not an actual revlog
46 # a revlog wrapper, eg: the manifestlog that is not an actual revlog
47 target = revlog2._revlog.target
47 target = revlog2._revlog.target
48 revlog.revlog.__init__(self, opener, target=target, indexfile=indexfile)
48 revlog.revlog.__init__(self, opener, target=target, indexfile=indexfile)
49 self.revlog2 = revlog2
49 self.revlog2 = revlog2
50
50
51 n = len(self)
51 n = len(self)
52 self.repotiprev = n - 1
52 self.repotiprev = n - 1
53 self.bundlerevs = set() # used by 'bundle()' revset expression
53 self.bundlerevs = set() # used by 'bundle()' revset expression
54 for rev2 in self.revlog2:
54 for rev2 in self.revlog2:
55 rev = self.revlog2.index[rev2]
55 rev = self.revlog2.index[rev2]
56 # rev numbers - in revlog2, very different from self.rev
56 # rev numbers - in revlog2, very different from self.rev
57 (
57 (
58 _start,
58 _start,
59 _csize,
59 _csize,
60 rsize,
60 rsize,
61 base,
61 base,
62 linkrev,
62 linkrev,
63 p1rev,
63 p1rev,
64 p2rev,
64 p2rev,
65 node,
65 node,
66 _sdo,
66 _sdo,
67 _sds,
67 _sds,
68 ) = rev
68 ) = rev
69 flags = _start & 0xFFFF
69 flags = _start & 0xFFFF
70
70
71 if linkmapper is None: # link is to same revlog
71 if linkmapper is None: # link is to same revlog
72 assert linkrev == rev2 # we never link back
72 assert linkrev == rev2 # we never link back
73 link = n
73 link = n
74 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
74 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
75 link = linkmapper(linkrev)
75 link = linkmapper(linkrev)
76
76
77 if linkmapper is not None: # link is to same revlog
77 if linkmapper is not None: # link is to same revlog
78 base = linkmapper(base)
78 base = linkmapper(base)
79
79
80 this_rev = self.index.get_rev(node)
80 this_rev = self.index.get_rev(node)
81 if this_rev is not None:
81 if this_rev is not None:
82 # this happens for the common revlog revisions
82 # this happens for the common revlog revisions
83 self.bundlerevs.add(this_rev)
83 self.bundlerevs.add(this_rev)
84 continue
84 continue
85
85
86 p1node = self.revlog2.node(p1rev)
86 p1node = self.revlog2.node(p1rev)
87 p2node = self.revlog2.node(p2rev)
87 p2node = self.revlog2.node(p2rev)
88
88
89 # TODO: it's probably wrong to set compressed length to -1, but
89 # TODO: it's probably wrong to set compressed length to -1, but
90 # I have no idea if csize is valid in the base revlog context.
90 # I have no idea if csize is valid in the base revlog context.
91 e = (
91 e = (
92 flags,
92 flags,
93 -1,
93 -1,
94 rsize,
94 rsize,
95 base,
95 base,
96 link,
96 link,
97 self.rev(p1node),
97 self.rev(p1node),
98 self.rev(p2node),
98 self.rev(p2node),
99 node,
99 node,
100 0, # sidedata offset
100 0, # sidedata offset
101 0, # sidedata size
101 0, # sidedata size
102 )
102 )
103 self.index.append(e)
103 self.index.append(e)
104 self.bundlerevs.add(n)
104 self.bundlerevs.add(n)
105 n += 1
105 n += 1
106
106
107 def _chunk(self, rev):
107 def _chunk(self, rev):
108 if rev <= self.repotiprev:
108 if rev <= self.repotiprev:
109 return revlog.revlog._chunk(self, rev)
109 return revlog.revlog._chunk(self, rev)
110 return self.revlog2._chunk(self.node(rev))
110 return self.revlog2._chunk(self.node(rev))
111
111
112 def revdiff(self, rev1, rev2):
112 def revdiff(self, rev1, rev2):
113 """return or calculate a delta between two revisions"""
113 """return or calculate a delta between two revisions"""
114 if rev1 > self.repotiprev and rev2 > self.repotiprev:
114 if rev1 > self.repotiprev and rev2 > self.repotiprev:
115 return self.revlog2.revdiff(
115 return self.revlog2.revdiff(
116 self.revlog2.rev(self.node(rev1)),
116 self.revlog2.rev(self.node(rev1)),
117 self.revlog2.rev(self.node(rev2)),
117 self.revlog2.rev(self.node(rev2)),
118 )
118 )
119 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
119 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
120 return super(unionrevlog, self).revdiff(rev1, rev2)
120 return super(unionrevlog, self).revdiff(rev1, rev2)
121
121
122 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
122 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
123
123
124 def _revisiondata(self, nodeorrev, _df=None, raw=False):
124 def _revisiondata(self, nodeorrev, _df=None, raw=False):
125 if isinstance(nodeorrev, int):
125 if isinstance(nodeorrev, int):
126 rev = nodeorrev
126 rev = nodeorrev
127 node = self.node(rev)
127 node = self.node(rev)
128 else:
128 else:
129 node = nodeorrev
129 node = nodeorrev
130 rev = self.rev(node)
130 rev = self.rev(node)
131
131
132 if rev > self.repotiprev:
132 if rev > self.repotiprev:
133 # work around manifestrevlog NOT being a revlog
133 # work around manifestrevlog NOT being a revlog
134 revlog2 = getattr(self.revlog2, '_revlog', self.revlog2)
134 revlog2 = getattr(self.revlog2, '_revlog', self.revlog2)
135 func = revlog2._revisiondata
135 func = revlog2._revisiondata
136 else:
136 else:
137 func = super(unionrevlog, self)._revisiondata
137 func = super(unionrevlog, self)._revisiondata
138 return func(node, _df=_df, raw=raw)
138 return func(node, _df=_df, raw=raw)
139
139
140 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
140 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
141 raise NotImplementedError
141 raise NotImplementedError
142
142
143 def addgroup(
143 def addgroup(
144 self,
144 self,
145 deltas,
145 deltas,
146 linkmapper,
146 linkmapper,
147 transaction,
147 transaction,
148 alwayscache=False,
148 alwayscache=False,
149 addrevisioncb=None,
149 addrevisioncb=None,
150 duplicaterevisioncb=None,
150 duplicaterevisioncb=None,
151 maybemissingparents=False,
151 maybemissingparents=False,
152 ):
152 ):
153 raise NotImplementedError
153 raise NotImplementedError
154
154
155 def strip(self, minlink, transaction):
155 def strip(self, minlink, transaction):
156 raise NotImplementedError
156 raise NotImplementedError
157
157
158 def checksize(self):
158 def checksize(self):
159 raise NotImplementedError
159 raise NotImplementedError
160
160
161
161
162 class unionchangelog(unionrevlog, changelog.changelog):
162 class unionchangelog(unionrevlog, changelog.changelog):
163 def __init__(self, opener, opener2):
163 def __init__(self, opener, opener2):
164 changelog.changelog.__init__(self, opener)
164 changelog.changelog.__init__(self, opener)
165 linkmapper = None
165 linkmapper = None
166 changelog2 = changelog.changelog(opener2)
166 changelog2 = changelog.changelog(opener2)
167 unionrevlog.__init__(
167 unionrevlog.__init__(
168 self, opener, self.indexfile, changelog2, linkmapper
168 self, opener, self.indexfile, changelog2, linkmapper
169 )
169 )
170
170
171
171
172 class unionmanifest(unionrevlog, manifest.manifestrevlog):
172 class unionmanifest(unionrevlog, manifest.manifestrevlog):
173 def __init__(self, nodeconstants, opener, opener2, linkmapper):
173 def __init__(self, nodeconstants, opener, opener2, linkmapper):
174 manifest.manifestrevlog.__init__(self, nodeconstants, opener)
174 manifest.manifestrevlog.__init__(self, nodeconstants, opener)
175 manifest2 = manifest.manifestrevlog(nodeconstants, opener2)
175 manifest2 = manifest.manifestrevlog(nodeconstants, opener2)
176 unionrevlog.__init__(
176 unionrevlog.__init__(
177 self, opener, self.indexfile, manifest2, linkmapper
177 self, opener, self._revlog.indexfile, manifest2, linkmapper
178 )
178 )
179
179
180
180
181 class unionfilelog(filelog.filelog):
181 class unionfilelog(filelog.filelog):
182 def __init__(self, opener, path, opener2, linkmapper, repo):
182 def __init__(self, opener, path, opener2, linkmapper, repo):
183 filelog.filelog.__init__(self, opener, path)
183 filelog.filelog.__init__(self, opener, path)
184 filelog2 = filelog.filelog(opener2, path)
184 filelog2 = filelog.filelog(opener2, path)
185 self._revlog = unionrevlog(
185 self._revlog = unionrevlog(
186 opener, self.indexfile, filelog2._revlog, linkmapper
186 opener, self.indexfile, filelog2._revlog, linkmapper
187 )
187 )
188 self._repo = repo
188 self._repo = repo
189 self.repotiprev = self._revlog.repotiprev
189 self.repotiprev = self._revlog.repotiprev
190 self.revlog2 = self._revlog.revlog2
190 self.revlog2 = self._revlog.revlog2
191
191
192 def iscensored(self, rev):
192 def iscensored(self, rev):
193 """Check if a revision is censored."""
193 """Check if a revision is censored."""
194 if rev <= self.repotiprev:
194 if rev <= self.repotiprev:
195 return filelog.filelog.iscensored(self, rev)
195 return filelog.filelog.iscensored(self, rev)
196 node = self.node(rev)
196 node = self.node(rev)
197 return self.revlog2.iscensored(self.revlog2.rev(node))
197 return self.revlog2.iscensored(self.revlog2.rev(node))
198
198
199
199
200 class unionpeer(localrepo.localpeer):
200 class unionpeer(localrepo.localpeer):
201 def canpush(self):
201 def canpush(self):
202 return False
202 return False
203
203
204
204
205 class unionrepository(object):
205 class unionrepository(object):
206 """Represents the union of data in 2 repositories.
206 """Represents the union of data in 2 repositories.
207
207
208 Instances are not usable if constructed directly. Use ``instance()``
208 Instances are not usable if constructed directly. Use ``instance()``
209 or ``makeunionrepository()`` to create a usable instance.
209 or ``makeunionrepository()`` to create a usable instance.
210 """
210 """
211
211
212 def __init__(self, repo2, url):
212 def __init__(self, repo2, url):
213 self.repo2 = repo2
213 self.repo2 = repo2
214 self._url = url
214 self._url = url
215
215
216 self.ui.setconfig(b'phases', b'publish', False, b'unionrepo')
216 self.ui.setconfig(b'phases', b'publish', False, b'unionrepo')
217
217
218 @localrepo.unfilteredpropertycache
218 @localrepo.unfilteredpropertycache
219 def changelog(self):
219 def changelog(self):
220 return unionchangelog(self.svfs, self.repo2.svfs)
220 return unionchangelog(self.svfs, self.repo2.svfs)
221
221
222 @localrepo.unfilteredpropertycache
222 @localrepo.unfilteredpropertycache
223 def manifestlog(self):
223 def manifestlog(self):
224 rootstore = unionmanifest(
224 rootstore = unionmanifest(
225 self.nodeconstants,
225 self.nodeconstants,
226 self.svfs,
226 self.svfs,
227 self.repo2.svfs,
227 self.repo2.svfs,
228 self.unfiltered()._clrev,
228 self.unfiltered()._clrev,
229 )
229 )
230 return manifest.manifestlog(
230 return manifest.manifestlog(
231 self.svfs, self, rootstore, self.narrowmatch()
231 self.svfs, self, rootstore, self.narrowmatch()
232 )
232 )
233
233
234 def _clrev(self, rev2):
234 def _clrev(self, rev2):
235 """map from repo2 changelog rev to temporary rev in self.changelog"""
235 """map from repo2 changelog rev to temporary rev in self.changelog"""
236 node = self.repo2.changelog.node(rev2)
236 node = self.repo2.changelog.node(rev2)
237 return self.changelog.rev(node)
237 return self.changelog.rev(node)
238
238
239 def url(self):
239 def url(self):
240 return self._url
240 return self._url
241
241
242 def file(self, f):
242 def file(self, f):
243 return unionfilelog(
243 return unionfilelog(
244 self.svfs, f, self.repo2.svfs, self.unfiltered()._clrev, self
244 self.svfs, f, self.repo2.svfs, self.unfiltered()._clrev, self
245 )
245 )
246
246
247 def close(self):
247 def close(self):
248 self.repo2.close()
248 self.repo2.close()
249
249
250 def cancopy(self):
250 def cancopy(self):
251 return False
251 return False
252
252
253 def peer(self):
253 def peer(self):
254 return unionpeer(self)
254 return unionpeer(self)
255
255
256 def getcwd(self):
256 def getcwd(self):
257 return encoding.getcwd() # always outside the repo
257 return encoding.getcwd() # always outside the repo
258
258
259
259
260 def instance(ui, path, create, intents=None, createopts=None):
260 def instance(ui, path, create, intents=None, createopts=None):
261 if create:
261 if create:
262 raise error.Abort(_(b'cannot create new union repository'))
262 raise error.Abort(_(b'cannot create new union repository'))
263 parentpath = ui.config(b"bundle", b"mainreporoot")
263 parentpath = ui.config(b"bundle", b"mainreporoot")
264 if not parentpath:
264 if not parentpath:
265 # try to find the correct path to the working directory repo
265 # try to find the correct path to the working directory repo
266 parentpath = cmdutil.findrepo(encoding.getcwd())
266 parentpath = cmdutil.findrepo(encoding.getcwd())
267 if parentpath is None:
267 if parentpath is None:
268 parentpath = b''
268 parentpath = b''
269 if parentpath:
269 if parentpath:
270 # Try to make the full path relative so we get a nice, short URL.
270 # Try to make the full path relative so we get a nice, short URL.
271 # In particular, we don't want temp dir names in test outputs.
271 # In particular, we don't want temp dir names in test outputs.
272 cwd = encoding.getcwd()
272 cwd = encoding.getcwd()
273 if parentpath == cwd:
273 if parentpath == cwd:
274 parentpath = b''
274 parentpath = b''
275 else:
275 else:
276 cwd = pathutil.normasprefix(cwd)
276 cwd = pathutil.normasprefix(cwd)
277 if parentpath.startswith(cwd):
277 if parentpath.startswith(cwd):
278 parentpath = parentpath[len(cwd) :]
278 parentpath = parentpath[len(cwd) :]
279 if path.startswith(b'union:'):
279 if path.startswith(b'union:'):
280 s = path.split(b":", 1)[1].split(b"+", 1)
280 s = path.split(b":", 1)[1].split(b"+", 1)
281 if len(s) == 1:
281 if len(s) == 1:
282 repopath, repopath2 = parentpath, s[0]
282 repopath, repopath2 = parentpath, s[0]
283 else:
283 else:
284 repopath, repopath2 = s
284 repopath, repopath2 = s
285 else:
285 else:
286 repopath, repopath2 = parentpath, path
286 repopath, repopath2 = parentpath, path
287
287
288 return makeunionrepository(ui, repopath, repopath2)
288 return makeunionrepository(ui, repopath, repopath2)
289
289
290
290
291 def makeunionrepository(ui, repopath1, repopath2):
291 def makeunionrepository(ui, repopath1, repopath2):
292 """Make a union repository object from 2 local repo paths."""
292 """Make a union repository object from 2 local repo paths."""
293 repo1 = localrepo.instance(ui, repopath1, create=False)
293 repo1 = localrepo.instance(ui, repopath1, create=False)
294 repo2 = localrepo.instance(ui, repopath2, create=False)
294 repo2 = localrepo.instance(ui, repopath2, create=False)
295
295
296 url = b'union:%s+%s' % (
296 url = b'union:%s+%s' % (
297 util.expandpath(repopath1),
297 util.expandpath(repopath1),
298 util.expandpath(repopath2),
298 util.expandpath(repopath2),
299 )
299 )
300
300
301 class derivedunionrepository(unionrepository, repo1.__class__):
301 class derivedunionrepository(unionrepository, repo1.__class__):
302 pass
302 pass
303
303
304 repo = repo1
304 repo = repo1
305 repo.__class__ = derivedunionrepository
305 repo.__class__ = derivedunionrepository
306 unionrepository.__init__(repo1, repo2, url)
306 unionrepository.__init__(repo1, repo2, url)
307
307
308 return repo
308 return repo
General Comments 0
You need to be logged in to leave comments. Login now