##// END OF EJS Templates
sidedata: move sidedata-related utils to the dedicated module...
Raphaël Gomès -
r47848:3aab2330 default
parent child Browse files
Show More
@@ -1,1963 +1,1943 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 collections
11 import os
10 import os
12 import struct
11 import struct
13 import weakref
12 import weakref
14
13
15 from .i18n import _
14 from .i18n import _
16 from .node import (
15 from .node import (
17 hex,
16 hex,
18 nullrev,
17 nullrev,
19 short,
18 short,
20 )
19 )
21 from .pycompat import open
20 from .pycompat import open
22
21
23 from . import (
22 from . import (
24 error,
23 error,
25 match as matchmod,
24 match as matchmod,
26 mdiff,
25 mdiff,
27 phases,
26 phases,
28 pycompat,
27 pycompat,
29 requirements,
28 requirements,
30 scmutil,
29 scmutil,
31 util,
30 util,
32 )
31 )
33
32
34 from .interfaces import repository
33 from .interfaces import repository
35 from .revlogutils import sidedata as sidedatamod
34 from .revlogutils import sidedata as sidedatamod
36 from .revlogutils import constants as revlog_constants
35 from .revlogutils import constants as revlog_constants
37 from .utils import storageutil
36 from .utils import storageutil
38
37
39 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
40 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
41 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
42 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
43
42
44 LFS_REQUIREMENT = b'lfs'
43 LFS_REQUIREMENT = b'lfs'
45
44
46 readexactly = util.readexactly
45 readexactly = util.readexactly
47
46
48
47
49 def getchunk(stream):
48 def getchunk(stream):
50 """return the next chunk from stream as a string"""
49 """return the next chunk from stream as a string"""
51 d = readexactly(stream, 4)
50 d = readexactly(stream, 4)
52 l = struct.unpack(b">l", d)[0]
51 l = struct.unpack(b">l", d)[0]
53 if l <= 4:
52 if l <= 4:
54 if l:
53 if l:
55 raise error.Abort(_(b"invalid chunk length %d") % l)
54 raise error.Abort(_(b"invalid chunk length %d") % l)
56 return b""
55 return b""
57 return readexactly(stream, l - 4)
56 return readexactly(stream, l - 4)
58
57
59
58
60 def chunkheader(length):
59 def chunkheader(length):
61 """return a changegroup chunk header (string)"""
60 """return a changegroup chunk header (string)"""
62 return struct.pack(b">l", length + 4)
61 return struct.pack(b">l", length + 4)
63
62
64
63
65 def closechunk():
64 def closechunk():
66 """return a changegroup chunk header (string) for a zero-length chunk"""
65 """return a changegroup chunk header (string) for a zero-length chunk"""
67 return struct.pack(b">l", 0)
66 return struct.pack(b">l", 0)
68
67
69
68
70 def _fileheader(path):
69 def _fileheader(path):
71 """Obtain a changegroup chunk header for a named path."""
70 """Obtain a changegroup chunk header for a named path."""
72 return chunkheader(len(path)) + path
71 return chunkheader(len(path)) + path
73
72
74
73
75 def writechunks(ui, chunks, filename, vfs=None):
74 def writechunks(ui, chunks, filename, vfs=None):
76 """Write chunks to a file and return its filename.
75 """Write chunks to a file and return its filename.
77
76
78 The stream is assumed to be a bundle file.
77 The stream is assumed to be a bundle file.
79 Existing files will not be overwritten.
78 Existing files will not be overwritten.
80 If no filename is specified, a temporary file is created.
79 If no filename is specified, a temporary file is created.
81 """
80 """
82 fh = None
81 fh = None
83 cleanup = None
82 cleanup = None
84 try:
83 try:
85 if filename:
84 if filename:
86 if vfs:
85 if vfs:
87 fh = vfs.open(filename, b"wb")
86 fh = vfs.open(filename, b"wb")
88 else:
87 else:
89 # Increase default buffer size because default is usually
88 # Increase default buffer size because default is usually
90 # small (4k is common on Linux).
89 # small (4k is common on Linux).
91 fh = open(filename, b"wb", 131072)
90 fh = open(filename, b"wb", 131072)
92 else:
91 else:
93 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
94 fh = os.fdopen(fd, "wb")
93 fh = os.fdopen(fd, "wb")
95 cleanup = filename
94 cleanup = filename
96 for c in chunks:
95 for c in chunks:
97 fh.write(c)
96 fh.write(c)
98 cleanup = None
97 cleanup = None
99 return filename
98 return filename
100 finally:
99 finally:
101 if fh is not None:
100 if fh is not None:
102 fh.close()
101 fh.close()
103 if cleanup is not None:
102 if cleanup is not None:
104 if filename and vfs:
103 if filename and vfs:
105 vfs.unlink(cleanup)
104 vfs.unlink(cleanup)
106 else:
105 else:
107 os.unlink(cleanup)
106 os.unlink(cleanup)
108
107
109
108
110 class cg1unpacker(object):
109 class cg1unpacker(object):
111 """Unpacker for cg1 changegroup streams.
110 """Unpacker for cg1 changegroup streams.
112
111
113 A changegroup unpacker handles the framing of the revision data in
112 A changegroup unpacker handles the framing of the revision data in
114 the wire format. Most consumers will want to use the apply()
113 the wire format. Most consumers will want to use the apply()
115 method to add the changes from the changegroup to a repository.
114 method to add the changes from the changegroup to a repository.
116
115
117 If you're forwarding a changegroup unmodified to another consumer,
116 If you're forwarding a changegroup unmodified to another consumer,
118 use getchunks(), which returns an iterator of changegroup
117 use getchunks(), which returns an iterator of changegroup
119 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
120 data stream has ended by observing the end of the changegroup.
119 data stream has ended by observing the end of the changegroup.
121
120
122 deltachunk() is useful only if you're applying delta data. Most
121 deltachunk() is useful only if you're applying delta data. Most
123 consumers should prefer apply() instead.
122 consumers should prefer apply() instead.
124
123
125 A few other public methods exist. Those are used only for
124 A few other public methods exist. Those are used only for
126 bundlerepo and some debug commands - their use is discouraged.
125 bundlerepo and some debug commands - their use is discouraged.
127 """
126 """
128
127
129 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
130 deltaheadersize = deltaheader.size
129 deltaheadersize = deltaheader.size
131 version = b'01'
130 version = b'01'
132 _grouplistcount = 1 # One list of files after the manifests
131 _grouplistcount = 1 # One list of files after the manifests
133
132
134 def __init__(self, fh, alg, extras=None):
133 def __init__(self, fh, alg, extras=None):
135 if alg is None:
134 if alg is None:
136 alg = b'UN'
135 alg = b'UN'
137 if alg not in util.compengines.supportedbundletypes:
136 if alg not in util.compengines.supportedbundletypes:
138 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
139 if alg == b'BZ':
138 if alg == b'BZ':
140 alg = b'_truncatedBZ'
139 alg = b'_truncatedBZ'
141
140
142 compengine = util.compengines.forbundletype(alg)
141 compengine = util.compengines.forbundletype(alg)
143 self._stream = compengine.decompressorreader(fh)
142 self._stream = compengine.decompressorreader(fh)
144 self._type = alg
143 self._type = alg
145 self.extras = extras or {}
144 self.extras = extras or {}
146 self.callback = None
145 self.callback = None
147
146
148 # These methods (compressed, read, seek, tell) all appear to only
147 # These methods (compressed, read, seek, tell) all appear to only
149 # 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.
150 def compressed(self):
149 def compressed(self):
151 return self._type is not None and self._type != b'UN'
150 return self._type is not None and self._type != b'UN'
152
151
153 def read(self, l):
152 def read(self, l):
154 return self._stream.read(l)
153 return self._stream.read(l)
155
154
156 def seek(self, pos):
155 def seek(self, pos):
157 return self._stream.seek(pos)
156 return self._stream.seek(pos)
158
157
159 def tell(self):
158 def tell(self):
160 return self._stream.tell()
159 return self._stream.tell()
161
160
162 def close(self):
161 def close(self):
163 return self._stream.close()
162 return self._stream.close()
164
163
165 def _chunklength(self):
164 def _chunklength(self):
166 d = readexactly(self._stream, 4)
165 d = readexactly(self._stream, 4)
167 l = struct.unpack(b">l", d)[0]
166 l = struct.unpack(b">l", d)[0]
168 if l <= 4:
167 if l <= 4:
169 if l:
168 if l:
170 raise error.Abort(_(b"invalid chunk length %d") % l)
169 raise error.Abort(_(b"invalid chunk length %d") % l)
171 return 0
170 return 0
172 if self.callback:
171 if self.callback:
173 self.callback()
172 self.callback()
174 return l - 4
173 return l - 4
175
174
176 def changelogheader(self):
175 def changelogheader(self):
177 """v10 does not have a changelog header chunk"""
176 """v10 does not have a changelog header chunk"""
178 return {}
177 return {}
179
178
180 def manifestheader(self):
179 def manifestheader(self):
181 """v10 does not have a manifest header chunk"""
180 """v10 does not have a manifest header chunk"""
182 return {}
181 return {}
183
182
184 def filelogheader(self):
183 def filelogheader(self):
185 """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"""
186 l = self._chunklength()
185 l = self._chunklength()
187 if not l:
186 if not l:
188 return {}
187 return {}
189 fname = readexactly(self._stream, l)
188 fname = readexactly(self._stream, l)
190 return {b'filename': fname}
189 return {b'filename': fname}
191
190
192 def _deltaheader(self, headertuple, prevnode):
191 def _deltaheader(self, headertuple, prevnode):
193 node, p1, p2, cs = headertuple
192 node, p1, p2, cs = headertuple
194 if prevnode is None:
193 if prevnode is None:
195 deltabase = p1
194 deltabase = p1
196 else:
195 else:
197 deltabase = prevnode
196 deltabase = prevnode
198 flags = 0
197 flags = 0
199 protocol_flags = 0
198 protocol_flags = 0
200 return node, p1, p2, deltabase, cs, flags, protocol_flags
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
201
200
202 def deltachunk(self, prevnode):
201 def deltachunk(self, prevnode):
203 l = self._chunklength()
202 l = self._chunklength()
204 if not l:
203 if not l:
205 return {}
204 return {}
206 headerdata = readexactly(self._stream, self.deltaheadersize)
205 headerdata = readexactly(self._stream, self.deltaheadersize)
207 header = self.deltaheader.unpack(headerdata)
206 header = self.deltaheader.unpack(headerdata)
208 delta = readexactly(self._stream, l - self.deltaheadersize)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
209 header = self._deltaheader(header, prevnode)
208 header = self._deltaheader(header, prevnode)
210 node, p1, p2, deltabase, cs, flags, protocol_flags = header
209 node, p1, p2, deltabase, cs, flags, protocol_flags = header
211 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
210 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
212
211
213 def getchunks(self):
212 def getchunks(self):
214 """returns all the chunks contains in the bundle
213 """returns all the chunks contains in the bundle
215
214
216 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
217 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
218 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.
219 """
218 """
220 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
219 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
221 # 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:
222 # changelog, manifestlog, a list of tree manifestlogs, and a list of
221 # changelog, manifestlog, a list of tree manifestlogs, and a list of
223 # filelogs.
222 # filelogs.
224 #
223 #
225 # Changelog and manifestlog parts are terminated with empty chunks. The
224 # Changelog and manifestlog parts are terminated with empty chunks. The
226 # 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
227 # 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
228 # 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
229 # 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
230 # chunk that was proceeded by no non-empty chunks.
229 # chunk that was proceeded by no non-empty chunks.
231
230
232 parts = 0
231 parts = 0
233 while parts < 2 + self._grouplistcount:
232 while parts < 2 + self._grouplistcount:
234 noentries = True
233 noentries = True
235 while True:
234 while True:
236 chunk = getchunk(self)
235 chunk = getchunk(self)
237 if not chunk:
236 if not chunk:
238 # The first two empty chunks represent the end of the
237 # The first two empty chunks represent the end of the
239 # changelog and the manifestlog portions. The remaining
238 # changelog and the manifestlog portions. The remaining
240 # empty chunks represent either A) the end of individual
239 # empty chunks represent either A) the end of individual
241 # 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
242 # 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
243 # were no entries (i.e. noentries is True).
242 # were no entries (i.e. noentries is True).
244 if parts < 2:
243 if parts < 2:
245 parts += 1
244 parts += 1
246 elif noentries:
245 elif noentries:
247 parts += 1
246 parts += 1
248 break
247 break
249 noentries = False
248 noentries = False
250 yield chunkheader(len(chunk))
249 yield chunkheader(len(chunk))
251 pos = 0
250 pos = 0
252 while pos < len(chunk):
251 while pos < len(chunk):
253 next = pos + 2 ** 20
252 next = pos + 2 ** 20
254 yield chunk[pos:next]
253 yield chunk[pos:next]
255 pos = next
254 pos = next
256 yield closechunk()
255 yield closechunk()
257
256
258 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
259 self.callback = prog.increment
258 self.callback = prog.increment
260 # no need to check for empty manifest group here:
259 # no need to check for empty manifest group here:
261 # 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,
262 # no new manifest will be created and the manifest group will
261 # no new manifest will be created and the manifest group will
263 # be empty during the pull
262 # be empty during the pull
264 self.manifestheader()
263 self.manifestheader()
265 deltas = self.deltaiter()
264 deltas = self.deltaiter()
266 storage = repo.manifestlog.getstorage(b'')
265 storage = repo.manifestlog.getstorage(b'')
267 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
268 prog.complete()
267 prog.complete()
269 self.callback = None
268 self.callback = None
270
269
271 def apply(
270 def apply(
272 self,
271 self,
273 repo,
272 repo,
274 tr,
273 tr,
275 srctype,
274 srctype,
276 url,
275 url,
277 targetphase=phases.draft,
276 targetphase=phases.draft,
278 expectedtotal=None,
277 expectedtotal=None,
279 sidedata_categories=None,
278 sidedata_categories=None,
280 ):
279 ):
281 """Add the changegroup returned by source.read() to this repo.
280 """Add the changegroup returned by source.read() to this repo.
282 srctype is a string like 'push', 'pull', or 'unbundle'. url is
281 srctype is a string like 'push', 'pull', or 'unbundle'. url is
283 the URL of the repo where this changegroup is coming from.
282 the URL of the repo where this changegroup is coming from.
284
283
285 Return an integer summarizing the change to this repo:
284 Return an integer summarizing the change to this repo:
286 - nothing changed or no source: 0
285 - nothing changed or no source: 0
287 - more heads than before: 1+added heads (2..n)
286 - more heads than before: 1+added heads (2..n)
288 - fewer heads than before: -1-removed heads (-2..-n)
287 - fewer heads than before: -1-removed heads (-2..-n)
289 - number of heads stays the same: 1
288 - number of heads stays the same: 1
290
289
291 `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
292 categories.
291 categories.
293 """
292 """
294 repo = repo.unfiltered()
293 repo = repo.unfiltered()
295
294
296 # 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
297 # the same categories, then we simply don't do anything.
296 # the same categories, then we simply don't do anything.
298 adding_sidedata = (
297 adding_sidedata = (
299 requirements.REVLOGV2_REQUIREMENT in repo.requirements
298 requirements.REVLOGV2_REQUIREMENT in repo.requirements
300 and self.version == b'04'
299 and self.version == b'04'
301 and srctype == b'pull'
300 and srctype == b'pull'
302 )
301 )
303 if adding_sidedata:
302 if adding_sidedata:
304 sidedata_helpers = get_sidedata_helpers(
303 sidedata_helpers = sidedatamod.get_sidedata_helpers(
305 repo,
304 repo,
306 sidedata_categories or set(),
305 sidedata_categories or set(),
307 pull=True,
306 pull=True,
308 )
307 )
309 else:
308 else:
310 sidedata_helpers = None
309 sidedata_helpers = None
311
310
312 def csmap(x):
311 def csmap(x):
313 repo.ui.debug(b"add changeset %s\n" % short(x))
312 repo.ui.debug(b"add changeset %s\n" % short(x))
314 return len(cl)
313 return len(cl)
315
314
316 def revmap(x):
315 def revmap(x):
317 return cl.rev(x)
316 return cl.rev(x)
318
317
319 try:
318 try:
320 # The transaction may already carry source information. In this
319 # The transaction may already carry source information. In this
321 # case we use the top level data. We overwrite the argument
320 # case we use the top level data. We overwrite the argument
322 # 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)
323 # in this function.
322 # in this function.
324 srctype = tr.hookargs.setdefault(b'source', srctype)
323 srctype = tr.hookargs.setdefault(b'source', srctype)
325 tr.hookargs.setdefault(b'url', url)
324 tr.hookargs.setdefault(b'url', url)
326 repo.hook(
325 repo.hook(
327 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
326 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
328 )
327 )
329
328
330 # write changelog data to temp files so concurrent readers
329 # write changelog data to temp files so concurrent readers
331 # will not see an inconsistent view
330 # will not see an inconsistent view
332 cl = repo.changelog
331 cl = repo.changelog
333 cl.delayupdate(tr)
332 cl.delayupdate(tr)
334 oldheads = set(cl.heads())
333 oldheads = set(cl.heads())
335
334
336 trp = weakref.proxy(tr)
335 trp = weakref.proxy(tr)
337 # pull off the changeset group
336 # pull off the changeset group
338 repo.ui.status(_(b"adding changesets\n"))
337 repo.ui.status(_(b"adding changesets\n"))
339 clstart = len(cl)
338 clstart = len(cl)
340 progress = repo.ui.makeprogress(
339 progress = repo.ui.makeprogress(
341 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
340 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
342 )
341 )
343 self.callback = progress.increment
342 self.callback = progress.increment
344
343
345 efilesset = set()
344 efilesset = set()
346 duprevs = []
345 duprevs = []
347
346
348 def ondupchangelog(cl, rev):
347 def ondupchangelog(cl, rev):
349 if rev < clstart:
348 if rev < clstart:
350 duprevs.append(rev)
349 duprevs.append(rev)
351
350
352 def onchangelog(cl, rev):
351 def onchangelog(cl, rev):
353 ctx = cl.changelogrevision(rev)
352 ctx = cl.changelogrevision(rev)
354 efilesset.update(ctx.files)
353 efilesset.update(ctx.files)
355 repo.register_changeset(rev, ctx)
354 repo.register_changeset(rev, ctx)
356
355
357 self.changelogheader()
356 self.changelogheader()
358 deltas = self.deltaiter()
357 deltas = self.deltaiter()
359 if not cl.addgroup(
358 if not cl.addgroup(
360 deltas,
359 deltas,
361 csmap,
360 csmap,
362 trp,
361 trp,
363 alwayscache=True,
362 alwayscache=True,
364 addrevisioncb=onchangelog,
363 addrevisioncb=onchangelog,
365 duplicaterevisioncb=ondupchangelog,
364 duplicaterevisioncb=ondupchangelog,
366 ):
365 ):
367 repo.ui.develwarn(
366 repo.ui.develwarn(
368 b'applied empty changelog from changegroup',
367 b'applied empty changelog from changegroup',
369 config=b'warn-empty-changegroup',
368 config=b'warn-empty-changegroup',
370 )
369 )
371 efiles = len(efilesset)
370 efiles = len(efilesset)
372 clend = len(cl)
371 clend = len(cl)
373 changesets = clend - clstart
372 changesets = clend - clstart
374 progress.complete()
373 progress.complete()
375 del deltas
374 del deltas
376 # TODO Python 2.7 removal
375 # TODO Python 2.7 removal
377 # del efilesset
376 # del efilesset
378 efilesset = None
377 efilesset = None
379 self.callback = None
378 self.callback = None
380
379
381 # 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
382 # range of new revisions for sidedata rewrite.
381 # range of new revisions for sidedata rewrite.
383 # TODO do something more efficient than keeping the reference to
382 # TODO do something more efficient than keeping the reference to
384 # the revlogs, especially memory-wise.
383 # the revlogs, especially memory-wise.
385 touched_manifests = {}
384 touched_manifests = {}
386 touched_filelogs = {}
385 touched_filelogs = {}
387
386
388 # pull off the manifest group
387 # pull off the manifest group
389 repo.ui.status(_(b"adding manifests\n"))
388 repo.ui.status(_(b"adding manifests\n"))
390 # 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
391 # changesets.
390 # changesets.
392 progress = repo.ui.makeprogress(
391 progress = repo.ui.makeprogress(
393 _(b'manifests'), unit=_(b'chunks'), total=changesets
392 _(b'manifests'), unit=_(b'chunks'), total=changesets
394 )
393 )
395 on_manifest_rev = None
394 on_manifest_rev = None
396 if sidedata_helpers:
395 if sidedata_helpers:
397 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
396 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
398
397
399 def on_manifest_rev(manifest, rev):
398 def on_manifest_rev(manifest, rev):
400 range = touched_manifests.get(manifest)
399 range = touched_manifests.get(manifest)
401 if not range:
400 if not range:
402 touched_manifests[manifest] = (rev, rev)
401 touched_manifests[manifest] = (rev, rev)
403 else:
402 else:
404 assert rev == range[1] + 1
403 assert rev == range[1] + 1
405 touched_manifests[manifest] = (range[0], rev)
404 touched_manifests[manifest] = (range[0], rev)
406
405
407 self._unpackmanifests(
406 self._unpackmanifests(
408 repo,
407 repo,
409 revmap,
408 revmap,
410 trp,
409 trp,
411 progress,
410 progress,
412 addrevisioncb=on_manifest_rev,
411 addrevisioncb=on_manifest_rev,
413 )
412 )
414
413
415 needfiles = {}
414 needfiles = {}
416 if repo.ui.configbool(b'server', b'validate'):
415 if repo.ui.configbool(b'server', b'validate'):
417 cl = repo.changelog
416 cl = repo.changelog
418 ml = repo.manifestlog
417 ml = repo.manifestlog
419 # validate incoming csets have their manifests
418 # validate incoming csets have their manifests
420 for cset in pycompat.xrange(clstart, clend):
419 for cset in pycompat.xrange(clstart, clend):
421 mfnode = cl.changelogrevision(cset).manifest
420 mfnode = cl.changelogrevision(cset).manifest
422 mfest = ml[mfnode].readdelta()
421 mfest = ml[mfnode].readdelta()
423 # store file nodes we must see
422 # store file nodes we must see
424 for f, n in pycompat.iteritems(mfest):
423 for f, n in pycompat.iteritems(mfest):
425 needfiles.setdefault(f, set()).add(n)
424 needfiles.setdefault(f, set()).add(n)
426
425
427 on_filelog_rev = None
426 on_filelog_rev = None
428 if sidedata_helpers:
427 if sidedata_helpers:
429 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
428 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
430
429
431 def on_filelog_rev(filelog, rev):
430 def on_filelog_rev(filelog, rev):
432 range = touched_filelogs.get(filelog)
431 range = touched_filelogs.get(filelog)
433 if not range:
432 if not range:
434 touched_filelogs[filelog] = (rev, rev)
433 touched_filelogs[filelog] = (rev, rev)
435 else:
434 else:
436 assert rev == range[1] + 1
435 assert rev == range[1] + 1
437 touched_filelogs[filelog] = (range[0], rev)
436 touched_filelogs[filelog] = (range[0], rev)
438
437
439 # process the files
438 # process the files
440 repo.ui.status(_(b"adding file changes\n"))
439 repo.ui.status(_(b"adding file changes\n"))
441 newrevs, newfiles = _addchangegroupfiles(
440 newrevs, newfiles = _addchangegroupfiles(
442 repo,
441 repo,
443 self,
442 self,
444 revmap,
443 revmap,
445 trp,
444 trp,
446 efiles,
445 efiles,
447 needfiles,
446 needfiles,
448 addrevisioncb=on_filelog_rev,
447 addrevisioncb=on_filelog_rev,
449 )
448 )
450
449
451 if sidedata_helpers:
450 if sidedata_helpers:
452 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
451 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
453 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
452 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
454 for mf, (startrev, endrev) in touched_manifests.items():
453 for mf, (startrev, endrev) in touched_manifests.items():
455 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
454 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
456 for fl, (startrev, endrev) in touched_filelogs.items():
455 for fl, (startrev, endrev) in touched_filelogs.items():
457 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
456 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
458
457
459 # making sure the value exists
458 # making sure the value exists
460 tr.changes.setdefault(b'changegroup-count-changesets', 0)
459 tr.changes.setdefault(b'changegroup-count-changesets', 0)
461 tr.changes.setdefault(b'changegroup-count-revisions', 0)
460 tr.changes.setdefault(b'changegroup-count-revisions', 0)
462 tr.changes.setdefault(b'changegroup-count-files', 0)
461 tr.changes.setdefault(b'changegroup-count-files', 0)
463 tr.changes.setdefault(b'changegroup-count-heads', 0)
462 tr.changes.setdefault(b'changegroup-count-heads', 0)
464
463
465 # some code use bundle operation for internal purpose. They usually
464 # some code use bundle operation for internal purpose. They usually
466 # 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
467 # 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
468 # ui.quiet has not direct effect on the output.
467 # ui.quiet has not direct effect on the output.
469 #
468 #
470 # 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
471 # the change if `quiet` is set. We should probably move to
470 # the change if `quiet` is set. We should probably move to
472 # 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
473 # of transaction report" to pass tests.
472 # of transaction report" to pass tests.
474 if not repo.ui.quiet:
473 if not repo.ui.quiet:
475 tr.changes[b'changegroup-count-changesets'] += changesets
474 tr.changes[b'changegroup-count-changesets'] += changesets
476 tr.changes[b'changegroup-count-revisions'] += newrevs
475 tr.changes[b'changegroup-count-revisions'] += newrevs
477 tr.changes[b'changegroup-count-files'] += newfiles
476 tr.changes[b'changegroup-count-files'] += newfiles
478
477
479 deltaheads = 0
478 deltaheads = 0
480 if oldheads:
479 if oldheads:
481 heads = cl.heads()
480 heads = cl.heads()
482 deltaheads += len(heads) - len(oldheads)
481 deltaheads += len(heads) - len(oldheads)
483 for h in heads:
482 for h in heads:
484 if h not in oldheads and repo[h].closesbranch():
483 if h not in oldheads and repo[h].closesbranch():
485 deltaheads -= 1
484 deltaheads -= 1
486
485
487 # see previous comment about checking ui.quiet
486 # see previous comment about checking ui.quiet
488 if not repo.ui.quiet:
487 if not repo.ui.quiet:
489 tr.changes[b'changegroup-count-heads'] += deltaheads
488 tr.changes[b'changegroup-count-heads'] += deltaheads
490 repo.invalidatevolatilesets()
489 repo.invalidatevolatilesets()
491
490
492 if changesets > 0:
491 if changesets > 0:
493 if b'node' not in tr.hookargs:
492 if b'node' not in tr.hookargs:
494 tr.hookargs[b'node'] = hex(cl.node(clstart))
493 tr.hookargs[b'node'] = hex(cl.node(clstart))
495 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
494 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
496 hookargs = dict(tr.hookargs)
495 hookargs = dict(tr.hookargs)
497 else:
496 else:
498 hookargs = dict(tr.hookargs)
497 hookargs = dict(tr.hookargs)
499 hookargs[b'node'] = hex(cl.node(clstart))
498 hookargs[b'node'] = hex(cl.node(clstart))
500 hookargs[b'node_last'] = hex(cl.node(clend - 1))
499 hookargs[b'node_last'] = hex(cl.node(clend - 1))
501 repo.hook(
500 repo.hook(
502 b'pretxnchangegroup',
501 b'pretxnchangegroup',
503 throw=True,
502 throw=True,
504 **pycompat.strkwargs(hookargs)
503 **pycompat.strkwargs(hookargs)
505 )
504 )
506
505
507 added = pycompat.xrange(clstart, clend)
506 added = pycompat.xrange(clstart, clend)
508 phaseall = None
507 phaseall = None
509 if srctype in (b'push', b'serve'):
508 if srctype in (b'push', b'serve'):
510 # Old servers can not push the boundary themselves.
509 # Old servers can not push the boundary themselves.
511 # New servers won't push the boundary if changeset already
510 # New servers won't push the boundary if changeset already
512 # exists locally as secret
511 # exists locally as secret
513 #
512 #
514 # 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
515 # the bundle
514 # the bundle
516 if repo.publishing():
515 if repo.publishing():
517 targetphase = phaseall = phases.public
516 targetphase = phaseall = phases.public
518 else:
517 else:
519 # closer target phase computation
518 # closer target phase computation
520
519
521 # Those changesets have been pushed from the
520 # Those changesets have been pushed from the
522 # outside, their phases are going to be pushed
521 # outside, their phases are going to be pushed
523 # alongside. Therefor `targetphase` is
522 # alongside. Therefor `targetphase` is
524 # ignored.
523 # ignored.
525 targetphase = phaseall = phases.draft
524 targetphase = phaseall = phases.draft
526 if added:
525 if added:
527 phases.registernew(repo, tr, targetphase, added)
526 phases.registernew(repo, tr, targetphase, added)
528 if phaseall is not None:
527 if phaseall is not None:
529 if duprevs:
528 if duprevs:
530 duprevs.extend(added)
529 duprevs.extend(added)
531 else:
530 else:
532 duprevs = added
531 duprevs = added
533 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
532 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
534 duprevs = []
533 duprevs = []
535
534
536 if changesets > 0:
535 if changesets > 0:
537
536
538 def runhooks(unused_success):
537 def runhooks(unused_success):
539 # These hooks run when the lock releases, not when the
538 # These hooks run when the lock releases, not when the
540 # transaction closes. So it's possible for the changelog
539 # transaction closes. So it's possible for the changelog
541 # to have changed since we last saw it.
540 # to have changed since we last saw it.
542 if clstart >= len(repo):
541 if clstart >= len(repo):
543 return
542 return
544
543
545 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
544 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
546
545
547 for rev in added:
546 for rev in added:
548 args = hookargs.copy()
547 args = hookargs.copy()
549 args[b'node'] = hex(cl.node(rev))
548 args[b'node'] = hex(cl.node(rev))
550 del args[b'node_last']
549 del args[b'node_last']
551 repo.hook(b"incoming", **pycompat.strkwargs(args))
550 repo.hook(b"incoming", **pycompat.strkwargs(args))
552
551
553 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]
554 repo.ui.log(
553 repo.ui.log(
555 b"incoming",
554 b"incoming",
556 b"%d incoming changes - new heads: %s\n",
555 b"%d incoming changes - new heads: %s\n",
557 len(added),
556 len(added),
558 b', '.join([hex(c[:6]) for c in newheads]),
557 b', '.join([hex(c[:6]) for c in newheads]),
559 )
558 )
560
559
561 tr.addpostclose(
560 tr.addpostclose(
562 b'changegroup-runhooks-%020i' % clstart,
561 b'changegroup-runhooks-%020i' % clstart,
563 lambda tr: repo._afterlock(runhooks),
562 lambda tr: repo._afterlock(runhooks),
564 )
563 )
565 finally:
564 finally:
566 repo.ui.flush()
565 repo.ui.flush()
567 # never return 0 here:
566 # never return 0 here:
568 if deltaheads < 0:
567 if deltaheads < 0:
569 ret = deltaheads - 1
568 ret = deltaheads - 1
570 else:
569 else:
571 ret = deltaheads + 1
570 ret = deltaheads + 1
572 return ret
571 return ret
573
572
574 def deltaiter(self):
573 def deltaiter(self):
575 """
574 """
576 returns an iterator of the deltas in this changegroup
575 returns an iterator of the deltas in this changegroup
577
576
578 Useful for passing to the underlying storage system to be stored.
577 Useful for passing to the underlying storage system to be stored.
579 """
578 """
580 chain = None
579 chain = None
581 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
580 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
582 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
581 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
583 yield chunkdata
582 yield chunkdata
584 chain = chunkdata[0]
583 chain = chunkdata[0]
585
584
586
585
587 class cg2unpacker(cg1unpacker):
586 class cg2unpacker(cg1unpacker):
588 """Unpacker for cg2 streams.
587 """Unpacker for cg2 streams.
589
588
590 cg2 streams add support for generaldelta, so the delta header
589 cg2 streams add support for generaldelta, so the delta header
591 format is slightly different. All other features about the data
590 format is slightly different. All other features about the data
592 remain the same.
591 remain the same.
593 """
592 """
594
593
595 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
594 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
596 deltaheadersize = deltaheader.size
595 deltaheadersize = deltaheader.size
597 version = b'02'
596 version = b'02'
598
597
599 def _deltaheader(self, headertuple, prevnode):
598 def _deltaheader(self, headertuple, prevnode):
600 node, p1, p2, deltabase, cs = headertuple
599 node, p1, p2, deltabase, cs = headertuple
601 flags = 0
600 flags = 0
602 protocol_flags = 0
601 protocol_flags = 0
603 return node, p1, p2, deltabase, cs, flags, protocol_flags
602 return node, p1, p2, deltabase, cs, flags, protocol_flags
604
603
605
604
606 class cg3unpacker(cg2unpacker):
605 class cg3unpacker(cg2unpacker):
607 """Unpacker for cg3 streams.
606 """Unpacker for cg3 streams.
608
607
609 cg3 streams add support for exchanging treemanifests and revlog
608 cg3 streams add support for exchanging treemanifests and revlog
610 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
611 separating manifests and files.
610 separating manifests and files.
612 """
611 """
613
612
614 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
613 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
615 deltaheadersize = deltaheader.size
614 deltaheadersize = deltaheader.size
616 version = b'03'
615 version = b'03'
617 _grouplistcount = 2 # One list of manifests and one list of files
616 _grouplistcount = 2 # One list of manifests and one list of files
618
617
619 def _deltaheader(self, headertuple, prevnode):
618 def _deltaheader(self, headertuple, prevnode):
620 node, p1, p2, deltabase, cs, flags = headertuple
619 node, p1, p2, deltabase, cs, flags = headertuple
621 protocol_flags = 0
620 protocol_flags = 0
622 return node, p1, p2, deltabase, cs, flags, protocol_flags
621 return node, p1, p2, deltabase, cs, flags, protocol_flags
623
622
624 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
623 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
625 super(cg3unpacker, self)._unpackmanifests(
624 super(cg3unpacker, self)._unpackmanifests(
626 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
625 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
627 )
626 )
628 for chunkdata in iter(self.filelogheader, {}):
627 for chunkdata in iter(self.filelogheader, {}):
629 # If we get here, there are directory manifests in the changegroup
628 # If we get here, there are directory manifests in the changegroup
630 d = chunkdata[b"filename"]
629 d = chunkdata[b"filename"]
631 repo.ui.debug(b"adding %s revisions\n" % d)
630 repo.ui.debug(b"adding %s revisions\n" % d)
632 deltas = self.deltaiter()
631 deltas = self.deltaiter()
633 if not repo.manifestlog.getstorage(d).addgroup(
632 if not repo.manifestlog.getstorage(d).addgroup(
634 deltas, revmap, trp, addrevisioncb=addrevisioncb
633 deltas, revmap, trp, addrevisioncb=addrevisioncb
635 ):
634 ):
636 raise error.Abort(_(b"received dir revlog group is empty"))
635 raise error.Abort(_(b"received dir revlog group is empty"))
637
636
638
637
639 class cg4unpacker(cg3unpacker):
638 class cg4unpacker(cg3unpacker):
640 """Unpacker for cg4 streams.
639 """Unpacker for cg4 streams.
641
640
642 cg4 streams add support for exchanging sidedata.
641 cg4 streams add support for exchanging sidedata.
643 """
642 """
644
643
645 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
644 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
646 deltaheadersize = deltaheader.size
645 deltaheadersize = deltaheader.size
647 version = b'04'
646 version = b'04'
648
647
649 def _deltaheader(self, headertuple, prevnode):
648 def _deltaheader(self, headertuple, prevnode):
650 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
649 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
651 return node, p1, p2, deltabase, cs, flags, protocol_flags
650 return node, p1, p2, deltabase, cs, flags, protocol_flags
652
651
653 def deltachunk(self, prevnode):
652 def deltachunk(self, prevnode):
654 res = super(cg4unpacker, self).deltachunk(prevnode)
653 res = super(cg4unpacker, self).deltachunk(prevnode)
655 if not res:
654 if not res:
656 return res
655 return res
657
656
658 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
657 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
659
658
660 sidedata = {}
659 sidedata = {}
661 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
660 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
662 sidedata_raw = getchunk(self._stream)
661 sidedata_raw = getchunk(self._stream)
663 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
662 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
664
663
665 return node, p1, p2, cs, deltabase, delta, flags, sidedata
664 return node, p1, p2, cs, deltabase, delta, flags, sidedata
666
665
667
666
668 class headerlessfixup(object):
667 class headerlessfixup(object):
669 def __init__(self, fh, h):
668 def __init__(self, fh, h):
670 self._h = h
669 self._h = h
671 self._fh = fh
670 self._fh = fh
672
671
673 def read(self, n):
672 def read(self, n):
674 if self._h:
673 if self._h:
675 d, self._h = self._h[:n], self._h[n:]
674 d, self._h = self._h[:n], self._h[n:]
676 if len(d) < n:
675 if len(d) < n:
677 d += readexactly(self._fh, n - len(d))
676 d += readexactly(self._fh, n - len(d))
678 return d
677 return d
679 return readexactly(self._fh, n)
678 return readexactly(self._fh, n)
680
679
681
680
682 def _revisiondeltatochunks(repo, delta, headerfn):
681 def _revisiondeltatochunks(repo, delta, headerfn):
683 """Serialize a revisiondelta to changegroup chunks."""
682 """Serialize a revisiondelta to changegroup chunks."""
684
683
685 # The captured revision delta may be encoded as a delta against
684 # The captured revision delta may be encoded as a delta against
686 # a base revision or as a full revision. The changegroup format
685 # a base revision or as a full revision. The changegroup format
687 # requires that everything on the wire be deltas. So for full
686 # requires that everything on the wire be deltas. So for full
688 # revisions, we need to invent a header that says to rewrite
687 # revisions, we need to invent a header that says to rewrite
689 # data.
688 # data.
690
689
691 if delta.delta is not None:
690 if delta.delta is not None:
692 prefix, data = b'', delta.delta
691 prefix, data = b'', delta.delta
693 elif delta.basenode == repo.nullid:
692 elif delta.basenode == repo.nullid:
694 data = delta.revision
693 data = delta.revision
695 prefix = mdiff.trivialdiffheader(len(data))
694 prefix = mdiff.trivialdiffheader(len(data))
696 else:
695 else:
697 data = delta.revision
696 data = delta.revision
698 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
697 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
699
698
700 meta = headerfn(delta)
699 meta = headerfn(delta)
701
700
702 yield chunkheader(len(meta) + len(prefix) + len(data))
701 yield chunkheader(len(meta) + len(prefix) + len(data))
703 yield meta
702 yield meta
704 if prefix:
703 if prefix:
705 yield prefix
704 yield prefix
706 yield data
705 yield data
707
706
708 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
707 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
709 # Need a separate chunk for sidedata to be able to differentiate
708 # Need a separate chunk for sidedata to be able to differentiate
710 # "raw delta" length and sidedata length
709 # "raw delta" length and sidedata length
711 sidedata = delta.sidedata
710 sidedata = delta.sidedata
712 yield chunkheader(len(sidedata))
711 yield chunkheader(len(sidedata))
713 yield sidedata
712 yield sidedata
714
713
715
714
716 def _sortnodesellipsis(store, nodes, cl, lookup):
715 def _sortnodesellipsis(store, nodes, cl, lookup):
717 """Sort nodes for changegroup generation."""
716 """Sort nodes for changegroup generation."""
718 # Ellipses serving mode.
717 # Ellipses serving mode.
719 #
718 #
720 # In a perfect world, we'd generate better ellipsis-ified graphs
719 # In a perfect world, we'd generate better ellipsis-ified graphs
721 # for non-changelog revlogs. In practice, we haven't started doing
720 # for non-changelog revlogs. In practice, we haven't started doing
722 # that yet, so the resulting DAGs for the manifestlog and filelogs
721 # that yet, so the resulting DAGs for the manifestlog and filelogs
723 # are actually full of bogus parentage on all the ellipsis
722 # are actually full of bogus parentage on all the ellipsis
724 # nodes. This has the side effect that, while the contents are
723 # nodes. This has the side effect that, while the contents are
725 # correct, the individual DAGs might be completely out of whack in
724 # correct, the individual DAGs might be completely out of whack in
726 # a case like 882681bc3166 and its ancestors (back about 10
725 # a case like 882681bc3166 and its ancestors (back about 10
727 # revisions or so) in the main hg repo.
726 # revisions or so) in the main hg repo.
728 #
727 #
729 # The one invariant we *know* holds is that the new (potentially
728 # The one invariant we *know* holds is that the new (potentially
730 # 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
731 # order that they're introduced in dramatis personae by the
730 # order that they're introduced in dramatis personae by the
732 # 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
733 # by the order in which they are used by the changelog.
732 # by the order in which they are used by the changelog.
734 key = lambda n: cl.rev(lookup(n))
733 key = lambda n: cl.rev(lookup(n))
735 return sorted(nodes, key=key)
734 return sorted(nodes, key=key)
736
735
737
736
738 def _resolvenarrowrevisioninfo(
737 def _resolvenarrowrevisioninfo(
739 cl,
738 cl,
740 store,
739 store,
741 ischangelog,
740 ischangelog,
742 rev,
741 rev,
743 linkrev,
742 linkrev,
744 linknode,
743 linknode,
745 clrevtolocalrev,
744 clrevtolocalrev,
746 fullclnodes,
745 fullclnodes,
747 precomputedellipsis,
746 precomputedellipsis,
748 ):
747 ):
749 linkparents = precomputedellipsis[linkrev]
748 linkparents = precomputedellipsis[linkrev]
750
749
751 def local(clrev):
750 def local(clrev):
752 """Turn a changelog revnum into a local revnum.
751 """Turn a changelog revnum into a local revnum.
753
752
754 The ellipsis dag is stored as revnums on the changelog,
753 The ellipsis dag is stored as revnums on the changelog,
755 but when we're producing ellipsis entries for
754 but when we're producing ellipsis entries for
756 non-changelog revlogs, we need to turn those numbers into
755 non-changelog revlogs, we need to turn those numbers into
757 something local. This does that for us, and during the
756 something local. This does that for us, and during the
758 changelog sending phase will also expand the stored
757 changelog sending phase will also expand the stored
759 mappings as needed.
758 mappings as needed.
760 """
759 """
761 if clrev == nullrev:
760 if clrev == nullrev:
762 return nullrev
761 return nullrev
763
762
764 if ischangelog:
763 if ischangelog:
765 return clrev
764 return clrev
766
765
767 # Walk the ellipsis-ized changelog breadth-first looking for a
766 # Walk the ellipsis-ized changelog breadth-first looking for a
768 # change that has been linked from the current revlog.
767 # change that has been linked from the current revlog.
769 #
768 #
770 # 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
771 # as all relevant changelog entries are relevant to the flat
770 # as all relevant changelog entries are relevant to the flat
772 # manifest.
771 # manifest.
773 #
772 #
774 # For a filelog or tree manifest dirlog however not every changelog
773 # For a filelog or tree manifest dirlog however not every changelog
775 # 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
776 # nodes even after ellipsis-izing.
775 # nodes even after ellipsis-izing.
777 walk = [clrev]
776 walk = [clrev]
778 while walk:
777 while walk:
779 p = walk[0]
778 p = walk[0]
780 walk = walk[1:]
779 walk = walk[1:]
781 if p in clrevtolocalrev:
780 if p in clrevtolocalrev:
782 return clrevtolocalrev[p]
781 return clrevtolocalrev[p]
783 elif p in fullclnodes:
782 elif p in fullclnodes:
784 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])
785 elif p in precomputedellipsis:
784 elif p in precomputedellipsis:
786 walk.extend(
785 walk.extend(
787 [pp for pp in precomputedellipsis[p] if pp != nullrev]
786 [pp for pp in precomputedellipsis[p] if pp != nullrev]
788 )
787 )
789 else:
788 else:
790 # In this case, we've got an ellipsis with parents
789 # In this case, we've got an ellipsis with parents
791 # outside the current bundle (likely an
790 # outside the current bundle (likely an
792 # incremental pull). We "know" that we can use the
791 # incremental pull). We "know" that we can use the
793 # value of this same revlog at whatever revision
792 # value of this same revlog at whatever revision
794 # is pointed to by linknode. "Know" is in scare
793 # is pointed to by linknode. "Know" is in scare
795 # quotes because I haven't done enough examination
794 # quotes because I haven't done enough examination
796 # of edge cases to convince myself this is really
795 # of edge cases to convince myself this is really
797 # a fact - it works for all the (admittedly
796 # a fact - it works for all the (admittedly
798 # thorough) cases in our testsuite, but I would be
797 # thorough) cases in our testsuite, but I would be
799 # somewhat unsurprised to find a case in the wild
798 # somewhat unsurprised to find a case in the wild
800 # where this breaks down a bit. That said, I don't
799 # where this breaks down a bit. That said, I don't
801 # know if it would hurt anything.
800 # know if it would hurt anything.
802 for i in pycompat.xrange(rev, 0, -1):
801 for i in pycompat.xrange(rev, 0, -1):
803 if store.linkrev(i) == clrev:
802 if store.linkrev(i) == clrev:
804 return i
803 return i
805 # We failed to resolve a parent for this node, so
804 # We failed to resolve a parent for this node, so
806 # we crash the changegroup construction.
805 # we crash the changegroup construction.
807 raise error.Abort(
806 raise error.Abort(
808 b"unable to resolve parent while packing '%s' %r"
807 b"unable to resolve parent while packing '%s' %r"
809 b' for changeset %r' % (store.indexfile, rev, clrev)
808 b' for changeset %r' % (store.indexfile, rev, clrev)
810 )
809 )
811
810
812 return nullrev
811 return nullrev
813
812
814 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
813 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
815 p1, p2 = nullrev, nullrev
814 p1, p2 = nullrev, nullrev
816 elif len(linkparents) == 1:
815 elif len(linkparents) == 1:
817 (p1,) = sorted(local(p) for p in linkparents)
816 (p1,) = sorted(local(p) for p in linkparents)
818 p2 = nullrev
817 p2 = nullrev
819 else:
818 else:
820 p1, p2 = sorted(local(p) for p in linkparents)
819 p1, p2 = sorted(local(p) for p in linkparents)
821
820
822 p1node, p2node = store.node(p1), store.node(p2)
821 p1node, p2node = store.node(p1), store.node(p2)
823
822
824 return p1node, p2node, linknode
823 return p1node, p2node, linknode
825
824
826
825
827 def deltagroup(
826 def deltagroup(
828 repo,
827 repo,
829 store,
828 store,
830 nodes,
829 nodes,
831 ischangelog,
830 ischangelog,
832 lookup,
831 lookup,
833 forcedeltaparentprev,
832 forcedeltaparentprev,
834 topic=None,
833 topic=None,
835 ellipses=False,
834 ellipses=False,
836 clrevtolocalrev=None,
835 clrevtolocalrev=None,
837 fullclnodes=None,
836 fullclnodes=None,
838 precomputedellipsis=None,
837 precomputedellipsis=None,
839 sidedata_helpers=None,
838 sidedata_helpers=None,
840 ):
839 ):
841 """Calculate deltas for a set of revisions.
840 """Calculate deltas for a set of revisions.
842
841
843 Is a generator of ``revisiondelta`` instances.
842 Is a generator of ``revisiondelta`` instances.
844
843
845 If topic is not None, progress detail will be generated using this
844 If topic is not None, progress detail will be generated using this
846 topic name (e.g. changesets, manifests, etc).
845 topic name (e.g. changesets, manifests, etc).
847
846
848 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
847 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
849 """
848 """
850 if not nodes:
849 if not nodes:
851 return
850 return
852
851
853 cl = repo.changelog
852 cl = repo.changelog
854
853
855 if ischangelog:
854 if ischangelog:
856 # `hg log` shows changesets in storage order. To preserve order
855 # `hg log` shows changesets in storage order. To preserve order
857 # across clones, send out changesets in storage order.
856 # across clones, send out changesets in storage order.
858 nodesorder = b'storage'
857 nodesorder = b'storage'
859 elif ellipses:
858 elif ellipses:
860 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
859 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
861 nodesorder = b'nodes'
860 nodesorder = b'nodes'
862 else:
861 else:
863 nodesorder = None
862 nodesorder = None
864
863
865 # Perform ellipses filtering and revision massaging. We do this before
864 # Perform ellipses filtering and revision massaging. We do this before
866 # emitrevisions() because a) filtering out revisions creates less work
865 # emitrevisions() because a) filtering out revisions creates less work
867 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
866 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
868 # assumptions about delta choices and we would possibly send a delta
867 # assumptions about delta choices and we would possibly send a delta
869 # referencing a missing base revision.
868 # referencing a missing base revision.
870 #
869 #
871 # Also, calling lookup() has side-effects with regards to populating
870 # 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
871 # 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 -
872 # lookup() after the first pass through each node, things can break -
874 # possibly intermittently depending on the python hash seed! For that
873 # possibly intermittently depending on the python hash seed! For that
875 # reason, we store a mapping of all linknodes during the initial node
874 # reason, we store a mapping of all linknodes during the initial node
876 # pass rather than use lookup() on the output side.
875 # pass rather than use lookup() on the output side.
877 if ellipses:
876 if ellipses:
878 filtered = []
877 filtered = []
879 adjustedparents = {}
878 adjustedparents = {}
880 linknodes = {}
879 linknodes = {}
881
880
882 for node in nodes:
881 for node in nodes:
883 rev = store.rev(node)
882 rev = store.rev(node)
884 linknode = lookup(node)
883 linknode = lookup(node)
885 linkrev = cl.rev(linknode)
884 linkrev = cl.rev(linknode)
886 clrevtolocalrev[linkrev] = rev
885 clrevtolocalrev[linkrev] = rev
887
886
888 # If linknode is in fullclnodes, it means the corresponding
887 # If linknode is in fullclnodes, it means the corresponding
889 # changeset was a full changeset and is being sent unaltered.
888 # changeset was a full changeset and is being sent unaltered.
890 if linknode in fullclnodes:
889 if linknode in fullclnodes:
891 linknodes[node] = linknode
890 linknodes[node] = linknode
892
891
893 # If the corresponding changeset wasn't in the set computed
892 # If the corresponding changeset wasn't in the set computed
894 # as relevant to us, it should be dropped outright.
893 # as relevant to us, it should be dropped outright.
895 elif linkrev not in precomputedellipsis:
894 elif linkrev not in precomputedellipsis:
896 continue
895 continue
897
896
898 else:
897 else:
899 # We could probably do this later and avoid the dict
898 # We could probably do this later and avoid the dict
900 # holding state. But it likely doesn't matter.
899 # holding state. But it likely doesn't matter.
901 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
900 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
902 cl,
901 cl,
903 store,
902 store,
904 ischangelog,
903 ischangelog,
905 rev,
904 rev,
906 linkrev,
905 linkrev,
907 linknode,
906 linknode,
908 clrevtolocalrev,
907 clrevtolocalrev,
909 fullclnodes,
908 fullclnodes,
910 precomputedellipsis,
909 precomputedellipsis,
911 )
910 )
912
911
913 adjustedparents[node] = (p1node, p2node)
912 adjustedparents[node] = (p1node, p2node)
914 linknodes[node] = linknode
913 linknodes[node] = linknode
915
914
916 filtered.append(node)
915 filtered.append(node)
917
916
918 nodes = filtered
917 nodes = filtered
919
918
920 # We expect the first pass to be fast, so we only engage the progress
919 # We expect the first pass to be fast, so we only engage the progress
921 # meter for constructing the revision deltas.
920 # meter for constructing the revision deltas.
922 progress = None
921 progress = None
923 if topic is not None:
922 if topic is not None:
924 progress = repo.ui.makeprogress(
923 progress = repo.ui.makeprogress(
925 topic, unit=_(b'chunks'), total=len(nodes)
924 topic, unit=_(b'chunks'), total=len(nodes)
926 )
925 )
927
926
928 configtarget = repo.ui.config(b'devel', b'bundle.delta')
927 configtarget = repo.ui.config(b'devel', b'bundle.delta')
929 if configtarget not in (b'', b'p1', b'full'):
928 if configtarget not in (b'', b'p1', b'full'):
930 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
929 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
931 repo.ui.warn(msg % configtarget)
930 repo.ui.warn(msg % configtarget)
932
931
933 deltamode = repository.CG_DELTAMODE_STD
932 deltamode = repository.CG_DELTAMODE_STD
934 if forcedeltaparentprev:
933 if forcedeltaparentprev:
935 deltamode = repository.CG_DELTAMODE_PREV
934 deltamode = repository.CG_DELTAMODE_PREV
936 elif configtarget == b'p1':
935 elif configtarget == b'p1':
937 deltamode = repository.CG_DELTAMODE_P1
936 deltamode = repository.CG_DELTAMODE_P1
938 elif configtarget == b'full':
937 elif configtarget == b'full':
939 deltamode = repository.CG_DELTAMODE_FULL
938 deltamode = repository.CG_DELTAMODE_FULL
940
939
941 revisions = store.emitrevisions(
940 revisions = store.emitrevisions(
942 nodes,
941 nodes,
943 nodesorder=nodesorder,
942 nodesorder=nodesorder,
944 revisiondata=True,
943 revisiondata=True,
945 assumehaveparentrevisions=not ellipses,
944 assumehaveparentrevisions=not ellipses,
946 deltamode=deltamode,
945 deltamode=deltamode,
947 sidedata_helpers=sidedata_helpers,
946 sidedata_helpers=sidedata_helpers,
948 )
947 )
949
948
950 for i, revision in enumerate(revisions):
949 for i, revision in enumerate(revisions):
951 if progress:
950 if progress:
952 progress.update(i + 1)
951 progress.update(i + 1)
953
952
954 if ellipses:
953 if ellipses:
955 linknode = linknodes[revision.node]
954 linknode = linknodes[revision.node]
956
955
957 if revision.node in adjustedparents:
956 if revision.node in adjustedparents:
958 p1node, p2node = adjustedparents[revision.node]
957 p1node, p2node = adjustedparents[revision.node]
959 revision.p1node = p1node
958 revision.p1node = p1node
960 revision.p2node = p2node
959 revision.p2node = p2node
961 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
960 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
962
961
963 else:
962 else:
964 linknode = lookup(revision.node)
963 linknode = lookup(revision.node)
965
964
966 revision.linknode = linknode
965 revision.linknode = linknode
967 yield revision
966 yield revision
968
967
969 if progress:
968 if progress:
970 progress.complete()
969 progress.complete()
971
970
972
971
973 class cgpacker(object):
972 class cgpacker(object):
974 def __init__(
973 def __init__(
975 self,
974 self,
976 repo,
975 repo,
977 oldmatcher,
976 oldmatcher,
978 matcher,
977 matcher,
979 version,
978 version,
980 builddeltaheader,
979 builddeltaheader,
981 manifestsend,
980 manifestsend,
982 forcedeltaparentprev=False,
981 forcedeltaparentprev=False,
983 bundlecaps=None,
982 bundlecaps=None,
984 ellipses=False,
983 ellipses=False,
985 shallow=False,
984 shallow=False,
986 ellipsisroots=None,
985 ellipsisroots=None,
987 fullnodes=None,
986 fullnodes=None,
988 remote_sidedata=None,
987 remote_sidedata=None,
989 ):
988 ):
990 """Given a source repo, construct a bundler.
989 """Given a source repo, construct a bundler.
991
990
992 oldmatcher is a matcher that matches on files the client already has.
991 oldmatcher is a matcher that matches on files the client already has.
993 These will not be included in the changegroup.
992 These will not be included in the changegroup.
994
993
995 matcher is a matcher that matches on files to include in the
994 matcher is a matcher that matches on files to include in the
996 changegroup. Used to facilitate sparse changegroups.
995 changegroup. Used to facilitate sparse changegroups.
997
996
998 forcedeltaparentprev indicates whether delta parents must be against
997 forcedeltaparentprev indicates whether delta parents must be against
999 the previous revision in a delta group. This should only be used for
998 the previous revision in a delta group. This should only be used for
1000 compatibility with changegroup version 1.
999 compatibility with changegroup version 1.
1001
1000
1002 builddeltaheader is a callable that constructs the header for a group
1001 builddeltaheader is a callable that constructs the header for a group
1003 delta.
1002 delta.
1004
1003
1005 manifestsend is a chunk to send after manifests have been fully emitted.
1004 manifestsend is a chunk to send after manifests have been fully emitted.
1006
1005
1007 ellipses indicates whether ellipsis serving mode is enabled.
1006 ellipses indicates whether ellipsis serving mode is enabled.
1008
1007
1009 bundlecaps is optional and can be used to specify the set of
1008 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
1009 capabilities which can be used to build the bundle. While bundlecaps is
1011 unused in core Mercurial, extensions rely on this feature to communicate
1010 unused in core Mercurial, extensions rely on this feature to communicate
1012 capabilities to customize the changegroup packer.
1011 capabilities to customize the changegroup packer.
1013
1012
1014 shallow indicates whether shallow data might be sent. The packer may
1013 shallow indicates whether shallow data might be sent. The packer may
1015 need to pack file contents not introduced by the changes being packed.
1014 need to pack file contents not introduced by the changes being packed.
1016
1015
1017 fullnodes is the set of changelog nodes which should not be ellipsis
1016 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
1017 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
1018 ellipsis because for very large histories we expect this to be
1020 significantly smaller.
1019 significantly smaller.
1021
1020
1022 remote_sidedata is the set of sidedata categories wanted by the remote.
1021 remote_sidedata is the set of sidedata categories wanted by the remote.
1023 """
1022 """
1024 assert oldmatcher
1023 assert oldmatcher
1025 assert matcher
1024 assert matcher
1026 self._oldmatcher = oldmatcher
1025 self._oldmatcher = oldmatcher
1027 self._matcher = matcher
1026 self._matcher = matcher
1028
1027
1029 self.version = version
1028 self.version = version
1030 self._forcedeltaparentprev = forcedeltaparentprev
1029 self._forcedeltaparentprev = forcedeltaparentprev
1031 self._builddeltaheader = builddeltaheader
1030 self._builddeltaheader = builddeltaheader
1032 self._manifestsend = manifestsend
1031 self._manifestsend = manifestsend
1033 self._ellipses = ellipses
1032 self._ellipses = ellipses
1034
1033
1035 # Set of capabilities we can use to build the bundle.
1034 # Set of capabilities we can use to build the bundle.
1036 if bundlecaps is None:
1035 if bundlecaps is None:
1037 bundlecaps = set()
1036 bundlecaps = set()
1038 self._bundlecaps = bundlecaps
1037 self._bundlecaps = bundlecaps
1039 if remote_sidedata is None:
1038 if remote_sidedata is None:
1040 remote_sidedata = set()
1039 remote_sidedata = set()
1041 self._remote_sidedata = remote_sidedata
1040 self._remote_sidedata = remote_sidedata
1042 self._isshallow = shallow
1041 self._isshallow = shallow
1043 self._fullclnodes = fullnodes
1042 self._fullclnodes = fullnodes
1044
1043
1045 # Maps ellipsis revs to their roots at the changelog level.
1044 # Maps ellipsis revs to their roots at the changelog level.
1046 self._precomputedellipsis = ellipsisroots
1045 self._precomputedellipsis = ellipsisroots
1047
1046
1048 self._repo = repo
1047 self._repo = repo
1049
1048
1050 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1049 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1051 self._verbosenote = self._repo.ui.note
1050 self._verbosenote = self._repo.ui.note
1052 else:
1051 else:
1053 self._verbosenote = lambda s: None
1052 self._verbosenote = lambda s: None
1054
1053
1055 def generate(
1054 def generate(
1056 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1055 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1057 ):
1056 ):
1058 """Yield a sequence of changegroup byte chunks.
1057 """Yield a sequence of changegroup byte chunks.
1059 If changelog is False, changelog data won't be added to changegroup
1058 If changelog is False, changelog data won't be added to changegroup
1060 """
1059 """
1061
1060
1062 repo = self._repo
1061 repo = self._repo
1063 cl = repo.changelog
1062 cl = repo.changelog
1064
1063
1065 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1064 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1066 size = 0
1065 size = 0
1067
1066
1068 sidedata_helpers = None
1067 sidedata_helpers = None
1069 if self.version == b'04':
1068 if self.version == b'04':
1070 remote_sidedata = self._remote_sidedata
1069 remote_sidedata = self._remote_sidedata
1071 if source == b'strip':
1070 if source == b'strip':
1072 # We're our own remote when stripping, get the no-op helpers
1071 # We're our own remote when stripping, get the no-op helpers
1073 # TODO a better approach would be for the strip bundle to
1072 # TODO a better approach would be for the strip bundle to
1074 # correctly advertise its sidedata categories directly.
1073 # correctly advertise its sidedata categories directly.
1075 remote_sidedata = repo._wanted_sidedata
1074 remote_sidedata = repo._wanted_sidedata
1076 sidedata_helpers = get_sidedata_helpers(repo, remote_sidedata)
1075 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1076 repo, remote_sidedata
1077 )
1077
1078
1078 clstate, deltas = self._generatechangelog(
1079 clstate, deltas = self._generatechangelog(
1079 cl,
1080 cl,
1080 clnodes,
1081 clnodes,
1081 generate=changelog,
1082 generate=changelog,
1082 sidedata_helpers=sidedata_helpers,
1083 sidedata_helpers=sidedata_helpers,
1083 )
1084 )
1084 for delta in deltas:
1085 for delta in deltas:
1085 for chunk in _revisiondeltatochunks(
1086 for chunk in _revisiondeltatochunks(
1086 self._repo, delta, self._builddeltaheader
1087 self._repo, delta, self._builddeltaheader
1087 ):
1088 ):
1088 size += len(chunk)
1089 size += len(chunk)
1089 yield chunk
1090 yield chunk
1090
1091
1091 close = closechunk()
1092 close = closechunk()
1092 size += len(close)
1093 size += len(close)
1093 yield closechunk()
1094 yield closechunk()
1094
1095
1095 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1096 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1096
1097
1097 clrevorder = clstate[b'clrevorder']
1098 clrevorder = clstate[b'clrevorder']
1098 manifests = clstate[b'manifests']
1099 manifests = clstate[b'manifests']
1099 changedfiles = clstate[b'changedfiles']
1100 changedfiles = clstate[b'changedfiles']
1100
1101
1101 # We need to make sure that the linkrev in the changegroup refers to
1102 # We need to make sure that the linkrev in the changegroup refers to
1102 # the first changeset that introduced the manifest or file revision.
1103 # the first changeset that introduced the manifest or file revision.
1103 # The fastpath is usually safer than the slowpath, because the filelogs
1104 # The fastpath is usually safer than the slowpath, because the filelogs
1104 # are walked in revlog order.
1105 # are walked in revlog order.
1105 #
1106 #
1106 # When taking the slowpath when the manifest revlog uses generaldelta,
1107 # When taking the slowpath when the manifest revlog uses generaldelta,
1107 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1108 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1108 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1109 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1109 #
1110 #
1110 # When taking the fastpath, we are only vulnerable to reordering
1111 # When taking the fastpath, we are only vulnerable to reordering
1111 # of the changelog itself. The changelog never uses generaldelta and is
1112 # of the changelog itself. The changelog never uses generaldelta and is
1112 # never reordered. To handle this case, we simply take the slowpath,
1113 # never reordered. To handle this case, we simply take the slowpath,
1113 # which already has the 'clrevorder' logic. This was also fixed in
1114 # which already has the 'clrevorder' logic. This was also fixed in
1114 # cc0ff93d0c0c.
1115 # cc0ff93d0c0c.
1115
1116
1116 # Treemanifests don't work correctly with fastpathlinkrev
1117 # Treemanifests don't work correctly with fastpathlinkrev
1117 # either, because we don't discover which directory nodes to
1118 # either, because we don't discover which directory nodes to
1118 # send along with files. This could probably be fixed.
1119 # send along with files. This could probably be fixed.
1119 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1120 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1120
1121
1121 fnodes = {} # needed file nodes
1122 fnodes = {} # needed file nodes
1122
1123
1123 size = 0
1124 size = 0
1124 it = self.generatemanifests(
1125 it = self.generatemanifests(
1125 commonrevs,
1126 commonrevs,
1126 clrevorder,
1127 clrevorder,
1127 fastpathlinkrev,
1128 fastpathlinkrev,
1128 manifests,
1129 manifests,
1129 fnodes,
1130 fnodes,
1130 source,
1131 source,
1131 clstate[b'clrevtomanifestrev'],
1132 clstate[b'clrevtomanifestrev'],
1132 sidedata_helpers=sidedata_helpers,
1133 sidedata_helpers=sidedata_helpers,
1133 )
1134 )
1134
1135
1135 for tree, deltas in it:
1136 for tree, deltas in it:
1136 if tree:
1137 if tree:
1137 assert self.version in (b'03', b'04')
1138 assert self.version in (b'03', b'04')
1138 chunk = _fileheader(tree)
1139 chunk = _fileheader(tree)
1139 size += len(chunk)
1140 size += len(chunk)
1140 yield chunk
1141 yield chunk
1141
1142
1142 for delta in deltas:
1143 for delta in deltas:
1143 chunks = _revisiondeltatochunks(
1144 chunks = _revisiondeltatochunks(
1144 self._repo, delta, self._builddeltaheader
1145 self._repo, delta, self._builddeltaheader
1145 )
1146 )
1146 for chunk in chunks:
1147 for chunk in chunks:
1147 size += len(chunk)
1148 size += len(chunk)
1148 yield chunk
1149 yield chunk
1149
1150
1150 close = closechunk()
1151 close = closechunk()
1151 size += len(close)
1152 size += len(close)
1152 yield close
1153 yield close
1153
1154
1154 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1155 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1155 yield self._manifestsend
1156 yield self._manifestsend
1156
1157
1157 mfdicts = None
1158 mfdicts = None
1158 if self._ellipses and self._isshallow:
1159 if self._ellipses and self._isshallow:
1159 mfdicts = [
1160 mfdicts = [
1160 (repo.manifestlog[n].read(), lr)
1161 (repo.manifestlog[n].read(), lr)
1161 for (n, lr) in pycompat.iteritems(manifests)
1162 for (n, lr) in pycompat.iteritems(manifests)
1162 ]
1163 ]
1163
1164
1164 manifests.clear()
1165 manifests.clear()
1165 clrevs = {cl.rev(x) for x in clnodes}
1166 clrevs = {cl.rev(x) for x in clnodes}
1166
1167
1167 it = self.generatefiles(
1168 it = self.generatefiles(
1168 changedfiles,
1169 changedfiles,
1169 commonrevs,
1170 commonrevs,
1170 source,
1171 source,
1171 mfdicts,
1172 mfdicts,
1172 fastpathlinkrev,
1173 fastpathlinkrev,
1173 fnodes,
1174 fnodes,
1174 clrevs,
1175 clrevs,
1175 sidedata_helpers=sidedata_helpers,
1176 sidedata_helpers=sidedata_helpers,
1176 )
1177 )
1177
1178
1178 for path, deltas in it:
1179 for path, deltas in it:
1179 h = _fileheader(path)
1180 h = _fileheader(path)
1180 size = len(h)
1181 size = len(h)
1181 yield h
1182 yield h
1182
1183
1183 for delta in deltas:
1184 for delta in deltas:
1184 chunks = _revisiondeltatochunks(
1185 chunks = _revisiondeltatochunks(
1185 self._repo, delta, self._builddeltaheader
1186 self._repo, delta, self._builddeltaheader
1186 )
1187 )
1187 for chunk in chunks:
1188 for chunk in chunks:
1188 size += len(chunk)
1189 size += len(chunk)
1189 yield chunk
1190 yield chunk
1190
1191
1191 close = closechunk()
1192 close = closechunk()
1192 size += len(close)
1193 size += len(close)
1193 yield close
1194 yield close
1194
1195
1195 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1196 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1196
1197
1197 yield closechunk()
1198 yield closechunk()
1198
1199
1199 if clnodes:
1200 if clnodes:
1200 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1201 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1201
1202
1202 def _generatechangelog(
1203 def _generatechangelog(
1203 self, cl, nodes, generate=True, sidedata_helpers=None
1204 self, cl, nodes, generate=True, sidedata_helpers=None
1204 ):
1205 ):
1205 """Generate data for changelog chunks.
1206 """Generate data for changelog chunks.
1206
1207
1207 Returns a 2-tuple of a dict containing state and an iterable of
1208 Returns a 2-tuple of a dict containing state and an iterable of
1208 byte chunks. The state will not be fully populated until the
1209 byte chunks. The state will not be fully populated until the
1209 chunk stream has been fully consumed.
1210 chunk stream has been fully consumed.
1210
1211
1211 if generate is False, the state will be fully populated and no chunk
1212 if generate is False, the state will be fully populated and no chunk
1212 stream will be yielded
1213 stream will be yielded
1213
1214
1214 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
1215 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
1215 """
1216 """
1216 clrevorder = {}
1217 clrevorder = {}
1217 manifests = {}
1218 manifests = {}
1218 mfl = self._repo.manifestlog
1219 mfl = self._repo.manifestlog
1219 changedfiles = set()
1220 changedfiles = set()
1220 clrevtomanifestrev = {}
1221 clrevtomanifestrev = {}
1221
1222
1222 state = {
1223 state = {
1223 b'clrevorder': clrevorder,
1224 b'clrevorder': clrevorder,
1224 b'manifests': manifests,
1225 b'manifests': manifests,
1225 b'changedfiles': changedfiles,
1226 b'changedfiles': changedfiles,
1226 b'clrevtomanifestrev': clrevtomanifestrev,
1227 b'clrevtomanifestrev': clrevtomanifestrev,
1227 }
1228 }
1228
1229
1229 if not (generate or self._ellipses):
1230 if not (generate or self._ellipses):
1230 # sort the nodes in storage order
1231 # sort the nodes in storage order
1231 nodes = sorted(nodes, key=cl.rev)
1232 nodes = sorted(nodes, key=cl.rev)
1232 for node in nodes:
1233 for node in nodes:
1233 c = cl.changelogrevision(node)
1234 c = cl.changelogrevision(node)
1234 clrevorder[node] = len(clrevorder)
1235 clrevorder[node] = len(clrevorder)
1235 # record the first changeset introducing this manifest version
1236 # record the first changeset introducing this manifest version
1236 manifests.setdefault(c.manifest, node)
1237 manifests.setdefault(c.manifest, node)
1237 # Record a complete list of potentially-changed files in
1238 # Record a complete list of potentially-changed files in
1238 # this manifest.
1239 # this manifest.
1239 changedfiles.update(c.files)
1240 changedfiles.update(c.files)
1240
1241
1241 return state, ()
1242 return state, ()
1242
1243
1243 # Callback for the changelog, used to collect changed files and
1244 # Callback for the changelog, used to collect changed files and
1244 # manifest nodes.
1245 # manifest nodes.
1245 # Returns the linkrev node (identity in the changelog case).
1246 # Returns the linkrev node (identity in the changelog case).
1246 def lookupcl(x):
1247 def lookupcl(x):
1247 c = cl.changelogrevision(x)
1248 c = cl.changelogrevision(x)
1248 clrevorder[x] = len(clrevorder)
1249 clrevorder[x] = len(clrevorder)
1249
1250
1250 if self._ellipses:
1251 if self._ellipses:
1251 # Only update manifests if x is going to be sent. Otherwise we
1252 # Only update manifests if x is going to be sent. Otherwise we
1252 # end up with bogus linkrevs specified for manifests and
1253 # end up with bogus linkrevs specified for manifests and
1253 # we skip some manifest nodes that we should otherwise
1254 # we skip some manifest nodes that we should otherwise
1254 # have sent.
1255 # have sent.
1255 if (
1256 if (
1256 x in self._fullclnodes
1257 x in self._fullclnodes
1257 or cl.rev(x) in self._precomputedellipsis
1258 or cl.rev(x) in self._precomputedellipsis
1258 ):
1259 ):
1259
1260
1260 manifestnode = c.manifest
1261 manifestnode = c.manifest
1261 # Record the first changeset introducing this manifest
1262 # Record the first changeset introducing this manifest
1262 # version.
1263 # version.
1263 manifests.setdefault(manifestnode, x)
1264 manifests.setdefault(manifestnode, x)
1264 # Set this narrow-specific dict so we have the lowest
1265 # Set this narrow-specific dict so we have the lowest
1265 # manifest revnum to look up for this cl revnum. (Part of
1266 # manifest revnum to look up for this cl revnum. (Part of
1266 # mapping changelog ellipsis parents to manifest ellipsis
1267 # mapping changelog ellipsis parents to manifest ellipsis
1267 # parents)
1268 # parents)
1268 clrevtomanifestrev.setdefault(
1269 clrevtomanifestrev.setdefault(
1269 cl.rev(x), mfl.rev(manifestnode)
1270 cl.rev(x), mfl.rev(manifestnode)
1270 )
1271 )
1271 # We can't trust the changed files list in the changeset if the
1272 # We can't trust the changed files list in the changeset if the
1272 # client requested a shallow clone.
1273 # client requested a shallow clone.
1273 if self._isshallow:
1274 if self._isshallow:
1274 changedfiles.update(mfl[c.manifest].read().keys())
1275 changedfiles.update(mfl[c.manifest].read().keys())
1275 else:
1276 else:
1276 changedfiles.update(c.files)
1277 changedfiles.update(c.files)
1277 else:
1278 else:
1278 # record the first changeset introducing this manifest version
1279 # record the first changeset introducing this manifest version
1279 manifests.setdefault(c.manifest, x)
1280 manifests.setdefault(c.manifest, x)
1280 # Record a complete list of potentially-changed files in
1281 # Record a complete list of potentially-changed files in
1281 # this manifest.
1282 # this manifest.
1282 changedfiles.update(c.files)
1283 changedfiles.update(c.files)
1283
1284
1284 return x
1285 return x
1285
1286
1286 gen = deltagroup(
1287 gen = deltagroup(
1287 self._repo,
1288 self._repo,
1288 cl,
1289 cl,
1289 nodes,
1290 nodes,
1290 True,
1291 True,
1291 lookupcl,
1292 lookupcl,
1292 self._forcedeltaparentprev,
1293 self._forcedeltaparentprev,
1293 ellipses=self._ellipses,
1294 ellipses=self._ellipses,
1294 topic=_(b'changesets'),
1295 topic=_(b'changesets'),
1295 clrevtolocalrev={},
1296 clrevtolocalrev={},
1296 fullclnodes=self._fullclnodes,
1297 fullclnodes=self._fullclnodes,
1297 precomputedellipsis=self._precomputedellipsis,
1298 precomputedellipsis=self._precomputedellipsis,
1298 sidedata_helpers=sidedata_helpers,
1299 sidedata_helpers=sidedata_helpers,
1299 )
1300 )
1300
1301
1301 return state, gen
1302 return state, gen
1302
1303
1303 def generatemanifests(
1304 def generatemanifests(
1304 self,
1305 self,
1305 commonrevs,
1306 commonrevs,
1306 clrevorder,
1307 clrevorder,
1307 fastpathlinkrev,
1308 fastpathlinkrev,
1308 manifests,
1309 manifests,
1309 fnodes,
1310 fnodes,
1310 source,
1311 source,
1311 clrevtolocalrev,
1312 clrevtolocalrev,
1312 sidedata_helpers=None,
1313 sidedata_helpers=None,
1313 ):
1314 ):
1314 """Returns an iterator of changegroup chunks containing manifests.
1315 """Returns an iterator of changegroup chunks containing manifests.
1315
1316
1316 `source` is unused here, but is used by extensions like remotefilelog to
1317 `source` is unused here, but is used by extensions like remotefilelog to
1317 change what is sent based in pulls vs pushes, etc.
1318 change what is sent based in pulls vs pushes, etc.
1318
1319
1319 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
1320 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
1320 """
1321 """
1321 repo = self._repo
1322 repo = self._repo
1322 mfl = repo.manifestlog
1323 mfl = repo.manifestlog
1323 tmfnodes = {b'': manifests}
1324 tmfnodes = {b'': manifests}
1324
1325
1325 # Callback for the manifest, used to collect linkrevs for filelog
1326 # Callback for the manifest, used to collect linkrevs for filelog
1326 # revisions.
1327 # revisions.
1327 # Returns the linkrev node (collected in lookupcl).
1328 # Returns the linkrev node (collected in lookupcl).
1328 def makelookupmflinknode(tree, nodes):
1329 def makelookupmflinknode(tree, nodes):
1329 if fastpathlinkrev:
1330 if fastpathlinkrev:
1330 assert not tree
1331 assert not tree
1331
1332
1332 # pytype: disable=unsupported-operands
1333 # pytype: disable=unsupported-operands
1333 return manifests.__getitem__
1334 return manifests.__getitem__
1334 # pytype: enable=unsupported-operands
1335 # pytype: enable=unsupported-operands
1335
1336
1336 def lookupmflinknode(x):
1337 def lookupmflinknode(x):
1337 """Callback for looking up the linknode for manifests.
1338 """Callback for looking up the linknode for manifests.
1338
1339
1339 Returns the linkrev node for the specified manifest.
1340 Returns the linkrev node for the specified manifest.
1340
1341
1341 SIDE EFFECT:
1342 SIDE EFFECT:
1342
1343
1343 1) fclnodes gets populated with the list of relevant
1344 1) fclnodes gets populated with the list of relevant
1344 file nodes if we're not using fastpathlinkrev
1345 file nodes if we're not using fastpathlinkrev
1345 2) When treemanifests are in use, collects treemanifest nodes
1346 2) When treemanifests are in use, collects treemanifest nodes
1346 to send
1347 to send
1347
1348
1348 Note that this means manifests must be completely sent to
1349 Note that this means manifests must be completely sent to
1349 the client before you can trust the list of files and
1350 the client before you can trust the list of files and
1350 treemanifests to send.
1351 treemanifests to send.
1351 """
1352 """
1352 clnode = nodes[x]
1353 clnode = nodes[x]
1353 mdata = mfl.get(tree, x).readfast(shallow=True)
1354 mdata = mfl.get(tree, x).readfast(shallow=True)
1354 for p, n, fl in mdata.iterentries():
1355 for p, n, fl in mdata.iterentries():
1355 if fl == b't': # subdirectory manifest
1356 if fl == b't': # subdirectory manifest
1356 subtree = tree + p + b'/'
1357 subtree = tree + p + b'/'
1357 tmfclnodes = tmfnodes.setdefault(subtree, {})
1358 tmfclnodes = tmfnodes.setdefault(subtree, {})
1358 tmfclnode = tmfclnodes.setdefault(n, clnode)
1359 tmfclnode = tmfclnodes.setdefault(n, clnode)
1359 if clrevorder[clnode] < clrevorder[tmfclnode]:
1360 if clrevorder[clnode] < clrevorder[tmfclnode]:
1360 tmfclnodes[n] = clnode
1361 tmfclnodes[n] = clnode
1361 else:
1362 else:
1362 f = tree + p
1363 f = tree + p
1363 fclnodes = fnodes.setdefault(f, {})
1364 fclnodes = fnodes.setdefault(f, {})
1364 fclnode = fclnodes.setdefault(n, clnode)
1365 fclnode = fclnodes.setdefault(n, clnode)
1365 if clrevorder[clnode] < clrevorder[fclnode]:
1366 if clrevorder[clnode] < clrevorder[fclnode]:
1366 fclnodes[n] = clnode
1367 fclnodes[n] = clnode
1367 return clnode
1368 return clnode
1368
1369
1369 return lookupmflinknode
1370 return lookupmflinknode
1370
1371
1371 while tmfnodes:
1372 while tmfnodes:
1372 tree, nodes = tmfnodes.popitem()
1373 tree, nodes = tmfnodes.popitem()
1373
1374
1374 should_visit = self._matcher.visitdir(tree[:-1])
1375 should_visit = self._matcher.visitdir(tree[:-1])
1375 if tree and not should_visit:
1376 if tree and not should_visit:
1376 continue
1377 continue
1377
1378
1378 store = mfl.getstorage(tree)
1379 store = mfl.getstorage(tree)
1379
1380
1380 if not should_visit:
1381 if not should_visit:
1381 # No nodes to send because this directory is out of
1382 # No nodes to send because this directory is out of
1382 # the client's view of the repository (probably
1383 # the client's view of the repository (probably
1383 # because of narrow clones). Do this even for the root
1384 # because of narrow clones). Do this even for the root
1384 # directory (tree=='')
1385 # directory (tree=='')
1385 prunednodes = []
1386 prunednodes = []
1386 else:
1387 else:
1387 # Avoid sending any manifest nodes we can prove the
1388 # Avoid sending any manifest nodes we can prove the
1388 # client already has by checking linkrevs. See the
1389 # client already has by checking linkrevs. See the
1389 # related comment in generatefiles().
1390 # related comment in generatefiles().
1390 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1391 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1391
1392
1392 if tree and not prunednodes:
1393 if tree and not prunednodes:
1393 continue
1394 continue
1394
1395
1395 lookupfn = makelookupmflinknode(tree, nodes)
1396 lookupfn = makelookupmflinknode(tree, nodes)
1396
1397
1397 deltas = deltagroup(
1398 deltas = deltagroup(
1398 self._repo,
1399 self._repo,
1399 store,
1400 store,
1400 prunednodes,
1401 prunednodes,
1401 False,
1402 False,
1402 lookupfn,
1403 lookupfn,
1403 self._forcedeltaparentprev,
1404 self._forcedeltaparentprev,
1404 ellipses=self._ellipses,
1405 ellipses=self._ellipses,
1405 topic=_(b'manifests'),
1406 topic=_(b'manifests'),
1406 clrevtolocalrev=clrevtolocalrev,
1407 clrevtolocalrev=clrevtolocalrev,
1407 fullclnodes=self._fullclnodes,
1408 fullclnodes=self._fullclnodes,
1408 precomputedellipsis=self._precomputedellipsis,
1409 precomputedellipsis=self._precomputedellipsis,
1409 sidedata_helpers=sidedata_helpers,
1410 sidedata_helpers=sidedata_helpers,
1410 )
1411 )
1411
1412
1412 if not self._oldmatcher.visitdir(store.tree[:-1]):
1413 if not self._oldmatcher.visitdir(store.tree[:-1]):
1413 yield tree, deltas
1414 yield tree, deltas
1414 else:
1415 else:
1415 # 'deltas' is a generator and we need to consume it even if
1416 # 'deltas' is a generator and we need to consume it even if
1416 # we are not going to send it because a side-effect is that
1417 # we are not going to send it because a side-effect is that
1417 # it updates tmdnodes (via lookupfn)
1418 # it updates tmdnodes (via lookupfn)
1418 for d in deltas:
1419 for d in deltas:
1419 pass
1420 pass
1420 if not tree:
1421 if not tree:
1421 yield tree, []
1422 yield tree, []
1422
1423
1423 def _prunemanifests(self, store, nodes, commonrevs):
1424 def _prunemanifests(self, store, nodes, commonrevs):
1424 if not self._ellipses:
1425 if not self._ellipses:
1425 # In non-ellipses case and large repositories, it is better to
1426 # In non-ellipses case and large repositories, it is better to
1426 # prevent calling of store.rev and store.linkrev on a lot of
1427 # prevent calling of store.rev and store.linkrev on a lot of
1427 # nodes as compared to sending some extra data
1428 # nodes as compared to sending some extra data
1428 return nodes.copy()
1429 return nodes.copy()
1429 # This is split out as a separate method to allow filtering
1430 # This is split out as a separate method to allow filtering
1430 # commonrevs in extension code.
1431 # commonrevs in extension code.
1431 #
1432 #
1432 # TODO(augie): this shouldn't be required, instead we should
1433 # TODO(augie): this shouldn't be required, instead we should
1433 # make filtering of revisions to send delegated to the store
1434 # make filtering of revisions to send delegated to the store
1434 # layer.
1435 # layer.
1435 frev, flr = store.rev, store.linkrev
1436 frev, flr = store.rev, store.linkrev
1436 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1437 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1437
1438
1438 # The 'source' parameter is useful for extensions
1439 # The 'source' parameter is useful for extensions
1439 def generatefiles(
1440 def generatefiles(
1440 self,
1441 self,
1441 changedfiles,
1442 changedfiles,
1442 commonrevs,
1443 commonrevs,
1443 source,
1444 source,
1444 mfdicts,
1445 mfdicts,
1445 fastpathlinkrev,
1446 fastpathlinkrev,
1446 fnodes,
1447 fnodes,
1447 clrevs,
1448 clrevs,
1448 sidedata_helpers=None,
1449 sidedata_helpers=None,
1449 ):
1450 ):
1450 changedfiles = [
1451 changedfiles = [
1451 f
1452 f
1452 for f in changedfiles
1453 for f in changedfiles
1453 if self._matcher(f) and not self._oldmatcher(f)
1454 if self._matcher(f) and not self._oldmatcher(f)
1454 ]
1455 ]
1455
1456
1456 if not fastpathlinkrev:
1457 if not fastpathlinkrev:
1457
1458
1458 def normallinknodes(unused, fname):
1459 def normallinknodes(unused, fname):
1459 return fnodes.get(fname, {})
1460 return fnodes.get(fname, {})
1460
1461
1461 else:
1462 else:
1462 cln = self._repo.changelog.node
1463 cln = self._repo.changelog.node
1463
1464
1464 def normallinknodes(store, fname):
1465 def normallinknodes(store, fname):
1465 flinkrev = store.linkrev
1466 flinkrev = store.linkrev
1466 fnode = store.node
1467 fnode = store.node
1467 revs = ((r, flinkrev(r)) for r in store)
1468 revs = ((r, flinkrev(r)) for r in store)
1468 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1469 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1469
1470
1470 clrevtolocalrev = {}
1471 clrevtolocalrev = {}
1471
1472
1472 if self._isshallow:
1473 if self._isshallow:
1473 # In a shallow clone, the linknodes callback needs to also include
1474 # In a shallow clone, the linknodes callback needs to also include
1474 # those file nodes that are in the manifests we sent but weren't
1475 # those file nodes that are in the manifests we sent but weren't
1475 # introduced by those manifests.
1476 # introduced by those manifests.
1476 commonctxs = [self._repo[c] for c in commonrevs]
1477 commonctxs = [self._repo[c] for c in commonrevs]
1477 clrev = self._repo.changelog.rev
1478 clrev = self._repo.changelog.rev
1478
1479
1479 def linknodes(flog, fname):
1480 def linknodes(flog, fname):
1480 for c in commonctxs:
1481 for c in commonctxs:
1481 try:
1482 try:
1482 fnode = c.filenode(fname)
1483 fnode = c.filenode(fname)
1483 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1484 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1484 except error.ManifestLookupError:
1485 except error.ManifestLookupError:
1485 pass
1486 pass
1486 links = normallinknodes(flog, fname)
1487 links = normallinknodes(flog, fname)
1487 if len(links) != len(mfdicts):
1488 if len(links) != len(mfdicts):
1488 for mf, lr in mfdicts:
1489 for mf, lr in mfdicts:
1489 fnode = mf.get(fname, None)
1490 fnode = mf.get(fname, None)
1490 if fnode in links:
1491 if fnode in links:
1491 links[fnode] = min(links[fnode], lr, key=clrev)
1492 links[fnode] = min(links[fnode], lr, key=clrev)
1492 elif fnode:
1493 elif fnode:
1493 links[fnode] = lr
1494 links[fnode] = lr
1494 return links
1495 return links
1495
1496
1496 else:
1497 else:
1497 linknodes = normallinknodes
1498 linknodes = normallinknodes
1498
1499
1499 repo = self._repo
1500 repo = self._repo
1500 progress = repo.ui.makeprogress(
1501 progress = repo.ui.makeprogress(
1501 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1502 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1502 )
1503 )
1503 for i, fname in enumerate(sorted(changedfiles)):
1504 for i, fname in enumerate(sorted(changedfiles)):
1504 filerevlog = repo.file(fname)
1505 filerevlog = repo.file(fname)
1505 if not filerevlog:
1506 if not filerevlog:
1506 raise error.Abort(
1507 raise error.Abort(
1507 _(b"empty or missing file data for %s") % fname
1508 _(b"empty or missing file data for %s") % fname
1508 )
1509 )
1509
1510
1510 clrevtolocalrev.clear()
1511 clrevtolocalrev.clear()
1511
1512
1512 linkrevnodes = linknodes(filerevlog, fname)
1513 linkrevnodes = linknodes(filerevlog, fname)
1513 # Lookup for filenodes, we collected the linkrev nodes above in the
1514 # Lookup for filenodes, we collected the linkrev nodes above in the
1514 # fastpath case and with lookupmf in the slowpath case.
1515 # fastpath case and with lookupmf in the slowpath case.
1515 def lookupfilelog(x):
1516 def lookupfilelog(x):
1516 return linkrevnodes[x]
1517 return linkrevnodes[x]
1517
1518
1518 frev, flr = filerevlog.rev, filerevlog.linkrev
1519 frev, flr = filerevlog.rev, filerevlog.linkrev
1519 # Skip sending any filenode we know the client already
1520 # Skip sending any filenode we know the client already
1520 # has. This avoids over-sending files relatively
1521 # has. This avoids over-sending files relatively
1521 # inexpensively, so it's not a problem if we under-filter
1522 # inexpensively, so it's not a problem if we under-filter
1522 # here.
1523 # here.
1523 filenodes = [
1524 filenodes = [
1524 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1525 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1525 ]
1526 ]
1526
1527
1527 if not filenodes:
1528 if not filenodes:
1528 continue
1529 continue
1529
1530
1530 progress.update(i + 1, item=fname)
1531 progress.update(i + 1, item=fname)
1531
1532
1532 deltas = deltagroup(
1533 deltas = deltagroup(
1533 self._repo,
1534 self._repo,
1534 filerevlog,
1535 filerevlog,
1535 filenodes,
1536 filenodes,
1536 False,
1537 False,
1537 lookupfilelog,
1538 lookupfilelog,
1538 self._forcedeltaparentprev,
1539 self._forcedeltaparentprev,
1539 ellipses=self._ellipses,
1540 ellipses=self._ellipses,
1540 clrevtolocalrev=clrevtolocalrev,
1541 clrevtolocalrev=clrevtolocalrev,
1541 fullclnodes=self._fullclnodes,
1542 fullclnodes=self._fullclnodes,
1542 precomputedellipsis=self._precomputedellipsis,
1543 precomputedellipsis=self._precomputedellipsis,
1543 sidedata_helpers=sidedata_helpers,
1544 sidedata_helpers=sidedata_helpers,
1544 )
1545 )
1545
1546
1546 yield fname, deltas
1547 yield fname, deltas
1547
1548
1548 progress.complete()
1549 progress.complete()
1549
1550
1550
1551
1551 def _makecg1packer(
1552 def _makecg1packer(
1552 repo,
1553 repo,
1553 oldmatcher,
1554 oldmatcher,
1554 matcher,
1555 matcher,
1555 bundlecaps,
1556 bundlecaps,
1556 ellipses=False,
1557 ellipses=False,
1557 shallow=False,
1558 shallow=False,
1558 ellipsisroots=None,
1559 ellipsisroots=None,
1559 fullnodes=None,
1560 fullnodes=None,
1560 remote_sidedata=None,
1561 remote_sidedata=None,
1561 ):
1562 ):
1562 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1563 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1563 d.node, d.p1node, d.p2node, d.linknode
1564 d.node, d.p1node, d.p2node, d.linknode
1564 )
1565 )
1565
1566
1566 return cgpacker(
1567 return cgpacker(
1567 repo,
1568 repo,
1568 oldmatcher,
1569 oldmatcher,
1569 matcher,
1570 matcher,
1570 b'01',
1571 b'01',
1571 builddeltaheader=builddeltaheader,
1572 builddeltaheader=builddeltaheader,
1572 manifestsend=b'',
1573 manifestsend=b'',
1573 forcedeltaparentprev=True,
1574 forcedeltaparentprev=True,
1574 bundlecaps=bundlecaps,
1575 bundlecaps=bundlecaps,
1575 ellipses=ellipses,
1576 ellipses=ellipses,
1576 shallow=shallow,
1577 shallow=shallow,
1577 ellipsisroots=ellipsisroots,
1578 ellipsisroots=ellipsisroots,
1578 fullnodes=fullnodes,
1579 fullnodes=fullnodes,
1579 )
1580 )
1580
1581
1581
1582
1582 def _makecg2packer(
1583 def _makecg2packer(
1583 repo,
1584 repo,
1584 oldmatcher,
1585 oldmatcher,
1585 matcher,
1586 matcher,
1586 bundlecaps,
1587 bundlecaps,
1587 ellipses=False,
1588 ellipses=False,
1588 shallow=False,
1589 shallow=False,
1589 ellipsisroots=None,
1590 ellipsisroots=None,
1590 fullnodes=None,
1591 fullnodes=None,
1591 remote_sidedata=None,
1592 remote_sidedata=None,
1592 ):
1593 ):
1593 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1594 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1594 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1595 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1595 )
1596 )
1596
1597
1597 return cgpacker(
1598 return cgpacker(
1598 repo,
1599 repo,
1599 oldmatcher,
1600 oldmatcher,
1600 matcher,
1601 matcher,
1601 b'02',
1602 b'02',
1602 builddeltaheader=builddeltaheader,
1603 builddeltaheader=builddeltaheader,
1603 manifestsend=b'',
1604 manifestsend=b'',
1604 bundlecaps=bundlecaps,
1605 bundlecaps=bundlecaps,
1605 ellipses=ellipses,
1606 ellipses=ellipses,
1606 shallow=shallow,
1607 shallow=shallow,
1607 ellipsisroots=ellipsisroots,
1608 ellipsisroots=ellipsisroots,
1608 fullnodes=fullnodes,
1609 fullnodes=fullnodes,
1609 )
1610 )
1610
1611
1611
1612
1612 def _makecg3packer(
1613 def _makecg3packer(
1613 repo,
1614 repo,
1614 oldmatcher,
1615 oldmatcher,
1615 matcher,
1616 matcher,
1616 bundlecaps,
1617 bundlecaps,
1617 ellipses=False,
1618 ellipses=False,
1618 shallow=False,
1619 shallow=False,
1619 ellipsisroots=None,
1620 ellipsisroots=None,
1620 fullnodes=None,
1621 fullnodes=None,
1621 remote_sidedata=None,
1622 remote_sidedata=None,
1622 ):
1623 ):
1623 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1624 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1624 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1625 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1625 )
1626 )
1626
1627
1627 return cgpacker(
1628 return cgpacker(
1628 repo,
1629 repo,
1629 oldmatcher,
1630 oldmatcher,
1630 matcher,
1631 matcher,
1631 b'03',
1632 b'03',
1632 builddeltaheader=builddeltaheader,
1633 builddeltaheader=builddeltaheader,
1633 manifestsend=closechunk(),
1634 manifestsend=closechunk(),
1634 bundlecaps=bundlecaps,
1635 bundlecaps=bundlecaps,
1635 ellipses=ellipses,
1636 ellipses=ellipses,
1636 shallow=shallow,
1637 shallow=shallow,
1637 ellipsisroots=ellipsisroots,
1638 ellipsisroots=ellipsisroots,
1638 fullnodes=fullnodes,
1639 fullnodes=fullnodes,
1639 )
1640 )
1640
1641
1641
1642
1642 def _makecg4packer(
1643 def _makecg4packer(
1643 repo,
1644 repo,
1644 oldmatcher,
1645 oldmatcher,
1645 matcher,
1646 matcher,
1646 bundlecaps,
1647 bundlecaps,
1647 ellipses=False,
1648 ellipses=False,
1648 shallow=False,
1649 shallow=False,
1649 ellipsisroots=None,
1650 ellipsisroots=None,
1650 fullnodes=None,
1651 fullnodes=None,
1651 remote_sidedata=None,
1652 remote_sidedata=None,
1652 ):
1653 ):
1653 # Sidedata is in a separate chunk from the delta to differentiate
1654 # Sidedata is in a separate chunk from the delta to differentiate
1654 # "raw delta" and sidedata.
1655 # "raw delta" and sidedata.
1655 def builddeltaheader(d):
1656 def builddeltaheader(d):
1656 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1657 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1657 d.protocol_flags,
1658 d.protocol_flags,
1658 d.node,
1659 d.node,
1659 d.p1node,
1660 d.p1node,
1660 d.p2node,
1661 d.p2node,
1661 d.basenode,
1662 d.basenode,
1662 d.linknode,
1663 d.linknode,
1663 d.flags,
1664 d.flags,
1664 )
1665 )
1665
1666
1666 return cgpacker(
1667 return cgpacker(
1667 repo,
1668 repo,
1668 oldmatcher,
1669 oldmatcher,
1669 matcher,
1670 matcher,
1670 b'04',
1671 b'04',
1671 builddeltaheader=builddeltaheader,
1672 builddeltaheader=builddeltaheader,
1672 manifestsend=closechunk(),
1673 manifestsend=closechunk(),
1673 bundlecaps=bundlecaps,
1674 bundlecaps=bundlecaps,
1674 ellipses=ellipses,
1675 ellipses=ellipses,
1675 shallow=shallow,
1676 shallow=shallow,
1676 ellipsisroots=ellipsisroots,
1677 ellipsisroots=ellipsisroots,
1677 fullnodes=fullnodes,
1678 fullnodes=fullnodes,
1678 remote_sidedata=remote_sidedata,
1679 remote_sidedata=remote_sidedata,
1679 )
1680 )
1680
1681
1681
1682
1682 _packermap = {
1683 _packermap = {
1683 b'01': (_makecg1packer, cg1unpacker),
1684 b'01': (_makecg1packer, cg1unpacker),
1684 # cg2 adds support for exchanging generaldelta
1685 # cg2 adds support for exchanging generaldelta
1685 b'02': (_makecg2packer, cg2unpacker),
1686 b'02': (_makecg2packer, cg2unpacker),
1686 # cg3 adds support for exchanging revlog flags and treemanifests
1687 # cg3 adds support for exchanging revlog flags and treemanifests
1687 b'03': (_makecg3packer, cg3unpacker),
1688 b'03': (_makecg3packer, cg3unpacker),
1688 # ch4 adds support for exchanging sidedata
1689 # ch4 adds support for exchanging sidedata
1689 b'04': (_makecg4packer, cg4unpacker),
1690 b'04': (_makecg4packer, cg4unpacker),
1690 }
1691 }
1691
1692
1692
1693
1693 def allsupportedversions(repo):
1694 def allsupportedversions(repo):
1694 versions = set(_packermap.keys())
1695 versions = set(_packermap.keys())
1695 needv03 = False
1696 needv03 = False
1696 if (
1697 if (
1697 repo.ui.configbool(b'experimental', b'changegroup3')
1698 repo.ui.configbool(b'experimental', b'changegroup3')
1698 or repo.ui.configbool(b'experimental', b'treemanifest')
1699 or repo.ui.configbool(b'experimental', b'treemanifest')
1699 or scmutil.istreemanifest(repo)
1700 or scmutil.istreemanifest(repo)
1700 ):
1701 ):
1701 # we keep version 03 because we need to to exchange treemanifest data
1702 # we keep version 03 because we need to to exchange treemanifest data
1702 #
1703 #
1703 # we also keep vresion 01 and 02, because it is possible for repo to
1704 # we also keep vresion 01 and 02, because it is possible for repo to
1704 # contains both normal and tree manifest at the same time. so using
1705 # contains both normal and tree manifest at the same time. so using
1705 # older version to pull data is viable
1706 # older version to pull data is viable
1706 #
1707 #
1707 # (or even to push subset of history)
1708 # (or even to push subset of history)
1708 needv03 = True
1709 needv03 = True
1709 if not needv03:
1710 if not needv03:
1710 versions.discard(b'03')
1711 versions.discard(b'03')
1711 want_v4 = (
1712 want_v4 = (
1712 repo.ui.configbool(b'experimental', b'changegroup4')
1713 repo.ui.configbool(b'experimental', b'changegroup4')
1713 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1714 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1714 )
1715 )
1715 if not want_v4:
1716 if not want_v4:
1716 versions.discard(b'04')
1717 versions.discard(b'04')
1717 return versions
1718 return versions
1718
1719
1719
1720
1720 # Changegroup versions that can be applied to the repo
1721 # Changegroup versions that can be applied to the repo
1721 def supportedincomingversions(repo):
1722 def supportedincomingversions(repo):
1722 return allsupportedversions(repo)
1723 return allsupportedversions(repo)
1723
1724
1724
1725
1725 # Changegroup versions that can be created from the repo
1726 # Changegroup versions that can be created from the repo
1726 def supportedoutgoingversions(repo):
1727 def supportedoutgoingversions(repo):
1727 versions = allsupportedversions(repo)
1728 versions = allsupportedversions(repo)
1728 if scmutil.istreemanifest(repo):
1729 if scmutil.istreemanifest(repo):
1729 # Versions 01 and 02 support only flat manifests and it's just too
1730 # Versions 01 and 02 support only flat manifests and it's just too
1730 # expensive to convert between the flat manifest and tree manifest on
1731 # expensive to convert between the flat manifest and tree manifest on
1731 # the fly. Since tree manifests are hashed differently, all of history
1732 # the fly. Since tree manifests are hashed differently, all of history
1732 # would have to be converted. Instead, we simply don't even pretend to
1733 # would have to be converted. Instead, we simply don't even pretend to
1733 # support versions 01 and 02.
1734 # support versions 01 and 02.
1734 versions.discard(b'01')
1735 versions.discard(b'01')
1735 versions.discard(b'02')
1736 versions.discard(b'02')
1736 if requirements.NARROW_REQUIREMENT in repo.requirements:
1737 if requirements.NARROW_REQUIREMENT in repo.requirements:
1737 # Versions 01 and 02 don't support revlog flags, and we need to
1738 # Versions 01 and 02 don't support revlog flags, and we need to
1738 # support that for stripping and unbundling to work.
1739 # support that for stripping and unbundling to work.
1739 versions.discard(b'01')
1740 versions.discard(b'01')
1740 versions.discard(b'02')
1741 versions.discard(b'02')
1741 if LFS_REQUIREMENT in repo.requirements:
1742 if LFS_REQUIREMENT in repo.requirements:
1742 # Versions 01 and 02 don't support revlog flags, and we need to
1743 # Versions 01 and 02 don't support revlog flags, and we need to
1743 # mark LFS entries with REVIDX_EXTSTORED.
1744 # mark LFS entries with REVIDX_EXTSTORED.
1744 versions.discard(b'01')
1745 versions.discard(b'01')
1745 versions.discard(b'02')
1746 versions.discard(b'02')
1746
1747
1747 return versions
1748 return versions
1748
1749
1749
1750
1750 def localversion(repo):
1751 def localversion(repo):
1751 # Finds the best version to use for bundles that are meant to be used
1752 # Finds the best version to use for bundles that are meant to be used
1752 # locally, such as those from strip and shelve, and temporary bundles.
1753 # locally, such as those from strip and shelve, and temporary bundles.
1753 return max(supportedoutgoingversions(repo))
1754 return max(supportedoutgoingversions(repo))
1754
1755
1755
1756
1756 def safeversion(repo):
1757 def safeversion(repo):
1757 # Finds the smallest version that it's safe to assume clients of the repo
1758 # Finds the smallest version that it's safe to assume clients of the repo
1758 # will support. For example, all hg versions that support generaldelta also
1759 # will support. For example, all hg versions that support generaldelta also
1759 # support changegroup 02.
1760 # support changegroup 02.
1760 versions = supportedoutgoingversions(repo)
1761 versions = supportedoutgoingversions(repo)
1761 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1762 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1762 versions.discard(b'01')
1763 versions.discard(b'01')
1763 assert versions
1764 assert versions
1764 return min(versions)
1765 return min(versions)
1765
1766
1766
1767
1767 def getbundler(
1768 def getbundler(
1768 version,
1769 version,
1769 repo,
1770 repo,
1770 bundlecaps=None,
1771 bundlecaps=None,
1771 oldmatcher=None,
1772 oldmatcher=None,
1772 matcher=None,
1773 matcher=None,
1773 ellipses=False,
1774 ellipses=False,
1774 shallow=False,
1775 shallow=False,
1775 ellipsisroots=None,
1776 ellipsisroots=None,
1776 fullnodes=None,
1777 fullnodes=None,
1777 remote_sidedata=None,
1778 remote_sidedata=None,
1778 ):
1779 ):
1779 assert version in supportedoutgoingversions(repo)
1780 assert version in supportedoutgoingversions(repo)
1780
1781
1781 if matcher is None:
1782 if matcher is None:
1782 matcher = matchmod.always()
1783 matcher = matchmod.always()
1783 if oldmatcher is None:
1784 if oldmatcher is None:
1784 oldmatcher = matchmod.never()
1785 oldmatcher = matchmod.never()
1785
1786
1786 if version == b'01' and not matcher.always():
1787 if version == b'01' and not matcher.always():
1787 raise error.ProgrammingError(
1788 raise error.ProgrammingError(
1788 b'version 01 changegroups do not support sparse file matchers'
1789 b'version 01 changegroups do not support sparse file matchers'
1789 )
1790 )
1790
1791
1791 if ellipses and version in (b'01', b'02'):
1792 if ellipses and version in (b'01', b'02'):
1792 raise error.Abort(
1793 raise error.Abort(
1793 _(
1794 _(
1794 b'ellipsis nodes require at least cg3 on client and server, '
1795 b'ellipsis nodes require at least cg3 on client and server, '
1795 b'but negotiated version %s'
1796 b'but negotiated version %s'
1796 )
1797 )
1797 % version
1798 % version
1798 )
1799 )
1799
1800
1800 # Requested files could include files not in the local store. So
1801 # Requested files could include files not in the local store. So
1801 # filter those out.
1802 # filter those out.
1802 matcher = repo.narrowmatch(matcher)
1803 matcher = repo.narrowmatch(matcher)
1803
1804
1804 fn = _packermap[version][0]
1805 fn = _packermap[version][0]
1805 return fn(
1806 return fn(
1806 repo,
1807 repo,
1807 oldmatcher,
1808 oldmatcher,
1808 matcher,
1809 matcher,
1809 bundlecaps,
1810 bundlecaps,
1810 ellipses=ellipses,
1811 ellipses=ellipses,
1811 shallow=shallow,
1812 shallow=shallow,
1812 ellipsisroots=ellipsisroots,
1813 ellipsisroots=ellipsisroots,
1813 fullnodes=fullnodes,
1814 fullnodes=fullnodes,
1814 remote_sidedata=remote_sidedata,
1815 remote_sidedata=remote_sidedata,
1815 )
1816 )
1816
1817
1817
1818
1818 def getunbundler(version, fh, alg, extras=None):
1819 def getunbundler(version, fh, alg, extras=None):
1819 return _packermap[version][1](fh, alg, extras=extras)
1820 return _packermap[version][1](fh, alg, extras=extras)
1820
1821
1821
1822
1822 def _changegroupinfo(repo, nodes, source):
1823 def _changegroupinfo(repo, nodes, source):
1823 if repo.ui.verbose or source == b'bundle':
1824 if repo.ui.verbose or source == b'bundle':
1824 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1825 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1825 if repo.ui.debugflag:
1826 if repo.ui.debugflag:
1826 repo.ui.debug(b"list of changesets:\n")
1827 repo.ui.debug(b"list of changesets:\n")
1827 for node in nodes:
1828 for node in nodes:
1828 repo.ui.debug(b"%s\n" % hex(node))
1829 repo.ui.debug(b"%s\n" % hex(node))
1829
1830
1830
1831
1831 def makechangegroup(
1832 def makechangegroup(
1832 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1833 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1833 ):
1834 ):
1834 cgstream = makestream(
1835 cgstream = makestream(
1835 repo,
1836 repo,
1836 outgoing,
1837 outgoing,
1837 version,
1838 version,
1838 source,
1839 source,
1839 fastpath=fastpath,
1840 fastpath=fastpath,
1840 bundlecaps=bundlecaps,
1841 bundlecaps=bundlecaps,
1841 )
1842 )
1842 return getunbundler(
1843 return getunbundler(
1843 version,
1844 version,
1844 util.chunkbuffer(cgstream),
1845 util.chunkbuffer(cgstream),
1845 None,
1846 None,
1846 {b'clcount': len(outgoing.missing)},
1847 {b'clcount': len(outgoing.missing)},
1847 )
1848 )
1848
1849
1849
1850
1850 def makestream(
1851 def makestream(
1851 repo,
1852 repo,
1852 outgoing,
1853 outgoing,
1853 version,
1854 version,
1854 source,
1855 source,
1855 fastpath=False,
1856 fastpath=False,
1856 bundlecaps=None,
1857 bundlecaps=None,
1857 matcher=None,
1858 matcher=None,
1858 remote_sidedata=None,
1859 remote_sidedata=None,
1859 ):
1860 ):
1860 bundler = getbundler(
1861 bundler = getbundler(
1861 version,
1862 version,
1862 repo,
1863 repo,
1863 bundlecaps=bundlecaps,
1864 bundlecaps=bundlecaps,
1864 matcher=matcher,
1865 matcher=matcher,
1865 remote_sidedata=remote_sidedata,
1866 remote_sidedata=remote_sidedata,
1866 )
1867 )
1867
1868
1868 repo = repo.unfiltered()
1869 repo = repo.unfiltered()
1869 commonrevs = outgoing.common
1870 commonrevs = outgoing.common
1870 csets = outgoing.missing
1871 csets = outgoing.missing
1871 heads = outgoing.ancestorsof
1872 heads = outgoing.ancestorsof
1872 # We go through the fast path if we get told to, or if all (unfiltered
1873 # We go through the fast path if we get told to, or if all (unfiltered
1873 # heads have been requested (since we then know there all linkrevs will
1874 # heads have been requested (since we then know there all linkrevs will
1874 # be pulled by the client).
1875 # be pulled by the client).
1875 heads.sort()
1876 heads.sort()
1876 fastpathlinkrev = fastpath or (
1877 fastpathlinkrev = fastpath or (
1877 repo.filtername is None and heads == sorted(repo.heads())
1878 repo.filtername is None and heads == sorted(repo.heads())
1878 )
1879 )
1879
1880
1880 repo.hook(b'preoutgoing', throw=True, source=source)
1881 repo.hook(b'preoutgoing', throw=True, source=source)
1881 _changegroupinfo(repo, csets, source)
1882 _changegroupinfo(repo, csets, source)
1882 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1883 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1883
1884
1884
1885
1885 def _addchangegroupfiles(
1886 def _addchangegroupfiles(
1886 repo,
1887 repo,
1887 source,
1888 source,
1888 revmap,
1889 revmap,
1889 trp,
1890 trp,
1890 expectedfiles,
1891 expectedfiles,
1891 needfiles,
1892 needfiles,
1892 addrevisioncb=None,
1893 addrevisioncb=None,
1893 ):
1894 ):
1894 revisions = 0
1895 revisions = 0
1895 files = 0
1896 files = 0
1896 progress = repo.ui.makeprogress(
1897 progress = repo.ui.makeprogress(
1897 _(b'files'), unit=_(b'files'), total=expectedfiles
1898 _(b'files'), unit=_(b'files'), total=expectedfiles
1898 )
1899 )
1899 for chunkdata in iter(source.filelogheader, {}):
1900 for chunkdata in iter(source.filelogheader, {}):
1900 files += 1
1901 files += 1
1901 f = chunkdata[b"filename"]
1902 f = chunkdata[b"filename"]
1902 repo.ui.debug(b"adding %s revisions\n" % f)
1903 repo.ui.debug(b"adding %s revisions\n" % f)
1903 progress.increment()
1904 progress.increment()
1904 fl = repo.file(f)
1905 fl = repo.file(f)
1905 o = len(fl)
1906 o = len(fl)
1906 try:
1907 try:
1907 deltas = source.deltaiter()
1908 deltas = source.deltaiter()
1908 added = fl.addgroup(
1909 added = fl.addgroup(
1909 deltas,
1910 deltas,
1910 revmap,
1911 revmap,
1911 trp,
1912 trp,
1912 addrevisioncb=addrevisioncb,
1913 addrevisioncb=addrevisioncb,
1913 )
1914 )
1914 if not added:
1915 if not added:
1915 raise error.Abort(_(b"received file revlog group is empty"))
1916 raise error.Abort(_(b"received file revlog group is empty"))
1916 except error.CensoredBaseError as e:
1917 except error.CensoredBaseError as e:
1917 raise error.Abort(_(b"received delta base is censored: %s") % e)
1918 raise error.Abort(_(b"received delta base is censored: %s") % e)
1918 revisions += len(fl) - o
1919 revisions += len(fl) - o
1919 if f in needfiles:
1920 if f in needfiles:
1920 needs = needfiles[f]
1921 needs = needfiles[f]
1921 for new in pycompat.xrange(o, len(fl)):
1922 for new in pycompat.xrange(o, len(fl)):
1922 n = fl.node(new)
1923 n = fl.node(new)
1923 if n in needs:
1924 if n in needs:
1924 needs.remove(n)
1925 needs.remove(n)
1925 else:
1926 else:
1926 raise error.Abort(_(b"received spurious file revlog entry"))
1927 raise error.Abort(_(b"received spurious file revlog entry"))
1927 if not needs:
1928 if not needs:
1928 del needfiles[f]
1929 del needfiles[f]
1929 progress.complete()
1930 progress.complete()
1930
1931
1931 for f, needs in pycompat.iteritems(needfiles):
1932 for f, needs in pycompat.iteritems(needfiles):
1932 fl = repo.file(f)
1933 fl = repo.file(f)
1933 for n in needs:
1934 for n in needs:
1934 try:
1935 try:
1935 fl.rev(n)
1936 fl.rev(n)
1936 except error.LookupError:
1937 except error.LookupError:
1937 raise error.Abort(
1938 raise error.Abort(
1938 _(b'missing file data for %s:%s - run hg verify')
1939 _(b'missing file data for %s:%s - run hg verify')
1939 % (f, hex(n))
1940 % (f, hex(n))
1940 )
1941 )
1941
1942
1942 return revisions, files
1943 return revisions, files
1943
1944
1945 def get_sidedata_helpers(repo, remote_sd_categories, pull=False):
1946 # Computers for computing sidedata on-the-fly
1947 sd_computers = collections.defaultdict(list)
1948 # Computers for categories to remove from sidedata
1949 sd_removers = collections.defaultdict(list)
1950 to_generate = remote_sd_categories - repo._wanted_sidedata
1951 to_remove = repo._wanted_sidedata - remote_sd_categories
1952 if pull:
1953 to_generate, to_remove = to_remove, to_generate
1954
1955 for revlog_kind, computers in repo._sidedata_computers.items():
1956 for category, computer in computers.items():
1957 if category in to_generate:
1958 sd_computers[revlog_kind].append(computer)
1959 if category in to_remove:
1960 sd_removers[revlog_kind].append(computer)
1961
1962 sidedata_helpers = (repo, sd_computers, sd_removers)
1963 return sidedata_helpers
@@ -1,3772 +1,3772 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository 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 errno
10 import errno
11 import functools
11 import functools
12 import os
12 import os
13 import random
13 import random
14 import sys
14 import sys
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 bin,
20 bin,
21 hex,
21 hex,
22 nullrev,
22 nullrev,
23 sha1nodeconstants,
23 sha1nodeconstants,
24 short,
24 short,
25 )
25 )
26 from .pycompat import (
26 from .pycompat import (
27 delattr,
27 delattr,
28 getattr,
28 getattr,
29 )
29 )
30 from . import (
30 from . import (
31 bookmarks,
31 bookmarks,
32 branchmap,
32 branchmap,
33 bundle2,
33 bundle2,
34 bundlecaches,
34 bundlecaches,
35 changegroup,
35 changegroup,
36 color,
36 color,
37 commit,
37 commit,
38 context,
38 context,
39 dirstate,
39 dirstate,
40 dirstateguard,
40 dirstateguard,
41 discovery,
41 discovery,
42 encoding,
42 encoding,
43 error,
43 error,
44 exchange,
44 exchange,
45 extensions,
45 extensions,
46 filelog,
46 filelog,
47 hook,
47 hook,
48 lock as lockmod,
48 lock as lockmod,
49 match as matchmod,
49 match as matchmod,
50 mergestate as mergestatemod,
50 mergestate as mergestatemod,
51 mergeutil,
51 mergeutil,
52 metadata as metadatamod,
53 namespaces,
52 namespaces,
54 narrowspec,
53 narrowspec,
55 obsolete,
54 obsolete,
56 pathutil,
55 pathutil,
57 phases,
56 phases,
58 pushkey,
57 pushkey,
59 pycompat,
58 pycompat,
60 rcutil,
59 rcutil,
61 repoview,
60 repoview,
62 requirements as requirementsmod,
61 requirements as requirementsmod,
63 revlog,
62 revlog,
64 revset,
63 revset,
65 revsetlang,
64 revsetlang,
66 scmutil,
65 scmutil,
67 sparse,
66 sparse,
68 store as storemod,
67 store as storemod,
69 subrepoutil,
68 subrepoutil,
70 tags as tagsmod,
69 tags as tagsmod,
71 transaction,
70 transaction,
72 txnutil,
71 txnutil,
73 util,
72 util,
74 vfs as vfsmod,
73 vfs as vfsmod,
75 wireprototypes,
74 wireprototypes,
76 )
75 )
77
76
78 from .interfaces import (
77 from .interfaces import (
79 repository,
78 repository,
80 util as interfaceutil,
79 util as interfaceutil,
81 )
80 )
82
81
83 from .utils import (
82 from .utils import (
84 hashutil,
83 hashutil,
85 procutil,
84 procutil,
86 stringutil,
85 stringutil,
87 urlutil,
86 urlutil,
88 )
87 )
89
88
90 from .revlogutils import (
89 from .revlogutils import (
91 concurrency_checker as revlogchecker,
90 concurrency_checker as revlogchecker,
92 constants as revlogconst,
91 constants as revlogconst,
92 sidedata as sidedatamod,
93 )
93 )
94
94
95 release = lockmod.release
95 release = lockmod.release
96 urlerr = util.urlerr
96 urlerr = util.urlerr
97 urlreq = util.urlreq
97 urlreq = util.urlreq
98
98
99 # set of (path, vfs-location) tuples. vfs-location is:
99 # set of (path, vfs-location) tuples. vfs-location is:
100 # - 'plain for vfs relative paths
100 # - 'plain for vfs relative paths
101 # - '' for svfs relative paths
101 # - '' for svfs relative paths
102 _cachedfiles = set()
102 _cachedfiles = set()
103
103
104
104
105 class _basefilecache(scmutil.filecache):
105 class _basefilecache(scmutil.filecache):
106 """All filecache usage on repo are done for logic that should be unfiltered"""
106 """All filecache usage on repo are done for logic that should be unfiltered"""
107
107
108 def __get__(self, repo, type=None):
108 def __get__(self, repo, type=None):
109 if repo is None:
109 if repo is None:
110 return self
110 return self
111 # proxy to unfiltered __dict__ since filtered repo has no entry
111 # proxy to unfiltered __dict__ since filtered repo has no entry
112 unfi = repo.unfiltered()
112 unfi = repo.unfiltered()
113 try:
113 try:
114 return unfi.__dict__[self.sname]
114 return unfi.__dict__[self.sname]
115 except KeyError:
115 except KeyError:
116 pass
116 pass
117 return super(_basefilecache, self).__get__(unfi, type)
117 return super(_basefilecache, self).__get__(unfi, type)
118
118
119 def set(self, repo, value):
119 def set(self, repo, value):
120 return super(_basefilecache, self).set(repo.unfiltered(), value)
120 return super(_basefilecache, self).set(repo.unfiltered(), value)
121
121
122
122
123 class repofilecache(_basefilecache):
123 class repofilecache(_basefilecache):
124 """filecache for files in .hg but outside of .hg/store"""
124 """filecache for files in .hg but outside of .hg/store"""
125
125
126 def __init__(self, *paths):
126 def __init__(self, *paths):
127 super(repofilecache, self).__init__(*paths)
127 super(repofilecache, self).__init__(*paths)
128 for path in paths:
128 for path in paths:
129 _cachedfiles.add((path, b'plain'))
129 _cachedfiles.add((path, b'plain'))
130
130
131 def join(self, obj, fname):
131 def join(self, obj, fname):
132 return obj.vfs.join(fname)
132 return obj.vfs.join(fname)
133
133
134
134
135 class storecache(_basefilecache):
135 class storecache(_basefilecache):
136 """filecache for files in the store"""
136 """filecache for files in the store"""
137
137
138 def __init__(self, *paths):
138 def __init__(self, *paths):
139 super(storecache, self).__init__(*paths)
139 super(storecache, self).__init__(*paths)
140 for path in paths:
140 for path in paths:
141 _cachedfiles.add((path, b''))
141 _cachedfiles.add((path, b''))
142
142
143 def join(self, obj, fname):
143 def join(self, obj, fname):
144 return obj.sjoin(fname)
144 return obj.sjoin(fname)
145
145
146
146
147 class mixedrepostorecache(_basefilecache):
147 class mixedrepostorecache(_basefilecache):
148 """filecache for a mix files in .hg/store and outside"""
148 """filecache for a mix files in .hg/store and outside"""
149
149
150 def __init__(self, *pathsandlocations):
150 def __init__(self, *pathsandlocations):
151 # scmutil.filecache only uses the path for passing back into our
151 # scmutil.filecache only uses the path for passing back into our
152 # join(), so we can safely pass a list of paths and locations
152 # join(), so we can safely pass a list of paths and locations
153 super(mixedrepostorecache, self).__init__(*pathsandlocations)
153 super(mixedrepostorecache, self).__init__(*pathsandlocations)
154 _cachedfiles.update(pathsandlocations)
154 _cachedfiles.update(pathsandlocations)
155
155
156 def join(self, obj, fnameandlocation):
156 def join(self, obj, fnameandlocation):
157 fname, location = fnameandlocation
157 fname, location = fnameandlocation
158 if location == b'plain':
158 if location == b'plain':
159 return obj.vfs.join(fname)
159 return obj.vfs.join(fname)
160 else:
160 else:
161 if location != b'':
161 if location != b'':
162 raise error.ProgrammingError(
162 raise error.ProgrammingError(
163 b'unexpected location: %s' % location
163 b'unexpected location: %s' % location
164 )
164 )
165 return obj.sjoin(fname)
165 return obj.sjoin(fname)
166
166
167
167
168 def isfilecached(repo, name):
168 def isfilecached(repo, name):
169 """check if a repo has already cached "name" filecache-ed property
169 """check if a repo has already cached "name" filecache-ed property
170
170
171 This returns (cachedobj-or-None, iscached) tuple.
171 This returns (cachedobj-or-None, iscached) tuple.
172 """
172 """
173 cacheentry = repo.unfiltered()._filecache.get(name, None)
173 cacheentry = repo.unfiltered()._filecache.get(name, None)
174 if not cacheentry:
174 if not cacheentry:
175 return None, False
175 return None, False
176 return cacheentry.obj, True
176 return cacheentry.obj, True
177
177
178
178
179 class unfilteredpropertycache(util.propertycache):
179 class unfilteredpropertycache(util.propertycache):
180 """propertycache that apply to unfiltered repo only"""
180 """propertycache that apply to unfiltered repo only"""
181
181
182 def __get__(self, repo, type=None):
182 def __get__(self, repo, type=None):
183 unfi = repo.unfiltered()
183 unfi = repo.unfiltered()
184 if unfi is repo:
184 if unfi is repo:
185 return super(unfilteredpropertycache, self).__get__(unfi)
185 return super(unfilteredpropertycache, self).__get__(unfi)
186 return getattr(unfi, self.name)
186 return getattr(unfi, self.name)
187
187
188
188
189 class filteredpropertycache(util.propertycache):
189 class filteredpropertycache(util.propertycache):
190 """propertycache that must take filtering in account"""
190 """propertycache that must take filtering in account"""
191
191
192 def cachevalue(self, obj, value):
192 def cachevalue(self, obj, value):
193 object.__setattr__(obj, self.name, value)
193 object.__setattr__(obj, self.name, value)
194
194
195
195
196 def hasunfilteredcache(repo, name):
196 def hasunfilteredcache(repo, name):
197 """check if a repo has an unfilteredpropertycache value for <name>"""
197 """check if a repo has an unfilteredpropertycache value for <name>"""
198 return name in vars(repo.unfiltered())
198 return name in vars(repo.unfiltered())
199
199
200
200
201 def unfilteredmethod(orig):
201 def unfilteredmethod(orig):
202 """decorate method that always need to be run on unfiltered version"""
202 """decorate method that always need to be run on unfiltered version"""
203
203
204 @functools.wraps(orig)
204 @functools.wraps(orig)
205 def wrapper(repo, *args, **kwargs):
205 def wrapper(repo, *args, **kwargs):
206 return orig(repo.unfiltered(), *args, **kwargs)
206 return orig(repo.unfiltered(), *args, **kwargs)
207
207
208 return wrapper
208 return wrapper
209
209
210
210
211 moderncaps = {
211 moderncaps = {
212 b'lookup',
212 b'lookup',
213 b'branchmap',
213 b'branchmap',
214 b'pushkey',
214 b'pushkey',
215 b'known',
215 b'known',
216 b'getbundle',
216 b'getbundle',
217 b'unbundle',
217 b'unbundle',
218 }
218 }
219 legacycaps = moderncaps.union({b'changegroupsubset'})
219 legacycaps = moderncaps.union({b'changegroupsubset'})
220
220
221
221
222 @interfaceutil.implementer(repository.ipeercommandexecutor)
222 @interfaceutil.implementer(repository.ipeercommandexecutor)
223 class localcommandexecutor(object):
223 class localcommandexecutor(object):
224 def __init__(self, peer):
224 def __init__(self, peer):
225 self._peer = peer
225 self._peer = peer
226 self._sent = False
226 self._sent = False
227 self._closed = False
227 self._closed = False
228
228
229 def __enter__(self):
229 def __enter__(self):
230 return self
230 return self
231
231
232 def __exit__(self, exctype, excvalue, exctb):
232 def __exit__(self, exctype, excvalue, exctb):
233 self.close()
233 self.close()
234
234
235 def callcommand(self, command, args):
235 def callcommand(self, command, args):
236 if self._sent:
236 if self._sent:
237 raise error.ProgrammingError(
237 raise error.ProgrammingError(
238 b'callcommand() cannot be used after sendcommands()'
238 b'callcommand() cannot be used after sendcommands()'
239 )
239 )
240
240
241 if self._closed:
241 if self._closed:
242 raise error.ProgrammingError(
242 raise error.ProgrammingError(
243 b'callcommand() cannot be used after close()'
243 b'callcommand() cannot be used after close()'
244 )
244 )
245
245
246 # We don't need to support anything fancy. Just call the named
246 # We don't need to support anything fancy. Just call the named
247 # method on the peer and return a resolved future.
247 # method on the peer and return a resolved future.
248 fn = getattr(self._peer, pycompat.sysstr(command))
248 fn = getattr(self._peer, pycompat.sysstr(command))
249
249
250 f = pycompat.futures.Future()
250 f = pycompat.futures.Future()
251
251
252 try:
252 try:
253 result = fn(**pycompat.strkwargs(args))
253 result = fn(**pycompat.strkwargs(args))
254 except Exception:
254 except Exception:
255 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
255 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
256 else:
256 else:
257 f.set_result(result)
257 f.set_result(result)
258
258
259 return f
259 return f
260
260
261 def sendcommands(self):
261 def sendcommands(self):
262 self._sent = True
262 self._sent = True
263
263
264 def close(self):
264 def close(self):
265 self._closed = True
265 self._closed = True
266
266
267
267
268 @interfaceutil.implementer(repository.ipeercommands)
268 @interfaceutil.implementer(repository.ipeercommands)
269 class localpeer(repository.peer):
269 class localpeer(repository.peer):
270 '''peer for a local repo; reflects only the most recent API'''
270 '''peer for a local repo; reflects only the most recent API'''
271
271
272 def __init__(self, repo, caps=None):
272 def __init__(self, repo, caps=None):
273 super(localpeer, self).__init__()
273 super(localpeer, self).__init__()
274
274
275 if caps is None:
275 if caps is None:
276 caps = moderncaps.copy()
276 caps = moderncaps.copy()
277 self._repo = repo.filtered(b'served')
277 self._repo = repo.filtered(b'served')
278 self.ui = repo.ui
278 self.ui = repo.ui
279
279
280 if repo._wanted_sidedata:
280 if repo._wanted_sidedata:
281 formatted = bundle2.format_remote_wanted_sidedata(repo)
281 formatted = bundle2.format_remote_wanted_sidedata(repo)
282 caps.add(b'exp-wanted-sidedata=' + formatted)
282 caps.add(b'exp-wanted-sidedata=' + formatted)
283
283
284 self._caps = repo._restrictcapabilities(caps)
284 self._caps = repo._restrictcapabilities(caps)
285
285
286 # Begin of _basepeer interface.
286 # Begin of _basepeer interface.
287
287
288 def url(self):
288 def url(self):
289 return self._repo.url()
289 return self._repo.url()
290
290
291 def local(self):
291 def local(self):
292 return self._repo
292 return self._repo
293
293
294 def peer(self):
294 def peer(self):
295 return self
295 return self
296
296
297 def canpush(self):
297 def canpush(self):
298 return True
298 return True
299
299
300 def close(self):
300 def close(self):
301 self._repo.close()
301 self._repo.close()
302
302
303 # End of _basepeer interface.
303 # End of _basepeer interface.
304
304
305 # Begin of _basewirecommands interface.
305 # Begin of _basewirecommands interface.
306
306
307 def branchmap(self):
307 def branchmap(self):
308 return self._repo.branchmap()
308 return self._repo.branchmap()
309
309
310 def capabilities(self):
310 def capabilities(self):
311 return self._caps
311 return self._caps
312
312
313 def clonebundles(self):
313 def clonebundles(self):
314 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
314 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
315
315
316 def debugwireargs(self, one, two, three=None, four=None, five=None):
316 def debugwireargs(self, one, two, three=None, four=None, five=None):
317 """Used to test argument passing over the wire"""
317 """Used to test argument passing over the wire"""
318 return b"%s %s %s %s %s" % (
318 return b"%s %s %s %s %s" % (
319 one,
319 one,
320 two,
320 two,
321 pycompat.bytestr(three),
321 pycompat.bytestr(three),
322 pycompat.bytestr(four),
322 pycompat.bytestr(four),
323 pycompat.bytestr(five),
323 pycompat.bytestr(five),
324 )
324 )
325
325
326 def getbundle(
326 def getbundle(
327 self,
327 self,
328 source,
328 source,
329 heads=None,
329 heads=None,
330 common=None,
330 common=None,
331 bundlecaps=None,
331 bundlecaps=None,
332 remote_sidedata=None,
332 remote_sidedata=None,
333 **kwargs
333 **kwargs
334 ):
334 ):
335 chunks = exchange.getbundlechunks(
335 chunks = exchange.getbundlechunks(
336 self._repo,
336 self._repo,
337 source,
337 source,
338 heads=heads,
338 heads=heads,
339 common=common,
339 common=common,
340 bundlecaps=bundlecaps,
340 bundlecaps=bundlecaps,
341 remote_sidedata=remote_sidedata,
341 remote_sidedata=remote_sidedata,
342 **kwargs
342 **kwargs
343 )[1]
343 )[1]
344 cb = util.chunkbuffer(chunks)
344 cb = util.chunkbuffer(chunks)
345
345
346 if exchange.bundle2requested(bundlecaps):
346 if exchange.bundle2requested(bundlecaps):
347 # When requesting a bundle2, getbundle returns a stream to make the
347 # When requesting a bundle2, getbundle returns a stream to make the
348 # wire level function happier. We need to build a proper object
348 # wire level function happier. We need to build a proper object
349 # from it in local peer.
349 # from it in local peer.
350 return bundle2.getunbundler(self.ui, cb)
350 return bundle2.getunbundler(self.ui, cb)
351 else:
351 else:
352 return changegroup.getunbundler(b'01', cb, None)
352 return changegroup.getunbundler(b'01', cb, None)
353
353
354 def heads(self):
354 def heads(self):
355 return self._repo.heads()
355 return self._repo.heads()
356
356
357 def known(self, nodes):
357 def known(self, nodes):
358 return self._repo.known(nodes)
358 return self._repo.known(nodes)
359
359
360 def listkeys(self, namespace):
360 def listkeys(self, namespace):
361 return self._repo.listkeys(namespace)
361 return self._repo.listkeys(namespace)
362
362
363 def lookup(self, key):
363 def lookup(self, key):
364 return self._repo.lookup(key)
364 return self._repo.lookup(key)
365
365
366 def pushkey(self, namespace, key, old, new):
366 def pushkey(self, namespace, key, old, new):
367 return self._repo.pushkey(namespace, key, old, new)
367 return self._repo.pushkey(namespace, key, old, new)
368
368
369 def stream_out(self):
369 def stream_out(self):
370 raise error.Abort(_(b'cannot perform stream clone against local peer'))
370 raise error.Abort(_(b'cannot perform stream clone against local peer'))
371
371
372 def unbundle(self, bundle, heads, url):
372 def unbundle(self, bundle, heads, url):
373 """apply a bundle on a repo
373 """apply a bundle on a repo
374
374
375 This function handles the repo locking itself."""
375 This function handles the repo locking itself."""
376 try:
376 try:
377 try:
377 try:
378 bundle = exchange.readbundle(self.ui, bundle, None)
378 bundle = exchange.readbundle(self.ui, bundle, None)
379 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
379 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
380 if util.safehasattr(ret, b'getchunks'):
380 if util.safehasattr(ret, b'getchunks'):
381 # This is a bundle20 object, turn it into an unbundler.
381 # This is a bundle20 object, turn it into an unbundler.
382 # This little dance should be dropped eventually when the
382 # This little dance should be dropped eventually when the
383 # API is finally improved.
383 # API is finally improved.
384 stream = util.chunkbuffer(ret.getchunks())
384 stream = util.chunkbuffer(ret.getchunks())
385 ret = bundle2.getunbundler(self.ui, stream)
385 ret = bundle2.getunbundler(self.ui, stream)
386 return ret
386 return ret
387 except Exception as exc:
387 except Exception as exc:
388 # If the exception contains output salvaged from a bundle2
388 # If the exception contains output salvaged from a bundle2
389 # reply, we need to make sure it is printed before continuing
389 # reply, we need to make sure it is printed before continuing
390 # to fail. So we build a bundle2 with such output and consume
390 # to fail. So we build a bundle2 with such output and consume
391 # it directly.
391 # it directly.
392 #
392 #
393 # This is not very elegant but allows a "simple" solution for
393 # This is not very elegant but allows a "simple" solution for
394 # issue4594
394 # issue4594
395 output = getattr(exc, '_bundle2salvagedoutput', ())
395 output = getattr(exc, '_bundle2salvagedoutput', ())
396 if output:
396 if output:
397 bundler = bundle2.bundle20(self._repo.ui)
397 bundler = bundle2.bundle20(self._repo.ui)
398 for out in output:
398 for out in output:
399 bundler.addpart(out)
399 bundler.addpart(out)
400 stream = util.chunkbuffer(bundler.getchunks())
400 stream = util.chunkbuffer(bundler.getchunks())
401 b = bundle2.getunbundler(self.ui, stream)
401 b = bundle2.getunbundler(self.ui, stream)
402 bundle2.processbundle(self._repo, b)
402 bundle2.processbundle(self._repo, b)
403 raise
403 raise
404 except error.PushRaced as exc:
404 except error.PushRaced as exc:
405 raise error.ResponseError(
405 raise error.ResponseError(
406 _(b'push failed:'), stringutil.forcebytestr(exc)
406 _(b'push failed:'), stringutil.forcebytestr(exc)
407 )
407 )
408
408
409 # End of _basewirecommands interface.
409 # End of _basewirecommands interface.
410
410
411 # Begin of peer interface.
411 # Begin of peer interface.
412
412
413 def commandexecutor(self):
413 def commandexecutor(self):
414 return localcommandexecutor(self)
414 return localcommandexecutor(self)
415
415
416 # End of peer interface.
416 # End of peer interface.
417
417
418
418
419 @interfaceutil.implementer(repository.ipeerlegacycommands)
419 @interfaceutil.implementer(repository.ipeerlegacycommands)
420 class locallegacypeer(localpeer):
420 class locallegacypeer(localpeer):
421 """peer extension which implements legacy methods too; used for tests with
421 """peer extension which implements legacy methods too; used for tests with
422 restricted capabilities"""
422 restricted capabilities"""
423
423
424 def __init__(self, repo):
424 def __init__(self, repo):
425 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
425 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
426
426
427 # Begin of baselegacywirecommands interface.
427 # Begin of baselegacywirecommands interface.
428
428
429 def between(self, pairs):
429 def between(self, pairs):
430 return self._repo.between(pairs)
430 return self._repo.between(pairs)
431
431
432 def branches(self, nodes):
432 def branches(self, nodes):
433 return self._repo.branches(nodes)
433 return self._repo.branches(nodes)
434
434
435 def changegroup(self, nodes, source):
435 def changegroup(self, nodes, source):
436 outgoing = discovery.outgoing(
436 outgoing = discovery.outgoing(
437 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
437 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
438 )
438 )
439 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
439 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
440
440
441 def changegroupsubset(self, bases, heads, source):
441 def changegroupsubset(self, bases, heads, source):
442 outgoing = discovery.outgoing(
442 outgoing = discovery.outgoing(
443 self._repo, missingroots=bases, ancestorsof=heads
443 self._repo, missingroots=bases, ancestorsof=heads
444 )
444 )
445 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
445 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
446
446
447 # End of baselegacywirecommands interface.
447 # End of baselegacywirecommands interface.
448
448
449
449
450 # Functions receiving (ui, features) that extensions can register to impact
450 # Functions receiving (ui, features) that extensions can register to impact
451 # the ability to load repositories with custom requirements. Only
451 # the ability to load repositories with custom requirements. Only
452 # functions defined in loaded extensions are called.
452 # functions defined in loaded extensions are called.
453 #
453 #
454 # The function receives a set of requirement strings that the repository
454 # The function receives a set of requirement strings that the repository
455 # is capable of opening. Functions will typically add elements to the
455 # is capable of opening. Functions will typically add elements to the
456 # set to reflect that the extension knows how to handle that requirements.
456 # set to reflect that the extension knows how to handle that requirements.
457 featuresetupfuncs = set()
457 featuresetupfuncs = set()
458
458
459
459
460 def _getsharedvfs(hgvfs, requirements):
460 def _getsharedvfs(hgvfs, requirements):
461 """returns the vfs object pointing to root of shared source
461 """returns the vfs object pointing to root of shared source
462 repo for a shared repository
462 repo for a shared repository
463
463
464 hgvfs is vfs pointing at .hg/ of current repo (shared one)
464 hgvfs is vfs pointing at .hg/ of current repo (shared one)
465 requirements is a set of requirements of current repo (shared one)
465 requirements is a set of requirements of current repo (shared one)
466 """
466 """
467 # The ``shared`` or ``relshared`` requirements indicate the
467 # The ``shared`` or ``relshared`` requirements indicate the
468 # store lives in the path contained in the ``.hg/sharedpath`` file.
468 # store lives in the path contained in the ``.hg/sharedpath`` file.
469 # This is an absolute path for ``shared`` and relative to
469 # This is an absolute path for ``shared`` and relative to
470 # ``.hg/`` for ``relshared``.
470 # ``.hg/`` for ``relshared``.
471 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
471 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
472 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
472 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
473 sharedpath = util.normpath(hgvfs.join(sharedpath))
473 sharedpath = util.normpath(hgvfs.join(sharedpath))
474
474
475 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
475 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
476
476
477 if not sharedvfs.exists():
477 if not sharedvfs.exists():
478 raise error.RepoError(
478 raise error.RepoError(
479 _(b'.hg/sharedpath points to nonexistent directory %s')
479 _(b'.hg/sharedpath points to nonexistent directory %s')
480 % sharedvfs.base
480 % sharedvfs.base
481 )
481 )
482 return sharedvfs
482 return sharedvfs
483
483
484
484
485 def _readrequires(vfs, allowmissing):
485 def _readrequires(vfs, allowmissing):
486 """reads the require file present at root of this vfs
486 """reads the require file present at root of this vfs
487 and return a set of requirements
487 and return a set of requirements
488
488
489 If allowmissing is True, we suppress ENOENT if raised"""
489 If allowmissing is True, we suppress ENOENT if raised"""
490 # requires file contains a newline-delimited list of
490 # requires file contains a newline-delimited list of
491 # features/capabilities the opener (us) must have in order to use
491 # features/capabilities the opener (us) must have in order to use
492 # the repository. This file was introduced in Mercurial 0.9.2,
492 # the repository. This file was introduced in Mercurial 0.9.2,
493 # which means very old repositories may not have one. We assume
493 # which means very old repositories may not have one. We assume
494 # a missing file translates to no requirements.
494 # a missing file translates to no requirements.
495 try:
495 try:
496 requirements = set(vfs.read(b'requires').splitlines())
496 requirements = set(vfs.read(b'requires').splitlines())
497 except IOError as e:
497 except IOError as e:
498 if not (allowmissing and e.errno == errno.ENOENT):
498 if not (allowmissing and e.errno == errno.ENOENT):
499 raise
499 raise
500 requirements = set()
500 requirements = set()
501 return requirements
501 return requirements
502
502
503
503
504 def makelocalrepository(baseui, path, intents=None):
504 def makelocalrepository(baseui, path, intents=None):
505 """Create a local repository object.
505 """Create a local repository object.
506
506
507 Given arguments needed to construct a local repository, this function
507 Given arguments needed to construct a local repository, this function
508 performs various early repository loading functionality (such as
508 performs various early repository loading functionality (such as
509 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
509 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
510 the repository can be opened, derives a type suitable for representing
510 the repository can be opened, derives a type suitable for representing
511 that repository, and returns an instance of it.
511 that repository, and returns an instance of it.
512
512
513 The returned object conforms to the ``repository.completelocalrepository``
513 The returned object conforms to the ``repository.completelocalrepository``
514 interface.
514 interface.
515
515
516 The repository type is derived by calling a series of factory functions
516 The repository type is derived by calling a series of factory functions
517 for each aspect/interface of the final repository. These are defined by
517 for each aspect/interface of the final repository. These are defined by
518 ``REPO_INTERFACES``.
518 ``REPO_INTERFACES``.
519
519
520 Each factory function is called to produce a type implementing a specific
520 Each factory function is called to produce a type implementing a specific
521 interface. The cumulative list of returned types will be combined into a
521 interface. The cumulative list of returned types will be combined into a
522 new type and that type will be instantiated to represent the local
522 new type and that type will be instantiated to represent the local
523 repository.
523 repository.
524
524
525 The factory functions each receive various state that may be consulted
525 The factory functions each receive various state that may be consulted
526 as part of deriving a type.
526 as part of deriving a type.
527
527
528 Extensions should wrap these factory functions to customize repository type
528 Extensions should wrap these factory functions to customize repository type
529 creation. Note that an extension's wrapped function may be called even if
529 creation. Note that an extension's wrapped function may be called even if
530 that extension is not loaded for the repo being constructed. Extensions
530 that extension is not loaded for the repo being constructed. Extensions
531 should check if their ``__name__`` appears in the
531 should check if their ``__name__`` appears in the
532 ``extensionmodulenames`` set passed to the factory function and no-op if
532 ``extensionmodulenames`` set passed to the factory function and no-op if
533 not.
533 not.
534 """
534 """
535 ui = baseui.copy()
535 ui = baseui.copy()
536 # Prevent copying repo configuration.
536 # Prevent copying repo configuration.
537 ui.copy = baseui.copy
537 ui.copy = baseui.copy
538
538
539 # Working directory VFS rooted at repository root.
539 # Working directory VFS rooted at repository root.
540 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
540 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
541
541
542 # Main VFS for .hg/ directory.
542 # Main VFS for .hg/ directory.
543 hgpath = wdirvfs.join(b'.hg')
543 hgpath = wdirvfs.join(b'.hg')
544 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
544 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
545 # Whether this repository is shared one or not
545 # Whether this repository is shared one or not
546 shared = False
546 shared = False
547 # If this repository is shared, vfs pointing to shared repo
547 # If this repository is shared, vfs pointing to shared repo
548 sharedvfs = None
548 sharedvfs = None
549
549
550 # The .hg/ path should exist and should be a directory. All other
550 # The .hg/ path should exist and should be a directory. All other
551 # cases are errors.
551 # cases are errors.
552 if not hgvfs.isdir():
552 if not hgvfs.isdir():
553 try:
553 try:
554 hgvfs.stat()
554 hgvfs.stat()
555 except OSError as e:
555 except OSError as e:
556 if e.errno != errno.ENOENT:
556 if e.errno != errno.ENOENT:
557 raise
557 raise
558 except ValueError as e:
558 except ValueError as e:
559 # Can be raised on Python 3.8 when path is invalid.
559 # Can be raised on Python 3.8 when path is invalid.
560 raise error.Abort(
560 raise error.Abort(
561 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
561 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
562 )
562 )
563
563
564 raise error.RepoError(_(b'repository %s not found') % path)
564 raise error.RepoError(_(b'repository %s not found') % path)
565
565
566 requirements = _readrequires(hgvfs, True)
566 requirements = _readrequires(hgvfs, True)
567 shared = (
567 shared = (
568 requirementsmod.SHARED_REQUIREMENT in requirements
568 requirementsmod.SHARED_REQUIREMENT in requirements
569 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
569 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
570 )
570 )
571 storevfs = None
571 storevfs = None
572 if shared:
572 if shared:
573 # This is a shared repo
573 # This is a shared repo
574 sharedvfs = _getsharedvfs(hgvfs, requirements)
574 sharedvfs = _getsharedvfs(hgvfs, requirements)
575 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
575 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
576 else:
576 else:
577 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
577 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
578
578
579 # if .hg/requires contains the sharesafe requirement, it means
579 # if .hg/requires contains the sharesafe requirement, it means
580 # there exists a `.hg/store/requires` too and we should read it
580 # there exists a `.hg/store/requires` too and we should read it
581 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
581 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
582 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
582 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
583 # is not present, refer checkrequirementscompat() for that
583 # is not present, refer checkrequirementscompat() for that
584 #
584 #
585 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
585 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
586 # repository was shared the old way. We check the share source .hg/requires
586 # repository was shared the old way. We check the share source .hg/requires
587 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
587 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
588 # to be reshared
588 # to be reshared
589 hint = _(b"see `hg help config.format.use-share-safe` for more information")
589 hint = _(b"see `hg help config.format.use-share-safe` for more information")
590 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
590 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
591
591
592 if (
592 if (
593 shared
593 shared
594 and requirementsmod.SHARESAFE_REQUIREMENT
594 and requirementsmod.SHARESAFE_REQUIREMENT
595 not in _readrequires(sharedvfs, True)
595 not in _readrequires(sharedvfs, True)
596 ):
596 ):
597 mismatch_warn = ui.configbool(
597 mismatch_warn = ui.configbool(
598 b'share', b'safe-mismatch.source-not-safe.warn'
598 b'share', b'safe-mismatch.source-not-safe.warn'
599 )
599 )
600 mismatch_config = ui.config(
600 mismatch_config = ui.config(
601 b'share', b'safe-mismatch.source-not-safe'
601 b'share', b'safe-mismatch.source-not-safe'
602 )
602 )
603 if mismatch_config in (
603 if mismatch_config in (
604 b'downgrade-allow',
604 b'downgrade-allow',
605 b'allow',
605 b'allow',
606 b'downgrade-abort',
606 b'downgrade-abort',
607 ):
607 ):
608 # prevent cyclic import localrepo -> upgrade -> localrepo
608 # prevent cyclic import localrepo -> upgrade -> localrepo
609 from . import upgrade
609 from . import upgrade
610
610
611 upgrade.downgrade_share_to_non_safe(
611 upgrade.downgrade_share_to_non_safe(
612 ui,
612 ui,
613 hgvfs,
613 hgvfs,
614 sharedvfs,
614 sharedvfs,
615 requirements,
615 requirements,
616 mismatch_config,
616 mismatch_config,
617 mismatch_warn,
617 mismatch_warn,
618 )
618 )
619 elif mismatch_config == b'abort':
619 elif mismatch_config == b'abort':
620 raise error.Abort(
620 raise error.Abort(
621 _(b"share source does not support share-safe requirement"),
621 _(b"share source does not support share-safe requirement"),
622 hint=hint,
622 hint=hint,
623 )
623 )
624 else:
624 else:
625 raise error.Abort(
625 raise error.Abort(
626 _(
626 _(
627 b"share-safe mismatch with source.\nUnrecognized"
627 b"share-safe mismatch with source.\nUnrecognized"
628 b" value '%s' of `share.safe-mismatch.source-not-safe`"
628 b" value '%s' of `share.safe-mismatch.source-not-safe`"
629 b" set."
629 b" set."
630 )
630 )
631 % mismatch_config,
631 % mismatch_config,
632 hint=hint,
632 hint=hint,
633 )
633 )
634 else:
634 else:
635 requirements |= _readrequires(storevfs, False)
635 requirements |= _readrequires(storevfs, False)
636 elif shared:
636 elif shared:
637 sourcerequires = _readrequires(sharedvfs, False)
637 sourcerequires = _readrequires(sharedvfs, False)
638 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
638 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
639 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
639 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
640 mismatch_warn = ui.configbool(
640 mismatch_warn = ui.configbool(
641 b'share', b'safe-mismatch.source-safe.warn'
641 b'share', b'safe-mismatch.source-safe.warn'
642 )
642 )
643 if mismatch_config in (
643 if mismatch_config in (
644 b'upgrade-allow',
644 b'upgrade-allow',
645 b'allow',
645 b'allow',
646 b'upgrade-abort',
646 b'upgrade-abort',
647 ):
647 ):
648 # prevent cyclic import localrepo -> upgrade -> localrepo
648 # prevent cyclic import localrepo -> upgrade -> localrepo
649 from . import upgrade
649 from . import upgrade
650
650
651 upgrade.upgrade_share_to_safe(
651 upgrade.upgrade_share_to_safe(
652 ui,
652 ui,
653 hgvfs,
653 hgvfs,
654 storevfs,
654 storevfs,
655 requirements,
655 requirements,
656 mismatch_config,
656 mismatch_config,
657 mismatch_warn,
657 mismatch_warn,
658 )
658 )
659 elif mismatch_config == b'abort':
659 elif mismatch_config == b'abort':
660 raise error.Abort(
660 raise error.Abort(
661 _(
661 _(
662 b'version mismatch: source uses share-safe'
662 b'version mismatch: source uses share-safe'
663 b' functionality while the current share does not'
663 b' functionality while the current share does not'
664 ),
664 ),
665 hint=hint,
665 hint=hint,
666 )
666 )
667 else:
667 else:
668 raise error.Abort(
668 raise error.Abort(
669 _(
669 _(
670 b"share-safe mismatch with source.\nUnrecognized"
670 b"share-safe mismatch with source.\nUnrecognized"
671 b" value '%s' of `share.safe-mismatch.source-safe` set."
671 b" value '%s' of `share.safe-mismatch.source-safe` set."
672 )
672 )
673 % mismatch_config,
673 % mismatch_config,
674 hint=hint,
674 hint=hint,
675 )
675 )
676
676
677 # The .hg/hgrc file may load extensions or contain config options
677 # The .hg/hgrc file may load extensions or contain config options
678 # that influence repository construction. Attempt to load it and
678 # that influence repository construction. Attempt to load it and
679 # process any new extensions that it may have pulled in.
679 # process any new extensions that it may have pulled in.
680 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
680 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
681 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
681 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
682 extensions.loadall(ui)
682 extensions.loadall(ui)
683 extensions.populateui(ui)
683 extensions.populateui(ui)
684
684
685 # Set of module names of extensions loaded for this repository.
685 # Set of module names of extensions loaded for this repository.
686 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
686 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
687
687
688 supportedrequirements = gathersupportedrequirements(ui)
688 supportedrequirements = gathersupportedrequirements(ui)
689
689
690 # We first validate the requirements are known.
690 # We first validate the requirements are known.
691 ensurerequirementsrecognized(requirements, supportedrequirements)
691 ensurerequirementsrecognized(requirements, supportedrequirements)
692
692
693 # Then we validate that the known set is reasonable to use together.
693 # Then we validate that the known set is reasonable to use together.
694 ensurerequirementscompatible(ui, requirements)
694 ensurerequirementscompatible(ui, requirements)
695
695
696 # TODO there are unhandled edge cases related to opening repositories with
696 # TODO there are unhandled edge cases related to opening repositories with
697 # shared storage. If storage is shared, we should also test for requirements
697 # shared storage. If storage is shared, we should also test for requirements
698 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
698 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
699 # that repo, as that repo may load extensions needed to open it. This is a
699 # that repo, as that repo may load extensions needed to open it. This is a
700 # bit complicated because we don't want the other hgrc to overwrite settings
700 # bit complicated because we don't want the other hgrc to overwrite settings
701 # in this hgrc.
701 # in this hgrc.
702 #
702 #
703 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
703 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
704 # file when sharing repos. But if a requirement is added after the share is
704 # file when sharing repos. But if a requirement is added after the share is
705 # performed, thereby introducing a new requirement for the opener, we may
705 # performed, thereby introducing a new requirement for the opener, we may
706 # will not see that and could encounter a run-time error interacting with
706 # will not see that and could encounter a run-time error interacting with
707 # that shared store since it has an unknown-to-us requirement.
707 # that shared store since it has an unknown-to-us requirement.
708
708
709 # At this point, we know we should be capable of opening the repository.
709 # At this point, we know we should be capable of opening the repository.
710 # Now get on with doing that.
710 # Now get on with doing that.
711
711
712 features = set()
712 features = set()
713
713
714 # The "store" part of the repository holds versioned data. How it is
714 # The "store" part of the repository holds versioned data. How it is
715 # accessed is determined by various requirements. If `shared` or
715 # accessed is determined by various requirements. If `shared` or
716 # `relshared` requirements are present, this indicates current repository
716 # `relshared` requirements are present, this indicates current repository
717 # is a share and store exists in path mentioned in `.hg/sharedpath`
717 # is a share and store exists in path mentioned in `.hg/sharedpath`
718 if shared:
718 if shared:
719 storebasepath = sharedvfs.base
719 storebasepath = sharedvfs.base
720 cachepath = sharedvfs.join(b'cache')
720 cachepath = sharedvfs.join(b'cache')
721 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
721 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
722 else:
722 else:
723 storebasepath = hgvfs.base
723 storebasepath = hgvfs.base
724 cachepath = hgvfs.join(b'cache')
724 cachepath = hgvfs.join(b'cache')
725 wcachepath = hgvfs.join(b'wcache')
725 wcachepath = hgvfs.join(b'wcache')
726
726
727 # The store has changed over time and the exact layout is dictated by
727 # The store has changed over time and the exact layout is dictated by
728 # requirements. The store interface abstracts differences across all
728 # requirements. The store interface abstracts differences across all
729 # of them.
729 # of them.
730 store = makestore(
730 store = makestore(
731 requirements,
731 requirements,
732 storebasepath,
732 storebasepath,
733 lambda base: vfsmod.vfs(base, cacheaudited=True),
733 lambda base: vfsmod.vfs(base, cacheaudited=True),
734 )
734 )
735 hgvfs.createmode = store.createmode
735 hgvfs.createmode = store.createmode
736
736
737 storevfs = store.vfs
737 storevfs = store.vfs
738 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
738 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
739
739
740 # The cache vfs is used to manage cache files.
740 # The cache vfs is used to manage cache files.
741 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
741 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
742 cachevfs.createmode = store.createmode
742 cachevfs.createmode = store.createmode
743 # The cache vfs is used to manage cache files related to the working copy
743 # The cache vfs is used to manage cache files related to the working copy
744 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
744 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
745 wcachevfs.createmode = store.createmode
745 wcachevfs.createmode = store.createmode
746
746
747 # Now resolve the type for the repository object. We do this by repeatedly
747 # Now resolve the type for the repository object. We do this by repeatedly
748 # calling a factory function to produces types for specific aspects of the
748 # calling a factory function to produces types for specific aspects of the
749 # repo's operation. The aggregate returned types are used as base classes
749 # repo's operation. The aggregate returned types are used as base classes
750 # for a dynamically-derived type, which will represent our new repository.
750 # for a dynamically-derived type, which will represent our new repository.
751
751
752 bases = []
752 bases = []
753 extrastate = {}
753 extrastate = {}
754
754
755 for iface, fn in REPO_INTERFACES:
755 for iface, fn in REPO_INTERFACES:
756 # We pass all potentially useful state to give extensions tons of
756 # We pass all potentially useful state to give extensions tons of
757 # flexibility.
757 # flexibility.
758 typ = fn()(
758 typ = fn()(
759 ui=ui,
759 ui=ui,
760 intents=intents,
760 intents=intents,
761 requirements=requirements,
761 requirements=requirements,
762 features=features,
762 features=features,
763 wdirvfs=wdirvfs,
763 wdirvfs=wdirvfs,
764 hgvfs=hgvfs,
764 hgvfs=hgvfs,
765 store=store,
765 store=store,
766 storevfs=storevfs,
766 storevfs=storevfs,
767 storeoptions=storevfs.options,
767 storeoptions=storevfs.options,
768 cachevfs=cachevfs,
768 cachevfs=cachevfs,
769 wcachevfs=wcachevfs,
769 wcachevfs=wcachevfs,
770 extensionmodulenames=extensionmodulenames,
770 extensionmodulenames=extensionmodulenames,
771 extrastate=extrastate,
771 extrastate=extrastate,
772 baseclasses=bases,
772 baseclasses=bases,
773 )
773 )
774
774
775 if not isinstance(typ, type):
775 if not isinstance(typ, type):
776 raise error.ProgrammingError(
776 raise error.ProgrammingError(
777 b'unable to construct type for %s' % iface
777 b'unable to construct type for %s' % iface
778 )
778 )
779
779
780 bases.append(typ)
780 bases.append(typ)
781
781
782 # type() allows you to use characters in type names that wouldn't be
782 # type() allows you to use characters in type names that wouldn't be
783 # recognized as Python symbols in source code. We abuse that to add
783 # recognized as Python symbols in source code. We abuse that to add
784 # rich information about our constructed repo.
784 # rich information about our constructed repo.
785 name = pycompat.sysstr(
785 name = pycompat.sysstr(
786 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
786 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
787 )
787 )
788
788
789 cls = type(name, tuple(bases), {})
789 cls = type(name, tuple(bases), {})
790
790
791 return cls(
791 return cls(
792 baseui=baseui,
792 baseui=baseui,
793 ui=ui,
793 ui=ui,
794 origroot=path,
794 origroot=path,
795 wdirvfs=wdirvfs,
795 wdirvfs=wdirvfs,
796 hgvfs=hgvfs,
796 hgvfs=hgvfs,
797 requirements=requirements,
797 requirements=requirements,
798 supportedrequirements=supportedrequirements,
798 supportedrequirements=supportedrequirements,
799 sharedpath=storebasepath,
799 sharedpath=storebasepath,
800 store=store,
800 store=store,
801 cachevfs=cachevfs,
801 cachevfs=cachevfs,
802 wcachevfs=wcachevfs,
802 wcachevfs=wcachevfs,
803 features=features,
803 features=features,
804 intents=intents,
804 intents=intents,
805 )
805 )
806
806
807
807
808 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
808 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
809 """Load hgrc files/content into a ui instance.
809 """Load hgrc files/content into a ui instance.
810
810
811 This is called during repository opening to load any additional
811 This is called during repository opening to load any additional
812 config files or settings relevant to the current repository.
812 config files or settings relevant to the current repository.
813
813
814 Returns a bool indicating whether any additional configs were loaded.
814 Returns a bool indicating whether any additional configs were loaded.
815
815
816 Extensions should monkeypatch this function to modify how per-repo
816 Extensions should monkeypatch this function to modify how per-repo
817 configs are loaded. For example, an extension may wish to pull in
817 configs are loaded. For example, an extension may wish to pull in
818 configs from alternate files or sources.
818 configs from alternate files or sources.
819
819
820 sharedvfs is vfs object pointing to source repo if the current one is a
820 sharedvfs is vfs object pointing to source repo if the current one is a
821 shared one
821 shared one
822 """
822 """
823 if not rcutil.use_repo_hgrc():
823 if not rcutil.use_repo_hgrc():
824 return False
824 return False
825
825
826 ret = False
826 ret = False
827 # first load config from shared source if we has to
827 # first load config from shared source if we has to
828 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
828 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
829 try:
829 try:
830 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
830 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
831 ret = True
831 ret = True
832 except IOError:
832 except IOError:
833 pass
833 pass
834
834
835 try:
835 try:
836 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
836 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
837 ret = True
837 ret = True
838 except IOError:
838 except IOError:
839 pass
839 pass
840
840
841 try:
841 try:
842 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
842 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
843 ret = True
843 ret = True
844 except IOError:
844 except IOError:
845 pass
845 pass
846
846
847 return ret
847 return ret
848
848
849
849
850 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
850 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
851 """Perform additional actions after .hg/hgrc is loaded.
851 """Perform additional actions after .hg/hgrc is loaded.
852
852
853 This function is called during repository loading immediately after
853 This function is called during repository loading immediately after
854 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
854 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
855
855
856 The function can be used to validate configs, automatically add
856 The function can be used to validate configs, automatically add
857 options (including extensions) based on requirements, etc.
857 options (including extensions) based on requirements, etc.
858 """
858 """
859
859
860 # Map of requirements to list of extensions to load automatically when
860 # Map of requirements to list of extensions to load automatically when
861 # requirement is present.
861 # requirement is present.
862 autoextensions = {
862 autoextensions = {
863 b'git': [b'git'],
863 b'git': [b'git'],
864 b'largefiles': [b'largefiles'],
864 b'largefiles': [b'largefiles'],
865 b'lfs': [b'lfs'],
865 b'lfs': [b'lfs'],
866 }
866 }
867
867
868 for requirement, names in sorted(autoextensions.items()):
868 for requirement, names in sorted(autoextensions.items()):
869 if requirement not in requirements:
869 if requirement not in requirements:
870 continue
870 continue
871
871
872 for name in names:
872 for name in names:
873 if not ui.hasconfig(b'extensions', name):
873 if not ui.hasconfig(b'extensions', name):
874 ui.setconfig(b'extensions', name, b'', source=b'autoload')
874 ui.setconfig(b'extensions', name, b'', source=b'autoload')
875
875
876
876
877 def gathersupportedrequirements(ui):
877 def gathersupportedrequirements(ui):
878 """Determine the complete set of recognized requirements."""
878 """Determine the complete set of recognized requirements."""
879 # Start with all requirements supported by this file.
879 # Start with all requirements supported by this file.
880 supported = set(localrepository._basesupported)
880 supported = set(localrepository._basesupported)
881
881
882 # Execute ``featuresetupfuncs`` entries if they belong to an extension
882 # Execute ``featuresetupfuncs`` entries if they belong to an extension
883 # relevant to this ui instance.
883 # relevant to this ui instance.
884 modules = {m.__name__ for n, m in extensions.extensions(ui)}
884 modules = {m.__name__ for n, m in extensions.extensions(ui)}
885
885
886 for fn in featuresetupfuncs:
886 for fn in featuresetupfuncs:
887 if fn.__module__ in modules:
887 if fn.__module__ in modules:
888 fn(ui, supported)
888 fn(ui, supported)
889
889
890 # Add derived requirements from registered compression engines.
890 # Add derived requirements from registered compression engines.
891 for name in util.compengines:
891 for name in util.compengines:
892 engine = util.compengines[name]
892 engine = util.compengines[name]
893 if engine.available() and engine.revlogheader():
893 if engine.available() and engine.revlogheader():
894 supported.add(b'exp-compression-%s' % name)
894 supported.add(b'exp-compression-%s' % name)
895 if engine.name() == b'zstd':
895 if engine.name() == b'zstd':
896 supported.add(b'revlog-compression-zstd')
896 supported.add(b'revlog-compression-zstd')
897
897
898 return supported
898 return supported
899
899
900
900
901 def ensurerequirementsrecognized(requirements, supported):
901 def ensurerequirementsrecognized(requirements, supported):
902 """Validate that a set of local requirements is recognized.
902 """Validate that a set of local requirements is recognized.
903
903
904 Receives a set of requirements. Raises an ``error.RepoError`` if there
904 Receives a set of requirements. Raises an ``error.RepoError`` if there
905 exists any requirement in that set that currently loaded code doesn't
905 exists any requirement in that set that currently loaded code doesn't
906 recognize.
906 recognize.
907
907
908 Returns a set of supported requirements.
908 Returns a set of supported requirements.
909 """
909 """
910 missing = set()
910 missing = set()
911
911
912 for requirement in requirements:
912 for requirement in requirements:
913 if requirement in supported:
913 if requirement in supported:
914 continue
914 continue
915
915
916 if not requirement or not requirement[0:1].isalnum():
916 if not requirement or not requirement[0:1].isalnum():
917 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
917 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
918
918
919 missing.add(requirement)
919 missing.add(requirement)
920
920
921 if missing:
921 if missing:
922 raise error.RequirementError(
922 raise error.RequirementError(
923 _(b'repository requires features unknown to this Mercurial: %s')
923 _(b'repository requires features unknown to this Mercurial: %s')
924 % b' '.join(sorted(missing)),
924 % b' '.join(sorted(missing)),
925 hint=_(
925 hint=_(
926 b'see https://mercurial-scm.org/wiki/MissingRequirement '
926 b'see https://mercurial-scm.org/wiki/MissingRequirement '
927 b'for more information'
927 b'for more information'
928 ),
928 ),
929 )
929 )
930
930
931
931
932 def ensurerequirementscompatible(ui, requirements):
932 def ensurerequirementscompatible(ui, requirements):
933 """Validates that a set of recognized requirements is mutually compatible.
933 """Validates that a set of recognized requirements is mutually compatible.
934
934
935 Some requirements may not be compatible with others or require
935 Some requirements may not be compatible with others or require
936 config options that aren't enabled. This function is called during
936 config options that aren't enabled. This function is called during
937 repository opening to ensure that the set of requirements needed
937 repository opening to ensure that the set of requirements needed
938 to open a repository is sane and compatible with config options.
938 to open a repository is sane and compatible with config options.
939
939
940 Extensions can monkeypatch this function to perform additional
940 Extensions can monkeypatch this function to perform additional
941 checking.
941 checking.
942
942
943 ``error.RepoError`` should be raised on failure.
943 ``error.RepoError`` should be raised on failure.
944 """
944 """
945 if (
945 if (
946 requirementsmod.SPARSE_REQUIREMENT in requirements
946 requirementsmod.SPARSE_REQUIREMENT in requirements
947 and not sparse.enabled
947 and not sparse.enabled
948 ):
948 ):
949 raise error.RepoError(
949 raise error.RepoError(
950 _(
950 _(
951 b'repository is using sparse feature but '
951 b'repository is using sparse feature but '
952 b'sparse is not enabled; enable the '
952 b'sparse is not enabled; enable the '
953 b'"sparse" extensions to access'
953 b'"sparse" extensions to access'
954 )
954 )
955 )
955 )
956
956
957
957
958 def makestore(requirements, path, vfstype):
958 def makestore(requirements, path, vfstype):
959 """Construct a storage object for a repository."""
959 """Construct a storage object for a repository."""
960 if requirementsmod.STORE_REQUIREMENT in requirements:
960 if requirementsmod.STORE_REQUIREMENT in requirements:
961 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
961 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
962 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
962 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
963 return storemod.fncachestore(path, vfstype, dotencode)
963 return storemod.fncachestore(path, vfstype, dotencode)
964
964
965 return storemod.encodedstore(path, vfstype)
965 return storemod.encodedstore(path, vfstype)
966
966
967 return storemod.basicstore(path, vfstype)
967 return storemod.basicstore(path, vfstype)
968
968
969
969
970 def resolvestorevfsoptions(ui, requirements, features):
970 def resolvestorevfsoptions(ui, requirements, features):
971 """Resolve the options to pass to the store vfs opener.
971 """Resolve the options to pass to the store vfs opener.
972
972
973 The returned dict is used to influence behavior of the storage layer.
973 The returned dict is used to influence behavior of the storage layer.
974 """
974 """
975 options = {}
975 options = {}
976
976
977 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
977 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
978 options[b'treemanifest'] = True
978 options[b'treemanifest'] = True
979
979
980 # experimental config: format.manifestcachesize
980 # experimental config: format.manifestcachesize
981 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
981 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
982 if manifestcachesize is not None:
982 if manifestcachesize is not None:
983 options[b'manifestcachesize'] = manifestcachesize
983 options[b'manifestcachesize'] = manifestcachesize
984
984
985 # In the absence of another requirement superseding a revlog-related
985 # In the absence of another requirement superseding a revlog-related
986 # requirement, we have to assume the repo is using revlog version 0.
986 # requirement, we have to assume the repo is using revlog version 0.
987 # This revlog format is super old and we don't bother trying to parse
987 # This revlog format is super old and we don't bother trying to parse
988 # opener options for it because those options wouldn't do anything
988 # opener options for it because those options wouldn't do anything
989 # meaningful on such old repos.
989 # meaningful on such old repos.
990 if (
990 if (
991 requirementsmod.REVLOGV1_REQUIREMENT in requirements
991 requirementsmod.REVLOGV1_REQUIREMENT in requirements
992 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
992 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
993 ):
993 ):
994 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
994 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
995 else: # explicitly mark repo as using revlogv0
995 else: # explicitly mark repo as using revlogv0
996 options[b'revlogv0'] = True
996 options[b'revlogv0'] = True
997
997
998 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
998 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
999 options[b'copies-storage'] = b'changeset-sidedata'
999 options[b'copies-storage'] = b'changeset-sidedata'
1000 else:
1000 else:
1001 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1001 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1002 copiesextramode = (b'changeset-only', b'compatibility')
1002 copiesextramode = (b'changeset-only', b'compatibility')
1003 if writecopiesto in copiesextramode:
1003 if writecopiesto in copiesextramode:
1004 options[b'copies-storage'] = b'extra'
1004 options[b'copies-storage'] = b'extra'
1005
1005
1006 return options
1006 return options
1007
1007
1008
1008
1009 def resolverevlogstorevfsoptions(ui, requirements, features):
1009 def resolverevlogstorevfsoptions(ui, requirements, features):
1010 """Resolve opener options specific to revlogs."""
1010 """Resolve opener options specific to revlogs."""
1011
1011
1012 options = {}
1012 options = {}
1013 options[b'flagprocessors'] = {}
1013 options[b'flagprocessors'] = {}
1014
1014
1015 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1015 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1016 options[b'revlogv1'] = True
1016 options[b'revlogv1'] = True
1017 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1017 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1018 options[b'revlogv2'] = True
1018 options[b'revlogv2'] = True
1019
1019
1020 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1020 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1021 options[b'generaldelta'] = True
1021 options[b'generaldelta'] = True
1022
1022
1023 # experimental config: format.chunkcachesize
1023 # experimental config: format.chunkcachesize
1024 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1024 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1025 if chunkcachesize is not None:
1025 if chunkcachesize is not None:
1026 options[b'chunkcachesize'] = chunkcachesize
1026 options[b'chunkcachesize'] = chunkcachesize
1027
1027
1028 deltabothparents = ui.configbool(
1028 deltabothparents = ui.configbool(
1029 b'storage', b'revlog.optimize-delta-parent-choice'
1029 b'storage', b'revlog.optimize-delta-parent-choice'
1030 )
1030 )
1031 options[b'deltabothparents'] = deltabothparents
1031 options[b'deltabothparents'] = deltabothparents
1032
1032
1033 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1033 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1034 lazydeltabase = False
1034 lazydeltabase = False
1035 if lazydelta:
1035 if lazydelta:
1036 lazydeltabase = ui.configbool(
1036 lazydeltabase = ui.configbool(
1037 b'storage', b'revlog.reuse-external-delta-parent'
1037 b'storage', b'revlog.reuse-external-delta-parent'
1038 )
1038 )
1039 if lazydeltabase is None:
1039 if lazydeltabase is None:
1040 lazydeltabase = not scmutil.gddeltaconfig(ui)
1040 lazydeltabase = not scmutil.gddeltaconfig(ui)
1041 options[b'lazydelta'] = lazydelta
1041 options[b'lazydelta'] = lazydelta
1042 options[b'lazydeltabase'] = lazydeltabase
1042 options[b'lazydeltabase'] = lazydeltabase
1043
1043
1044 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1044 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1045 if 0 <= chainspan:
1045 if 0 <= chainspan:
1046 options[b'maxdeltachainspan'] = chainspan
1046 options[b'maxdeltachainspan'] = chainspan
1047
1047
1048 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1048 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1049 if mmapindexthreshold is not None:
1049 if mmapindexthreshold is not None:
1050 options[b'mmapindexthreshold'] = mmapindexthreshold
1050 options[b'mmapindexthreshold'] = mmapindexthreshold
1051
1051
1052 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1052 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1053 srdensitythres = float(
1053 srdensitythres = float(
1054 ui.config(b'experimental', b'sparse-read.density-threshold')
1054 ui.config(b'experimental', b'sparse-read.density-threshold')
1055 )
1055 )
1056 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1056 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1057 options[b'with-sparse-read'] = withsparseread
1057 options[b'with-sparse-read'] = withsparseread
1058 options[b'sparse-read-density-threshold'] = srdensitythres
1058 options[b'sparse-read-density-threshold'] = srdensitythres
1059 options[b'sparse-read-min-gap-size'] = srmingapsize
1059 options[b'sparse-read-min-gap-size'] = srmingapsize
1060
1060
1061 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1061 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1062 options[b'sparse-revlog'] = sparserevlog
1062 options[b'sparse-revlog'] = sparserevlog
1063 if sparserevlog:
1063 if sparserevlog:
1064 options[b'generaldelta'] = True
1064 options[b'generaldelta'] = True
1065
1065
1066 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
1066 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
1067 options[b'side-data'] = sidedata
1067 options[b'side-data'] = sidedata
1068
1068
1069 maxchainlen = None
1069 maxchainlen = None
1070 if sparserevlog:
1070 if sparserevlog:
1071 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1071 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1072 # experimental config: format.maxchainlen
1072 # experimental config: format.maxchainlen
1073 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1073 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1074 if maxchainlen is not None:
1074 if maxchainlen is not None:
1075 options[b'maxchainlen'] = maxchainlen
1075 options[b'maxchainlen'] = maxchainlen
1076
1076
1077 for r in requirements:
1077 for r in requirements:
1078 # we allow multiple compression engine requirement to co-exist because
1078 # we allow multiple compression engine requirement to co-exist because
1079 # strickly speaking, revlog seems to support mixed compression style.
1079 # strickly speaking, revlog seems to support mixed compression style.
1080 #
1080 #
1081 # The compression used for new entries will be "the last one"
1081 # The compression used for new entries will be "the last one"
1082 prefix = r.startswith
1082 prefix = r.startswith
1083 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1083 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1084 options[b'compengine'] = r.split(b'-', 2)[2]
1084 options[b'compengine'] = r.split(b'-', 2)[2]
1085
1085
1086 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1086 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1087 if options[b'zlib.level'] is not None:
1087 if options[b'zlib.level'] is not None:
1088 if not (0 <= options[b'zlib.level'] <= 9):
1088 if not (0 <= options[b'zlib.level'] <= 9):
1089 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1089 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1090 raise error.Abort(msg % options[b'zlib.level'])
1090 raise error.Abort(msg % options[b'zlib.level'])
1091 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1091 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1092 if options[b'zstd.level'] is not None:
1092 if options[b'zstd.level'] is not None:
1093 if not (0 <= options[b'zstd.level'] <= 22):
1093 if not (0 <= options[b'zstd.level'] <= 22):
1094 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1094 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1095 raise error.Abort(msg % options[b'zstd.level'])
1095 raise error.Abort(msg % options[b'zstd.level'])
1096
1096
1097 if requirementsmod.NARROW_REQUIREMENT in requirements:
1097 if requirementsmod.NARROW_REQUIREMENT in requirements:
1098 options[b'enableellipsis'] = True
1098 options[b'enableellipsis'] = True
1099
1099
1100 if ui.configbool(b'experimental', b'rust.index'):
1100 if ui.configbool(b'experimental', b'rust.index'):
1101 options[b'rust.index'] = True
1101 options[b'rust.index'] = True
1102 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1102 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1103 slow_path = ui.config(
1103 slow_path = ui.config(
1104 b'storage', b'revlog.persistent-nodemap.slow-path'
1104 b'storage', b'revlog.persistent-nodemap.slow-path'
1105 )
1105 )
1106 if slow_path not in (b'allow', b'warn', b'abort'):
1106 if slow_path not in (b'allow', b'warn', b'abort'):
1107 default = ui.config_default(
1107 default = ui.config_default(
1108 b'storage', b'revlog.persistent-nodemap.slow-path'
1108 b'storage', b'revlog.persistent-nodemap.slow-path'
1109 )
1109 )
1110 msg = _(
1110 msg = _(
1111 b'unknown value for config '
1111 b'unknown value for config '
1112 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1112 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1113 )
1113 )
1114 ui.warn(msg % slow_path)
1114 ui.warn(msg % slow_path)
1115 if not ui.quiet:
1115 if not ui.quiet:
1116 ui.warn(_(b'falling back to default value: %s\n') % default)
1116 ui.warn(_(b'falling back to default value: %s\n') % default)
1117 slow_path = default
1117 slow_path = default
1118
1118
1119 msg = _(
1119 msg = _(
1120 b"accessing `persistent-nodemap` repository without associated "
1120 b"accessing `persistent-nodemap` repository without associated "
1121 b"fast implementation."
1121 b"fast implementation."
1122 )
1122 )
1123 hint = _(
1123 hint = _(
1124 b"check `hg help config.format.use-persistent-nodemap` "
1124 b"check `hg help config.format.use-persistent-nodemap` "
1125 b"for details"
1125 b"for details"
1126 )
1126 )
1127 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1127 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1128 if slow_path == b'warn':
1128 if slow_path == b'warn':
1129 msg = b"warning: " + msg + b'\n'
1129 msg = b"warning: " + msg + b'\n'
1130 ui.warn(msg)
1130 ui.warn(msg)
1131 if not ui.quiet:
1131 if not ui.quiet:
1132 hint = b'(' + hint + b')\n'
1132 hint = b'(' + hint + b')\n'
1133 ui.warn(hint)
1133 ui.warn(hint)
1134 if slow_path == b'abort':
1134 if slow_path == b'abort':
1135 raise error.Abort(msg, hint=hint)
1135 raise error.Abort(msg, hint=hint)
1136 options[b'persistent-nodemap'] = True
1136 options[b'persistent-nodemap'] = True
1137 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1137 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1138 options[b'persistent-nodemap.mmap'] = True
1138 options[b'persistent-nodemap.mmap'] = True
1139 if ui.configbool(b'devel', b'persistent-nodemap'):
1139 if ui.configbool(b'devel', b'persistent-nodemap'):
1140 options[b'devel-force-nodemap'] = True
1140 options[b'devel-force-nodemap'] = True
1141
1141
1142 return options
1142 return options
1143
1143
1144
1144
1145 def makemain(**kwargs):
1145 def makemain(**kwargs):
1146 """Produce a type conforming to ``ilocalrepositorymain``."""
1146 """Produce a type conforming to ``ilocalrepositorymain``."""
1147 return localrepository
1147 return localrepository
1148
1148
1149
1149
1150 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1150 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1151 class revlogfilestorage(object):
1151 class revlogfilestorage(object):
1152 """File storage when using revlogs."""
1152 """File storage when using revlogs."""
1153
1153
1154 def file(self, path):
1154 def file(self, path):
1155 if path.startswith(b'/'):
1155 if path.startswith(b'/'):
1156 path = path[1:]
1156 path = path[1:]
1157
1157
1158 return filelog.filelog(self.svfs, path)
1158 return filelog.filelog(self.svfs, path)
1159
1159
1160
1160
1161 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1161 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1162 class revlognarrowfilestorage(object):
1162 class revlognarrowfilestorage(object):
1163 """File storage when using revlogs and narrow files."""
1163 """File storage when using revlogs and narrow files."""
1164
1164
1165 def file(self, path):
1165 def file(self, path):
1166 if path.startswith(b'/'):
1166 if path.startswith(b'/'):
1167 path = path[1:]
1167 path = path[1:]
1168
1168
1169 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1169 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1170
1170
1171
1171
1172 def makefilestorage(requirements, features, **kwargs):
1172 def makefilestorage(requirements, features, **kwargs):
1173 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1173 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1174 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1174 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1175 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1175 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1176
1176
1177 if requirementsmod.NARROW_REQUIREMENT in requirements:
1177 if requirementsmod.NARROW_REQUIREMENT in requirements:
1178 return revlognarrowfilestorage
1178 return revlognarrowfilestorage
1179 else:
1179 else:
1180 return revlogfilestorage
1180 return revlogfilestorage
1181
1181
1182
1182
1183 # List of repository interfaces and factory functions for them. Each
1183 # List of repository interfaces and factory functions for them. Each
1184 # will be called in order during ``makelocalrepository()`` to iteratively
1184 # will be called in order during ``makelocalrepository()`` to iteratively
1185 # derive the final type for a local repository instance. We capture the
1185 # derive the final type for a local repository instance. We capture the
1186 # function as a lambda so we don't hold a reference and the module-level
1186 # function as a lambda so we don't hold a reference and the module-level
1187 # functions can be wrapped.
1187 # functions can be wrapped.
1188 REPO_INTERFACES = [
1188 REPO_INTERFACES = [
1189 (repository.ilocalrepositorymain, lambda: makemain),
1189 (repository.ilocalrepositorymain, lambda: makemain),
1190 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1190 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1191 ]
1191 ]
1192
1192
1193
1193
1194 @interfaceutil.implementer(repository.ilocalrepositorymain)
1194 @interfaceutil.implementer(repository.ilocalrepositorymain)
1195 class localrepository(object):
1195 class localrepository(object):
1196 """Main class for representing local repositories.
1196 """Main class for representing local repositories.
1197
1197
1198 All local repositories are instances of this class.
1198 All local repositories are instances of this class.
1199
1199
1200 Constructed on its own, instances of this class are not usable as
1200 Constructed on its own, instances of this class are not usable as
1201 repository objects. To obtain a usable repository object, call
1201 repository objects. To obtain a usable repository object, call
1202 ``hg.repository()``, ``localrepo.instance()``, or
1202 ``hg.repository()``, ``localrepo.instance()``, or
1203 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1203 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1204 ``instance()`` adds support for creating new repositories.
1204 ``instance()`` adds support for creating new repositories.
1205 ``hg.repository()`` adds more extension integration, including calling
1205 ``hg.repository()`` adds more extension integration, including calling
1206 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1206 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1207 used.
1207 used.
1208 """
1208 """
1209
1209
1210 # obsolete experimental requirements:
1210 # obsolete experimental requirements:
1211 # - manifestv2: An experimental new manifest format that allowed
1211 # - manifestv2: An experimental new manifest format that allowed
1212 # for stem compression of long paths. Experiment ended up not
1212 # for stem compression of long paths. Experiment ended up not
1213 # being successful (repository sizes went up due to worse delta
1213 # being successful (repository sizes went up due to worse delta
1214 # chains), and the code was deleted in 4.6.
1214 # chains), and the code was deleted in 4.6.
1215 supportedformats = {
1215 supportedformats = {
1216 requirementsmod.REVLOGV1_REQUIREMENT,
1216 requirementsmod.REVLOGV1_REQUIREMENT,
1217 requirementsmod.GENERALDELTA_REQUIREMENT,
1217 requirementsmod.GENERALDELTA_REQUIREMENT,
1218 requirementsmod.TREEMANIFEST_REQUIREMENT,
1218 requirementsmod.TREEMANIFEST_REQUIREMENT,
1219 requirementsmod.COPIESSDC_REQUIREMENT,
1219 requirementsmod.COPIESSDC_REQUIREMENT,
1220 requirementsmod.REVLOGV2_REQUIREMENT,
1220 requirementsmod.REVLOGV2_REQUIREMENT,
1221 requirementsmod.SIDEDATA_REQUIREMENT,
1221 requirementsmod.SIDEDATA_REQUIREMENT,
1222 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1222 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1223 requirementsmod.NODEMAP_REQUIREMENT,
1223 requirementsmod.NODEMAP_REQUIREMENT,
1224 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1224 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1225 requirementsmod.SHARESAFE_REQUIREMENT,
1225 requirementsmod.SHARESAFE_REQUIREMENT,
1226 }
1226 }
1227 _basesupported = supportedformats | {
1227 _basesupported = supportedformats | {
1228 requirementsmod.STORE_REQUIREMENT,
1228 requirementsmod.STORE_REQUIREMENT,
1229 requirementsmod.FNCACHE_REQUIREMENT,
1229 requirementsmod.FNCACHE_REQUIREMENT,
1230 requirementsmod.SHARED_REQUIREMENT,
1230 requirementsmod.SHARED_REQUIREMENT,
1231 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1231 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1232 requirementsmod.DOTENCODE_REQUIREMENT,
1232 requirementsmod.DOTENCODE_REQUIREMENT,
1233 requirementsmod.SPARSE_REQUIREMENT,
1233 requirementsmod.SPARSE_REQUIREMENT,
1234 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1234 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1235 }
1235 }
1236
1236
1237 # list of prefix for file which can be written without 'wlock'
1237 # list of prefix for file which can be written without 'wlock'
1238 # Extensions should extend this list when needed
1238 # Extensions should extend this list when needed
1239 _wlockfreeprefix = {
1239 _wlockfreeprefix = {
1240 # We migh consider requiring 'wlock' for the next
1240 # We migh consider requiring 'wlock' for the next
1241 # two, but pretty much all the existing code assume
1241 # two, but pretty much all the existing code assume
1242 # wlock is not needed so we keep them excluded for
1242 # wlock is not needed so we keep them excluded for
1243 # now.
1243 # now.
1244 b'hgrc',
1244 b'hgrc',
1245 b'requires',
1245 b'requires',
1246 # XXX cache is a complicatged business someone
1246 # XXX cache is a complicatged business someone
1247 # should investigate this in depth at some point
1247 # should investigate this in depth at some point
1248 b'cache/',
1248 b'cache/',
1249 # XXX shouldn't be dirstate covered by the wlock?
1249 # XXX shouldn't be dirstate covered by the wlock?
1250 b'dirstate',
1250 b'dirstate',
1251 # XXX bisect was still a bit too messy at the time
1251 # XXX bisect was still a bit too messy at the time
1252 # this changeset was introduced. Someone should fix
1252 # this changeset was introduced. Someone should fix
1253 # the remainig bit and drop this line
1253 # the remainig bit and drop this line
1254 b'bisect.state',
1254 b'bisect.state',
1255 }
1255 }
1256
1256
1257 def __init__(
1257 def __init__(
1258 self,
1258 self,
1259 baseui,
1259 baseui,
1260 ui,
1260 ui,
1261 origroot,
1261 origroot,
1262 wdirvfs,
1262 wdirvfs,
1263 hgvfs,
1263 hgvfs,
1264 requirements,
1264 requirements,
1265 supportedrequirements,
1265 supportedrequirements,
1266 sharedpath,
1266 sharedpath,
1267 store,
1267 store,
1268 cachevfs,
1268 cachevfs,
1269 wcachevfs,
1269 wcachevfs,
1270 features,
1270 features,
1271 intents=None,
1271 intents=None,
1272 ):
1272 ):
1273 """Create a new local repository instance.
1273 """Create a new local repository instance.
1274
1274
1275 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1275 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1276 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1276 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1277 object.
1277 object.
1278
1278
1279 Arguments:
1279 Arguments:
1280
1280
1281 baseui
1281 baseui
1282 ``ui.ui`` instance that ``ui`` argument was based off of.
1282 ``ui.ui`` instance that ``ui`` argument was based off of.
1283
1283
1284 ui
1284 ui
1285 ``ui.ui`` instance for use by the repository.
1285 ``ui.ui`` instance for use by the repository.
1286
1286
1287 origroot
1287 origroot
1288 ``bytes`` path to working directory root of this repository.
1288 ``bytes`` path to working directory root of this repository.
1289
1289
1290 wdirvfs
1290 wdirvfs
1291 ``vfs.vfs`` rooted at the working directory.
1291 ``vfs.vfs`` rooted at the working directory.
1292
1292
1293 hgvfs
1293 hgvfs
1294 ``vfs.vfs`` rooted at .hg/
1294 ``vfs.vfs`` rooted at .hg/
1295
1295
1296 requirements
1296 requirements
1297 ``set`` of bytestrings representing repository opening requirements.
1297 ``set`` of bytestrings representing repository opening requirements.
1298
1298
1299 supportedrequirements
1299 supportedrequirements
1300 ``set`` of bytestrings representing repository requirements that we
1300 ``set`` of bytestrings representing repository requirements that we
1301 know how to open. May be a supetset of ``requirements``.
1301 know how to open. May be a supetset of ``requirements``.
1302
1302
1303 sharedpath
1303 sharedpath
1304 ``bytes`` Defining path to storage base directory. Points to a
1304 ``bytes`` Defining path to storage base directory. Points to a
1305 ``.hg/`` directory somewhere.
1305 ``.hg/`` directory somewhere.
1306
1306
1307 store
1307 store
1308 ``store.basicstore`` (or derived) instance providing access to
1308 ``store.basicstore`` (or derived) instance providing access to
1309 versioned storage.
1309 versioned storage.
1310
1310
1311 cachevfs
1311 cachevfs
1312 ``vfs.vfs`` used for cache files.
1312 ``vfs.vfs`` used for cache files.
1313
1313
1314 wcachevfs
1314 wcachevfs
1315 ``vfs.vfs`` used for cache files related to the working copy.
1315 ``vfs.vfs`` used for cache files related to the working copy.
1316
1316
1317 features
1317 features
1318 ``set`` of bytestrings defining features/capabilities of this
1318 ``set`` of bytestrings defining features/capabilities of this
1319 instance.
1319 instance.
1320
1320
1321 intents
1321 intents
1322 ``set`` of system strings indicating what this repo will be used
1322 ``set`` of system strings indicating what this repo will be used
1323 for.
1323 for.
1324 """
1324 """
1325 self.baseui = baseui
1325 self.baseui = baseui
1326 self.ui = ui
1326 self.ui = ui
1327 self.origroot = origroot
1327 self.origroot = origroot
1328 # vfs rooted at working directory.
1328 # vfs rooted at working directory.
1329 self.wvfs = wdirvfs
1329 self.wvfs = wdirvfs
1330 self.root = wdirvfs.base
1330 self.root = wdirvfs.base
1331 # vfs rooted at .hg/. Used to access most non-store paths.
1331 # vfs rooted at .hg/. Used to access most non-store paths.
1332 self.vfs = hgvfs
1332 self.vfs = hgvfs
1333 self.path = hgvfs.base
1333 self.path = hgvfs.base
1334 self.requirements = requirements
1334 self.requirements = requirements
1335 self.nodeconstants = sha1nodeconstants
1335 self.nodeconstants = sha1nodeconstants
1336 self.nullid = self.nodeconstants.nullid
1336 self.nullid = self.nodeconstants.nullid
1337 self.supported = supportedrequirements
1337 self.supported = supportedrequirements
1338 self.sharedpath = sharedpath
1338 self.sharedpath = sharedpath
1339 self.store = store
1339 self.store = store
1340 self.cachevfs = cachevfs
1340 self.cachevfs = cachevfs
1341 self.wcachevfs = wcachevfs
1341 self.wcachevfs = wcachevfs
1342 self.features = features
1342 self.features = features
1343
1343
1344 self.filtername = None
1344 self.filtername = None
1345
1345
1346 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1346 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1347 b'devel', b'check-locks'
1347 b'devel', b'check-locks'
1348 ):
1348 ):
1349 self.vfs.audit = self._getvfsward(self.vfs.audit)
1349 self.vfs.audit = self._getvfsward(self.vfs.audit)
1350 # A list of callback to shape the phase if no data were found.
1350 # A list of callback to shape the phase if no data were found.
1351 # Callback are in the form: func(repo, roots) --> processed root.
1351 # Callback are in the form: func(repo, roots) --> processed root.
1352 # This list it to be filled by extension during repo setup
1352 # This list it to be filled by extension during repo setup
1353 self._phasedefaults = []
1353 self._phasedefaults = []
1354
1354
1355 color.setup(self.ui)
1355 color.setup(self.ui)
1356
1356
1357 self.spath = self.store.path
1357 self.spath = self.store.path
1358 self.svfs = self.store.vfs
1358 self.svfs = self.store.vfs
1359 self.sjoin = self.store.join
1359 self.sjoin = self.store.join
1360 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1360 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1361 b'devel', b'check-locks'
1361 b'devel', b'check-locks'
1362 ):
1362 ):
1363 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1363 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1364 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1364 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1365 else: # standard vfs
1365 else: # standard vfs
1366 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1366 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1367
1367
1368 self._dirstatevalidatewarned = False
1368 self._dirstatevalidatewarned = False
1369
1369
1370 self._branchcaches = branchmap.BranchMapCache()
1370 self._branchcaches = branchmap.BranchMapCache()
1371 self._revbranchcache = None
1371 self._revbranchcache = None
1372 self._filterpats = {}
1372 self._filterpats = {}
1373 self._datafilters = {}
1373 self._datafilters = {}
1374 self._transref = self._lockref = self._wlockref = None
1374 self._transref = self._lockref = self._wlockref = None
1375
1375
1376 # A cache for various files under .hg/ that tracks file changes,
1376 # A cache for various files under .hg/ that tracks file changes,
1377 # (used by the filecache decorator)
1377 # (used by the filecache decorator)
1378 #
1378 #
1379 # Maps a property name to its util.filecacheentry
1379 # Maps a property name to its util.filecacheentry
1380 self._filecache = {}
1380 self._filecache = {}
1381
1381
1382 # hold sets of revision to be filtered
1382 # hold sets of revision to be filtered
1383 # should be cleared when something might have changed the filter value:
1383 # should be cleared when something might have changed the filter value:
1384 # - new changesets,
1384 # - new changesets,
1385 # - phase change,
1385 # - phase change,
1386 # - new obsolescence marker,
1386 # - new obsolescence marker,
1387 # - working directory parent change,
1387 # - working directory parent change,
1388 # - bookmark changes
1388 # - bookmark changes
1389 self.filteredrevcache = {}
1389 self.filteredrevcache = {}
1390
1390
1391 # post-dirstate-status hooks
1391 # post-dirstate-status hooks
1392 self._postdsstatus = []
1392 self._postdsstatus = []
1393
1393
1394 # generic mapping between names and nodes
1394 # generic mapping between names and nodes
1395 self.names = namespaces.namespaces()
1395 self.names = namespaces.namespaces()
1396
1396
1397 # Key to signature value.
1397 # Key to signature value.
1398 self._sparsesignaturecache = {}
1398 self._sparsesignaturecache = {}
1399 # Signature to cached matcher instance.
1399 # Signature to cached matcher instance.
1400 self._sparsematchercache = {}
1400 self._sparsematchercache = {}
1401
1401
1402 self._extrafilterid = repoview.extrafilter(ui)
1402 self._extrafilterid = repoview.extrafilter(ui)
1403
1403
1404 self.filecopiesmode = None
1404 self.filecopiesmode = None
1405 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1405 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1406 self.filecopiesmode = b'changeset-sidedata'
1406 self.filecopiesmode = b'changeset-sidedata'
1407
1407
1408 self._wanted_sidedata = set()
1408 self._wanted_sidedata = set()
1409 self._sidedata_computers = {}
1409 self._sidedata_computers = {}
1410 metadatamod.set_sidedata_spec_for_repo(self)
1410 sidedatamod.set_sidedata_spec_for_repo(self)
1411
1411
1412 def _getvfsward(self, origfunc):
1412 def _getvfsward(self, origfunc):
1413 """build a ward for self.vfs"""
1413 """build a ward for self.vfs"""
1414 rref = weakref.ref(self)
1414 rref = weakref.ref(self)
1415
1415
1416 def checkvfs(path, mode=None):
1416 def checkvfs(path, mode=None):
1417 ret = origfunc(path, mode=mode)
1417 ret = origfunc(path, mode=mode)
1418 repo = rref()
1418 repo = rref()
1419 if (
1419 if (
1420 repo is None
1420 repo is None
1421 or not util.safehasattr(repo, b'_wlockref')
1421 or not util.safehasattr(repo, b'_wlockref')
1422 or not util.safehasattr(repo, b'_lockref')
1422 or not util.safehasattr(repo, b'_lockref')
1423 ):
1423 ):
1424 return
1424 return
1425 if mode in (None, b'r', b'rb'):
1425 if mode in (None, b'r', b'rb'):
1426 return
1426 return
1427 if path.startswith(repo.path):
1427 if path.startswith(repo.path):
1428 # truncate name relative to the repository (.hg)
1428 # truncate name relative to the repository (.hg)
1429 path = path[len(repo.path) + 1 :]
1429 path = path[len(repo.path) + 1 :]
1430 if path.startswith(b'cache/'):
1430 if path.startswith(b'cache/'):
1431 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1431 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1432 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1432 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1433 # path prefixes covered by 'lock'
1433 # path prefixes covered by 'lock'
1434 vfs_path_prefixes = (
1434 vfs_path_prefixes = (
1435 b'journal.',
1435 b'journal.',
1436 b'undo.',
1436 b'undo.',
1437 b'strip-backup/',
1437 b'strip-backup/',
1438 b'cache/',
1438 b'cache/',
1439 )
1439 )
1440 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1440 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1441 if repo._currentlock(repo._lockref) is None:
1441 if repo._currentlock(repo._lockref) is None:
1442 repo.ui.develwarn(
1442 repo.ui.develwarn(
1443 b'write with no lock: "%s"' % path,
1443 b'write with no lock: "%s"' % path,
1444 stacklevel=3,
1444 stacklevel=3,
1445 config=b'check-locks',
1445 config=b'check-locks',
1446 )
1446 )
1447 elif repo._currentlock(repo._wlockref) is None:
1447 elif repo._currentlock(repo._wlockref) is None:
1448 # rest of vfs files are covered by 'wlock'
1448 # rest of vfs files are covered by 'wlock'
1449 #
1449 #
1450 # exclude special files
1450 # exclude special files
1451 for prefix in self._wlockfreeprefix:
1451 for prefix in self._wlockfreeprefix:
1452 if path.startswith(prefix):
1452 if path.startswith(prefix):
1453 return
1453 return
1454 repo.ui.develwarn(
1454 repo.ui.develwarn(
1455 b'write with no wlock: "%s"' % path,
1455 b'write with no wlock: "%s"' % path,
1456 stacklevel=3,
1456 stacklevel=3,
1457 config=b'check-locks',
1457 config=b'check-locks',
1458 )
1458 )
1459 return ret
1459 return ret
1460
1460
1461 return checkvfs
1461 return checkvfs
1462
1462
1463 def _getsvfsward(self, origfunc):
1463 def _getsvfsward(self, origfunc):
1464 """build a ward for self.svfs"""
1464 """build a ward for self.svfs"""
1465 rref = weakref.ref(self)
1465 rref = weakref.ref(self)
1466
1466
1467 def checksvfs(path, mode=None):
1467 def checksvfs(path, mode=None):
1468 ret = origfunc(path, mode=mode)
1468 ret = origfunc(path, mode=mode)
1469 repo = rref()
1469 repo = rref()
1470 if repo is None or not util.safehasattr(repo, b'_lockref'):
1470 if repo is None or not util.safehasattr(repo, b'_lockref'):
1471 return
1471 return
1472 if mode in (None, b'r', b'rb'):
1472 if mode in (None, b'r', b'rb'):
1473 return
1473 return
1474 if path.startswith(repo.sharedpath):
1474 if path.startswith(repo.sharedpath):
1475 # truncate name relative to the repository (.hg)
1475 # truncate name relative to the repository (.hg)
1476 path = path[len(repo.sharedpath) + 1 :]
1476 path = path[len(repo.sharedpath) + 1 :]
1477 if repo._currentlock(repo._lockref) is None:
1477 if repo._currentlock(repo._lockref) is None:
1478 repo.ui.develwarn(
1478 repo.ui.develwarn(
1479 b'write with no lock: "%s"' % path, stacklevel=4
1479 b'write with no lock: "%s"' % path, stacklevel=4
1480 )
1480 )
1481 return ret
1481 return ret
1482
1482
1483 return checksvfs
1483 return checksvfs
1484
1484
1485 def close(self):
1485 def close(self):
1486 self._writecaches()
1486 self._writecaches()
1487
1487
1488 def _writecaches(self):
1488 def _writecaches(self):
1489 if self._revbranchcache:
1489 if self._revbranchcache:
1490 self._revbranchcache.write()
1490 self._revbranchcache.write()
1491
1491
1492 def _restrictcapabilities(self, caps):
1492 def _restrictcapabilities(self, caps):
1493 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1493 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1494 caps = set(caps)
1494 caps = set(caps)
1495 capsblob = bundle2.encodecaps(
1495 capsblob = bundle2.encodecaps(
1496 bundle2.getrepocaps(self, role=b'client')
1496 bundle2.getrepocaps(self, role=b'client')
1497 )
1497 )
1498 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1498 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1499 if self.ui.configbool(b'experimental', b'narrow'):
1499 if self.ui.configbool(b'experimental', b'narrow'):
1500 caps.add(wireprototypes.NARROWCAP)
1500 caps.add(wireprototypes.NARROWCAP)
1501 return caps
1501 return caps
1502
1502
1503 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1503 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1504 # self -> auditor -> self._checknested -> self
1504 # self -> auditor -> self._checknested -> self
1505
1505
1506 @property
1506 @property
1507 def auditor(self):
1507 def auditor(self):
1508 # This is only used by context.workingctx.match in order to
1508 # This is only used by context.workingctx.match in order to
1509 # detect files in subrepos.
1509 # detect files in subrepos.
1510 return pathutil.pathauditor(self.root, callback=self._checknested)
1510 return pathutil.pathauditor(self.root, callback=self._checknested)
1511
1511
1512 @property
1512 @property
1513 def nofsauditor(self):
1513 def nofsauditor(self):
1514 # This is only used by context.basectx.match in order to detect
1514 # This is only used by context.basectx.match in order to detect
1515 # files in subrepos.
1515 # files in subrepos.
1516 return pathutil.pathauditor(
1516 return pathutil.pathauditor(
1517 self.root, callback=self._checknested, realfs=False, cached=True
1517 self.root, callback=self._checknested, realfs=False, cached=True
1518 )
1518 )
1519
1519
1520 def _checknested(self, path):
1520 def _checknested(self, path):
1521 """Determine if path is a legal nested repository."""
1521 """Determine if path is a legal nested repository."""
1522 if not path.startswith(self.root):
1522 if not path.startswith(self.root):
1523 return False
1523 return False
1524 subpath = path[len(self.root) + 1 :]
1524 subpath = path[len(self.root) + 1 :]
1525 normsubpath = util.pconvert(subpath)
1525 normsubpath = util.pconvert(subpath)
1526
1526
1527 # XXX: Checking against the current working copy is wrong in
1527 # XXX: Checking against the current working copy is wrong in
1528 # the sense that it can reject things like
1528 # the sense that it can reject things like
1529 #
1529 #
1530 # $ hg cat -r 10 sub/x.txt
1530 # $ hg cat -r 10 sub/x.txt
1531 #
1531 #
1532 # if sub/ is no longer a subrepository in the working copy
1532 # if sub/ is no longer a subrepository in the working copy
1533 # parent revision.
1533 # parent revision.
1534 #
1534 #
1535 # However, it can of course also allow things that would have
1535 # However, it can of course also allow things that would have
1536 # been rejected before, such as the above cat command if sub/
1536 # been rejected before, such as the above cat command if sub/
1537 # is a subrepository now, but was a normal directory before.
1537 # is a subrepository now, but was a normal directory before.
1538 # The old path auditor would have rejected by mistake since it
1538 # The old path auditor would have rejected by mistake since it
1539 # panics when it sees sub/.hg/.
1539 # panics when it sees sub/.hg/.
1540 #
1540 #
1541 # All in all, checking against the working copy seems sensible
1541 # All in all, checking against the working copy seems sensible
1542 # since we want to prevent access to nested repositories on
1542 # since we want to prevent access to nested repositories on
1543 # the filesystem *now*.
1543 # the filesystem *now*.
1544 ctx = self[None]
1544 ctx = self[None]
1545 parts = util.splitpath(subpath)
1545 parts = util.splitpath(subpath)
1546 while parts:
1546 while parts:
1547 prefix = b'/'.join(parts)
1547 prefix = b'/'.join(parts)
1548 if prefix in ctx.substate:
1548 if prefix in ctx.substate:
1549 if prefix == normsubpath:
1549 if prefix == normsubpath:
1550 return True
1550 return True
1551 else:
1551 else:
1552 sub = ctx.sub(prefix)
1552 sub = ctx.sub(prefix)
1553 return sub.checknested(subpath[len(prefix) + 1 :])
1553 return sub.checknested(subpath[len(prefix) + 1 :])
1554 else:
1554 else:
1555 parts.pop()
1555 parts.pop()
1556 return False
1556 return False
1557
1557
1558 def peer(self):
1558 def peer(self):
1559 return localpeer(self) # not cached to avoid reference cycle
1559 return localpeer(self) # not cached to avoid reference cycle
1560
1560
1561 def unfiltered(self):
1561 def unfiltered(self):
1562 """Return unfiltered version of the repository
1562 """Return unfiltered version of the repository
1563
1563
1564 Intended to be overwritten by filtered repo."""
1564 Intended to be overwritten by filtered repo."""
1565 return self
1565 return self
1566
1566
1567 def filtered(self, name, visibilityexceptions=None):
1567 def filtered(self, name, visibilityexceptions=None):
1568 """Return a filtered version of a repository
1568 """Return a filtered version of a repository
1569
1569
1570 The `name` parameter is the identifier of the requested view. This
1570 The `name` parameter is the identifier of the requested view. This
1571 will return a repoview object set "exactly" to the specified view.
1571 will return a repoview object set "exactly" to the specified view.
1572
1572
1573 This function does not apply recursive filtering to a repository. For
1573 This function does not apply recursive filtering to a repository. For
1574 example calling `repo.filtered("served")` will return a repoview using
1574 example calling `repo.filtered("served")` will return a repoview using
1575 the "served" view, regardless of the initial view used by `repo`.
1575 the "served" view, regardless of the initial view used by `repo`.
1576
1576
1577 In other word, there is always only one level of `repoview` "filtering".
1577 In other word, there is always only one level of `repoview` "filtering".
1578 """
1578 """
1579 if self._extrafilterid is not None and b'%' not in name:
1579 if self._extrafilterid is not None and b'%' not in name:
1580 name = name + b'%' + self._extrafilterid
1580 name = name + b'%' + self._extrafilterid
1581
1581
1582 cls = repoview.newtype(self.unfiltered().__class__)
1582 cls = repoview.newtype(self.unfiltered().__class__)
1583 return cls(self, name, visibilityexceptions)
1583 return cls(self, name, visibilityexceptions)
1584
1584
1585 @mixedrepostorecache(
1585 @mixedrepostorecache(
1586 (b'bookmarks', b'plain'),
1586 (b'bookmarks', b'plain'),
1587 (b'bookmarks.current', b'plain'),
1587 (b'bookmarks.current', b'plain'),
1588 (b'bookmarks', b''),
1588 (b'bookmarks', b''),
1589 (b'00changelog.i', b''),
1589 (b'00changelog.i', b''),
1590 )
1590 )
1591 def _bookmarks(self):
1591 def _bookmarks(self):
1592 # Since the multiple files involved in the transaction cannot be
1592 # Since the multiple files involved in the transaction cannot be
1593 # written atomically (with current repository format), there is a race
1593 # written atomically (with current repository format), there is a race
1594 # condition here.
1594 # condition here.
1595 #
1595 #
1596 # 1) changelog content A is read
1596 # 1) changelog content A is read
1597 # 2) outside transaction update changelog to content B
1597 # 2) outside transaction update changelog to content B
1598 # 3) outside transaction update bookmark file referring to content B
1598 # 3) outside transaction update bookmark file referring to content B
1599 # 4) bookmarks file content is read and filtered against changelog-A
1599 # 4) bookmarks file content is read and filtered against changelog-A
1600 #
1600 #
1601 # When this happens, bookmarks against nodes missing from A are dropped.
1601 # When this happens, bookmarks against nodes missing from A are dropped.
1602 #
1602 #
1603 # Having this happening during read is not great, but it become worse
1603 # Having this happening during read is not great, but it become worse
1604 # when this happen during write because the bookmarks to the "unknown"
1604 # when this happen during write because the bookmarks to the "unknown"
1605 # nodes will be dropped for good. However, writes happen within locks.
1605 # nodes will be dropped for good. However, writes happen within locks.
1606 # This locking makes it possible to have a race free consistent read.
1606 # This locking makes it possible to have a race free consistent read.
1607 # For this purpose data read from disc before locking are
1607 # For this purpose data read from disc before locking are
1608 # "invalidated" right after the locks are taken. This invalidations are
1608 # "invalidated" right after the locks are taken. This invalidations are
1609 # "light", the `filecache` mechanism keep the data in memory and will
1609 # "light", the `filecache` mechanism keep the data in memory and will
1610 # reuse them if the underlying files did not changed. Not parsing the
1610 # reuse them if the underlying files did not changed. Not parsing the
1611 # same data multiple times helps performances.
1611 # same data multiple times helps performances.
1612 #
1612 #
1613 # Unfortunately in the case describe above, the files tracked by the
1613 # Unfortunately in the case describe above, the files tracked by the
1614 # bookmarks file cache might not have changed, but the in-memory
1614 # bookmarks file cache might not have changed, but the in-memory
1615 # content is still "wrong" because we used an older changelog content
1615 # content is still "wrong" because we used an older changelog content
1616 # to process the on-disk data. So after locking, the changelog would be
1616 # to process the on-disk data. So after locking, the changelog would be
1617 # refreshed but `_bookmarks` would be preserved.
1617 # refreshed but `_bookmarks` would be preserved.
1618 # Adding `00changelog.i` to the list of tracked file is not
1618 # Adding `00changelog.i` to the list of tracked file is not
1619 # enough, because at the time we build the content for `_bookmarks` in
1619 # enough, because at the time we build the content for `_bookmarks` in
1620 # (4), the changelog file has already diverged from the content used
1620 # (4), the changelog file has already diverged from the content used
1621 # for loading `changelog` in (1)
1621 # for loading `changelog` in (1)
1622 #
1622 #
1623 # To prevent the issue, we force the changelog to be explicitly
1623 # To prevent the issue, we force the changelog to be explicitly
1624 # reloaded while computing `_bookmarks`. The data race can still happen
1624 # reloaded while computing `_bookmarks`. The data race can still happen
1625 # without the lock (with a narrower window), but it would no longer go
1625 # without the lock (with a narrower window), but it would no longer go
1626 # undetected during the lock time refresh.
1626 # undetected during the lock time refresh.
1627 #
1627 #
1628 # The new schedule is as follow
1628 # The new schedule is as follow
1629 #
1629 #
1630 # 1) filecache logic detect that `_bookmarks` needs to be computed
1630 # 1) filecache logic detect that `_bookmarks` needs to be computed
1631 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1631 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1632 # 3) We force `changelog` filecache to be tested
1632 # 3) We force `changelog` filecache to be tested
1633 # 4) cachestat for `changelog` are captured (for changelog)
1633 # 4) cachestat for `changelog` are captured (for changelog)
1634 # 5) `_bookmarks` is computed and cached
1634 # 5) `_bookmarks` is computed and cached
1635 #
1635 #
1636 # The step in (3) ensure we have a changelog at least as recent as the
1636 # The step in (3) ensure we have a changelog at least as recent as the
1637 # cache stat computed in (1). As a result at locking time:
1637 # cache stat computed in (1). As a result at locking time:
1638 # * if the changelog did not changed since (1) -> we can reuse the data
1638 # * if the changelog did not changed since (1) -> we can reuse the data
1639 # * otherwise -> the bookmarks get refreshed.
1639 # * otherwise -> the bookmarks get refreshed.
1640 self._refreshchangelog()
1640 self._refreshchangelog()
1641 return bookmarks.bmstore(self)
1641 return bookmarks.bmstore(self)
1642
1642
1643 def _refreshchangelog(self):
1643 def _refreshchangelog(self):
1644 """make sure the in memory changelog match the on-disk one"""
1644 """make sure the in memory changelog match the on-disk one"""
1645 if 'changelog' in vars(self) and self.currenttransaction() is None:
1645 if 'changelog' in vars(self) and self.currenttransaction() is None:
1646 del self.changelog
1646 del self.changelog
1647
1647
1648 @property
1648 @property
1649 def _activebookmark(self):
1649 def _activebookmark(self):
1650 return self._bookmarks.active
1650 return self._bookmarks.active
1651
1651
1652 # _phasesets depend on changelog. what we need is to call
1652 # _phasesets depend on changelog. what we need is to call
1653 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1653 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1654 # can't be easily expressed in filecache mechanism.
1654 # can't be easily expressed in filecache mechanism.
1655 @storecache(b'phaseroots', b'00changelog.i')
1655 @storecache(b'phaseroots', b'00changelog.i')
1656 def _phasecache(self):
1656 def _phasecache(self):
1657 return phases.phasecache(self, self._phasedefaults)
1657 return phases.phasecache(self, self._phasedefaults)
1658
1658
1659 @storecache(b'obsstore')
1659 @storecache(b'obsstore')
1660 def obsstore(self):
1660 def obsstore(self):
1661 return obsolete.makestore(self.ui, self)
1661 return obsolete.makestore(self.ui, self)
1662
1662
1663 @storecache(b'00changelog.i')
1663 @storecache(b'00changelog.i')
1664 def changelog(self):
1664 def changelog(self):
1665 # load dirstate before changelog to avoid race see issue6303
1665 # load dirstate before changelog to avoid race see issue6303
1666 self.dirstate.prefetch_parents()
1666 self.dirstate.prefetch_parents()
1667 return self.store.changelog(
1667 return self.store.changelog(
1668 txnutil.mayhavepending(self.root),
1668 txnutil.mayhavepending(self.root),
1669 concurrencychecker=revlogchecker.get_checker(self.ui, b'changelog'),
1669 concurrencychecker=revlogchecker.get_checker(self.ui, b'changelog'),
1670 )
1670 )
1671
1671
1672 @storecache(b'00manifest.i')
1672 @storecache(b'00manifest.i')
1673 def manifestlog(self):
1673 def manifestlog(self):
1674 return self.store.manifestlog(self, self._storenarrowmatch)
1674 return self.store.manifestlog(self, self._storenarrowmatch)
1675
1675
1676 @repofilecache(b'dirstate')
1676 @repofilecache(b'dirstate')
1677 def dirstate(self):
1677 def dirstate(self):
1678 return self._makedirstate()
1678 return self._makedirstate()
1679
1679
1680 def _makedirstate(self):
1680 def _makedirstate(self):
1681 """Extension point for wrapping the dirstate per-repo."""
1681 """Extension point for wrapping the dirstate per-repo."""
1682 sparsematchfn = lambda: sparse.matcher(self)
1682 sparsematchfn = lambda: sparse.matcher(self)
1683
1683
1684 return dirstate.dirstate(
1684 return dirstate.dirstate(
1685 self.vfs,
1685 self.vfs,
1686 self.ui,
1686 self.ui,
1687 self.root,
1687 self.root,
1688 self._dirstatevalidate,
1688 self._dirstatevalidate,
1689 sparsematchfn,
1689 sparsematchfn,
1690 self.nodeconstants,
1690 self.nodeconstants,
1691 )
1691 )
1692
1692
1693 def _dirstatevalidate(self, node):
1693 def _dirstatevalidate(self, node):
1694 try:
1694 try:
1695 self.changelog.rev(node)
1695 self.changelog.rev(node)
1696 return node
1696 return node
1697 except error.LookupError:
1697 except error.LookupError:
1698 if not self._dirstatevalidatewarned:
1698 if not self._dirstatevalidatewarned:
1699 self._dirstatevalidatewarned = True
1699 self._dirstatevalidatewarned = True
1700 self.ui.warn(
1700 self.ui.warn(
1701 _(b"warning: ignoring unknown working parent %s!\n")
1701 _(b"warning: ignoring unknown working parent %s!\n")
1702 % short(node)
1702 % short(node)
1703 )
1703 )
1704 return self.nullid
1704 return self.nullid
1705
1705
1706 @storecache(narrowspec.FILENAME)
1706 @storecache(narrowspec.FILENAME)
1707 def narrowpats(self):
1707 def narrowpats(self):
1708 """matcher patterns for this repository's narrowspec
1708 """matcher patterns for this repository's narrowspec
1709
1709
1710 A tuple of (includes, excludes).
1710 A tuple of (includes, excludes).
1711 """
1711 """
1712 return narrowspec.load(self)
1712 return narrowspec.load(self)
1713
1713
1714 @storecache(narrowspec.FILENAME)
1714 @storecache(narrowspec.FILENAME)
1715 def _storenarrowmatch(self):
1715 def _storenarrowmatch(self):
1716 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1716 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1717 return matchmod.always()
1717 return matchmod.always()
1718 include, exclude = self.narrowpats
1718 include, exclude = self.narrowpats
1719 return narrowspec.match(self.root, include=include, exclude=exclude)
1719 return narrowspec.match(self.root, include=include, exclude=exclude)
1720
1720
1721 @storecache(narrowspec.FILENAME)
1721 @storecache(narrowspec.FILENAME)
1722 def _narrowmatch(self):
1722 def _narrowmatch(self):
1723 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1723 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1724 return matchmod.always()
1724 return matchmod.always()
1725 narrowspec.checkworkingcopynarrowspec(self)
1725 narrowspec.checkworkingcopynarrowspec(self)
1726 include, exclude = self.narrowpats
1726 include, exclude = self.narrowpats
1727 return narrowspec.match(self.root, include=include, exclude=exclude)
1727 return narrowspec.match(self.root, include=include, exclude=exclude)
1728
1728
1729 def narrowmatch(self, match=None, includeexact=False):
1729 def narrowmatch(self, match=None, includeexact=False):
1730 """matcher corresponding the the repo's narrowspec
1730 """matcher corresponding the the repo's narrowspec
1731
1731
1732 If `match` is given, then that will be intersected with the narrow
1732 If `match` is given, then that will be intersected with the narrow
1733 matcher.
1733 matcher.
1734
1734
1735 If `includeexact` is True, then any exact matches from `match` will
1735 If `includeexact` is True, then any exact matches from `match` will
1736 be included even if they're outside the narrowspec.
1736 be included even if they're outside the narrowspec.
1737 """
1737 """
1738 if match:
1738 if match:
1739 if includeexact and not self._narrowmatch.always():
1739 if includeexact and not self._narrowmatch.always():
1740 # do not exclude explicitly-specified paths so that they can
1740 # do not exclude explicitly-specified paths so that they can
1741 # be warned later on
1741 # be warned later on
1742 em = matchmod.exact(match.files())
1742 em = matchmod.exact(match.files())
1743 nm = matchmod.unionmatcher([self._narrowmatch, em])
1743 nm = matchmod.unionmatcher([self._narrowmatch, em])
1744 return matchmod.intersectmatchers(match, nm)
1744 return matchmod.intersectmatchers(match, nm)
1745 return matchmod.intersectmatchers(match, self._narrowmatch)
1745 return matchmod.intersectmatchers(match, self._narrowmatch)
1746 return self._narrowmatch
1746 return self._narrowmatch
1747
1747
1748 def setnarrowpats(self, newincludes, newexcludes):
1748 def setnarrowpats(self, newincludes, newexcludes):
1749 narrowspec.save(self, newincludes, newexcludes)
1749 narrowspec.save(self, newincludes, newexcludes)
1750 self.invalidate(clearfilecache=True)
1750 self.invalidate(clearfilecache=True)
1751
1751
1752 @unfilteredpropertycache
1752 @unfilteredpropertycache
1753 def _quick_access_changeid_null(self):
1753 def _quick_access_changeid_null(self):
1754 return {
1754 return {
1755 b'null': (nullrev, self.nodeconstants.nullid),
1755 b'null': (nullrev, self.nodeconstants.nullid),
1756 nullrev: (nullrev, self.nodeconstants.nullid),
1756 nullrev: (nullrev, self.nodeconstants.nullid),
1757 self.nullid: (nullrev, self.nullid),
1757 self.nullid: (nullrev, self.nullid),
1758 }
1758 }
1759
1759
1760 @unfilteredpropertycache
1760 @unfilteredpropertycache
1761 def _quick_access_changeid_wc(self):
1761 def _quick_access_changeid_wc(self):
1762 # also fast path access to the working copy parents
1762 # also fast path access to the working copy parents
1763 # however, only do it for filter that ensure wc is visible.
1763 # however, only do it for filter that ensure wc is visible.
1764 quick = self._quick_access_changeid_null.copy()
1764 quick = self._quick_access_changeid_null.copy()
1765 cl = self.unfiltered().changelog
1765 cl = self.unfiltered().changelog
1766 for node in self.dirstate.parents():
1766 for node in self.dirstate.parents():
1767 if node == self.nullid:
1767 if node == self.nullid:
1768 continue
1768 continue
1769 rev = cl.index.get_rev(node)
1769 rev = cl.index.get_rev(node)
1770 if rev is None:
1770 if rev is None:
1771 # unknown working copy parent case:
1771 # unknown working copy parent case:
1772 #
1772 #
1773 # skip the fast path and let higher code deal with it
1773 # skip the fast path and let higher code deal with it
1774 continue
1774 continue
1775 pair = (rev, node)
1775 pair = (rev, node)
1776 quick[rev] = pair
1776 quick[rev] = pair
1777 quick[node] = pair
1777 quick[node] = pair
1778 # also add the parents of the parents
1778 # also add the parents of the parents
1779 for r in cl.parentrevs(rev):
1779 for r in cl.parentrevs(rev):
1780 if r == nullrev:
1780 if r == nullrev:
1781 continue
1781 continue
1782 n = cl.node(r)
1782 n = cl.node(r)
1783 pair = (r, n)
1783 pair = (r, n)
1784 quick[r] = pair
1784 quick[r] = pair
1785 quick[n] = pair
1785 quick[n] = pair
1786 p1node = self.dirstate.p1()
1786 p1node = self.dirstate.p1()
1787 if p1node != self.nullid:
1787 if p1node != self.nullid:
1788 quick[b'.'] = quick[p1node]
1788 quick[b'.'] = quick[p1node]
1789 return quick
1789 return quick
1790
1790
1791 @unfilteredmethod
1791 @unfilteredmethod
1792 def _quick_access_changeid_invalidate(self):
1792 def _quick_access_changeid_invalidate(self):
1793 if '_quick_access_changeid_wc' in vars(self):
1793 if '_quick_access_changeid_wc' in vars(self):
1794 del self.__dict__['_quick_access_changeid_wc']
1794 del self.__dict__['_quick_access_changeid_wc']
1795
1795
1796 @property
1796 @property
1797 def _quick_access_changeid(self):
1797 def _quick_access_changeid(self):
1798 """an helper dictionnary for __getitem__ calls
1798 """an helper dictionnary for __getitem__ calls
1799
1799
1800 This contains a list of symbol we can recognise right away without
1800 This contains a list of symbol we can recognise right away without
1801 further processing.
1801 further processing.
1802 """
1802 """
1803 if self.filtername in repoview.filter_has_wc:
1803 if self.filtername in repoview.filter_has_wc:
1804 return self._quick_access_changeid_wc
1804 return self._quick_access_changeid_wc
1805 return self._quick_access_changeid_null
1805 return self._quick_access_changeid_null
1806
1806
1807 def __getitem__(self, changeid):
1807 def __getitem__(self, changeid):
1808 # dealing with special cases
1808 # dealing with special cases
1809 if changeid is None:
1809 if changeid is None:
1810 return context.workingctx(self)
1810 return context.workingctx(self)
1811 if isinstance(changeid, context.basectx):
1811 if isinstance(changeid, context.basectx):
1812 return changeid
1812 return changeid
1813
1813
1814 # dealing with multiple revisions
1814 # dealing with multiple revisions
1815 if isinstance(changeid, slice):
1815 if isinstance(changeid, slice):
1816 # wdirrev isn't contiguous so the slice shouldn't include it
1816 # wdirrev isn't contiguous so the slice shouldn't include it
1817 return [
1817 return [
1818 self[i]
1818 self[i]
1819 for i in pycompat.xrange(*changeid.indices(len(self)))
1819 for i in pycompat.xrange(*changeid.indices(len(self)))
1820 if i not in self.changelog.filteredrevs
1820 if i not in self.changelog.filteredrevs
1821 ]
1821 ]
1822
1822
1823 # dealing with some special values
1823 # dealing with some special values
1824 quick_access = self._quick_access_changeid.get(changeid)
1824 quick_access = self._quick_access_changeid.get(changeid)
1825 if quick_access is not None:
1825 if quick_access is not None:
1826 rev, node = quick_access
1826 rev, node = quick_access
1827 return context.changectx(self, rev, node, maybe_filtered=False)
1827 return context.changectx(self, rev, node, maybe_filtered=False)
1828 if changeid == b'tip':
1828 if changeid == b'tip':
1829 node = self.changelog.tip()
1829 node = self.changelog.tip()
1830 rev = self.changelog.rev(node)
1830 rev = self.changelog.rev(node)
1831 return context.changectx(self, rev, node)
1831 return context.changectx(self, rev, node)
1832
1832
1833 # dealing with arbitrary values
1833 # dealing with arbitrary values
1834 try:
1834 try:
1835 if isinstance(changeid, int):
1835 if isinstance(changeid, int):
1836 node = self.changelog.node(changeid)
1836 node = self.changelog.node(changeid)
1837 rev = changeid
1837 rev = changeid
1838 elif changeid == b'.':
1838 elif changeid == b'.':
1839 # this is a hack to delay/avoid loading obsmarkers
1839 # this is a hack to delay/avoid loading obsmarkers
1840 # when we know that '.' won't be hidden
1840 # when we know that '.' won't be hidden
1841 node = self.dirstate.p1()
1841 node = self.dirstate.p1()
1842 rev = self.unfiltered().changelog.rev(node)
1842 rev = self.unfiltered().changelog.rev(node)
1843 elif len(changeid) == self.nodeconstants.nodelen:
1843 elif len(changeid) == self.nodeconstants.nodelen:
1844 try:
1844 try:
1845 node = changeid
1845 node = changeid
1846 rev = self.changelog.rev(changeid)
1846 rev = self.changelog.rev(changeid)
1847 except error.FilteredLookupError:
1847 except error.FilteredLookupError:
1848 changeid = hex(changeid) # for the error message
1848 changeid = hex(changeid) # for the error message
1849 raise
1849 raise
1850 except LookupError:
1850 except LookupError:
1851 # check if it might have come from damaged dirstate
1851 # check if it might have come from damaged dirstate
1852 #
1852 #
1853 # XXX we could avoid the unfiltered if we had a recognizable
1853 # XXX we could avoid the unfiltered if we had a recognizable
1854 # exception for filtered changeset access
1854 # exception for filtered changeset access
1855 if (
1855 if (
1856 self.local()
1856 self.local()
1857 and changeid in self.unfiltered().dirstate.parents()
1857 and changeid in self.unfiltered().dirstate.parents()
1858 ):
1858 ):
1859 msg = _(b"working directory has unknown parent '%s'!")
1859 msg = _(b"working directory has unknown parent '%s'!")
1860 raise error.Abort(msg % short(changeid))
1860 raise error.Abort(msg % short(changeid))
1861 changeid = hex(changeid) # for the error message
1861 changeid = hex(changeid) # for the error message
1862 raise
1862 raise
1863
1863
1864 elif len(changeid) == 2 * self.nodeconstants.nodelen:
1864 elif len(changeid) == 2 * self.nodeconstants.nodelen:
1865 node = bin(changeid)
1865 node = bin(changeid)
1866 rev = self.changelog.rev(node)
1866 rev = self.changelog.rev(node)
1867 else:
1867 else:
1868 raise error.ProgrammingError(
1868 raise error.ProgrammingError(
1869 b"unsupported changeid '%s' of type %s"
1869 b"unsupported changeid '%s' of type %s"
1870 % (changeid, pycompat.bytestr(type(changeid)))
1870 % (changeid, pycompat.bytestr(type(changeid)))
1871 )
1871 )
1872
1872
1873 return context.changectx(self, rev, node)
1873 return context.changectx(self, rev, node)
1874
1874
1875 except (error.FilteredIndexError, error.FilteredLookupError):
1875 except (error.FilteredIndexError, error.FilteredLookupError):
1876 raise error.FilteredRepoLookupError(
1876 raise error.FilteredRepoLookupError(
1877 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1877 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1878 )
1878 )
1879 except (IndexError, LookupError):
1879 except (IndexError, LookupError):
1880 raise error.RepoLookupError(
1880 raise error.RepoLookupError(
1881 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1881 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1882 )
1882 )
1883 except error.WdirUnsupported:
1883 except error.WdirUnsupported:
1884 return context.workingctx(self)
1884 return context.workingctx(self)
1885
1885
1886 def __contains__(self, changeid):
1886 def __contains__(self, changeid):
1887 """True if the given changeid exists"""
1887 """True if the given changeid exists"""
1888 try:
1888 try:
1889 self[changeid]
1889 self[changeid]
1890 return True
1890 return True
1891 except error.RepoLookupError:
1891 except error.RepoLookupError:
1892 return False
1892 return False
1893
1893
1894 def __nonzero__(self):
1894 def __nonzero__(self):
1895 return True
1895 return True
1896
1896
1897 __bool__ = __nonzero__
1897 __bool__ = __nonzero__
1898
1898
1899 def __len__(self):
1899 def __len__(self):
1900 # no need to pay the cost of repoview.changelog
1900 # no need to pay the cost of repoview.changelog
1901 unfi = self.unfiltered()
1901 unfi = self.unfiltered()
1902 return len(unfi.changelog)
1902 return len(unfi.changelog)
1903
1903
1904 def __iter__(self):
1904 def __iter__(self):
1905 return iter(self.changelog)
1905 return iter(self.changelog)
1906
1906
1907 def revs(self, expr, *args):
1907 def revs(self, expr, *args):
1908 """Find revisions matching a revset.
1908 """Find revisions matching a revset.
1909
1909
1910 The revset is specified as a string ``expr`` that may contain
1910 The revset is specified as a string ``expr`` that may contain
1911 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1911 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1912
1912
1913 Revset aliases from the configuration are not expanded. To expand
1913 Revset aliases from the configuration are not expanded. To expand
1914 user aliases, consider calling ``scmutil.revrange()`` or
1914 user aliases, consider calling ``scmutil.revrange()`` or
1915 ``repo.anyrevs([expr], user=True)``.
1915 ``repo.anyrevs([expr], user=True)``.
1916
1916
1917 Returns a smartset.abstractsmartset, which is a list-like interface
1917 Returns a smartset.abstractsmartset, which is a list-like interface
1918 that contains integer revisions.
1918 that contains integer revisions.
1919 """
1919 """
1920 tree = revsetlang.spectree(expr, *args)
1920 tree = revsetlang.spectree(expr, *args)
1921 return revset.makematcher(tree)(self)
1921 return revset.makematcher(tree)(self)
1922
1922
1923 def set(self, expr, *args):
1923 def set(self, expr, *args):
1924 """Find revisions matching a revset and emit changectx instances.
1924 """Find revisions matching a revset and emit changectx instances.
1925
1925
1926 This is a convenience wrapper around ``revs()`` that iterates the
1926 This is a convenience wrapper around ``revs()`` that iterates the
1927 result and is a generator of changectx instances.
1927 result and is a generator of changectx instances.
1928
1928
1929 Revset aliases from the configuration are not expanded. To expand
1929 Revset aliases from the configuration are not expanded. To expand
1930 user aliases, consider calling ``scmutil.revrange()``.
1930 user aliases, consider calling ``scmutil.revrange()``.
1931 """
1931 """
1932 for r in self.revs(expr, *args):
1932 for r in self.revs(expr, *args):
1933 yield self[r]
1933 yield self[r]
1934
1934
1935 def anyrevs(self, specs, user=False, localalias=None):
1935 def anyrevs(self, specs, user=False, localalias=None):
1936 """Find revisions matching one of the given revsets.
1936 """Find revisions matching one of the given revsets.
1937
1937
1938 Revset aliases from the configuration are not expanded by default. To
1938 Revset aliases from the configuration are not expanded by default. To
1939 expand user aliases, specify ``user=True``. To provide some local
1939 expand user aliases, specify ``user=True``. To provide some local
1940 definitions overriding user aliases, set ``localalias`` to
1940 definitions overriding user aliases, set ``localalias`` to
1941 ``{name: definitionstring}``.
1941 ``{name: definitionstring}``.
1942 """
1942 """
1943 if specs == [b'null']:
1943 if specs == [b'null']:
1944 return revset.baseset([nullrev])
1944 return revset.baseset([nullrev])
1945 if specs == [b'.']:
1945 if specs == [b'.']:
1946 quick_data = self._quick_access_changeid.get(b'.')
1946 quick_data = self._quick_access_changeid.get(b'.')
1947 if quick_data is not None:
1947 if quick_data is not None:
1948 return revset.baseset([quick_data[0]])
1948 return revset.baseset([quick_data[0]])
1949 if user:
1949 if user:
1950 m = revset.matchany(
1950 m = revset.matchany(
1951 self.ui,
1951 self.ui,
1952 specs,
1952 specs,
1953 lookup=revset.lookupfn(self),
1953 lookup=revset.lookupfn(self),
1954 localalias=localalias,
1954 localalias=localalias,
1955 )
1955 )
1956 else:
1956 else:
1957 m = revset.matchany(None, specs, localalias=localalias)
1957 m = revset.matchany(None, specs, localalias=localalias)
1958 return m(self)
1958 return m(self)
1959
1959
1960 def url(self):
1960 def url(self):
1961 return b'file:' + self.root
1961 return b'file:' + self.root
1962
1962
1963 def hook(self, name, throw=False, **args):
1963 def hook(self, name, throw=False, **args):
1964 """Call a hook, passing this repo instance.
1964 """Call a hook, passing this repo instance.
1965
1965
1966 This a convenience method to aid invoking hooks. Extensions likely
1966 This a convenience method to aid invoking hooks. Extensions likely
1967 won't call this unless they have registered a custom hook or are
1967 won't call this unless they have registered a custom hook or are
1968 replacing code that is expected to call a hook.
1968 replacing code that is expected to call a hook.
1969 """
1969 """
1970 return hook.hook(self.ui, self, name, throw, **args)
1970 return hook.hook(self.ui, self, name, throw, **args)
1971
1971
1972 @filteredpropertycache
1972 @filteredpropertycache
1973 def _tagscache(self):
1973 def _tagscache(self):
1974 """Returns a tagscache object that contains various tags related
1974 """Returns a tagscache object that contains various tags related
1975 caches."""
1975 caches."""
1976
1976
1977 # This simplifies its cache management by having one decorated
1977 # This simplifies its cache management by having one decorated
1978 # function (this one) and the rest simply fetch things from it.
1978 # function (this one) and the rest simply fetch things from it.
1979 class tagscache(object):
1979 class tagscache(object):
1980 def __init__(self):
1980 def __init__(self):
1981 # These two define the set of tags for this repository. tags
1981 # These two define the set of tags for this repository. tags
1982 # maps tag name to node; tagtypes maps tag name to 'global' or
1982 # maps tag name to node; tagtypes maps tag name to 'global' or
1983 # 'local'. (Global tags are defined by .hgtags across all
1983 # 'local'. (Global tags are defined by .hgtags across all
1984 # heads, and local tags are defined in .hg/localtags.)
1984 # heads, and local tags are defined in .hg/localtags.)
1985 # They constitute the in-memory cache of tags.
1985 # They constitute the in-memory cache of tags.
1986 self.tags = self.tagtypes = None
1986 self.tags = self.tagtypes = None
1987
1987
1988 self.nodetagscache = self.tagslist = None
1988 self.nodetagscache = self.tagslist = None
1989
1989
1990 cache = tagscache()
1990 cache = tagscache()
1991 cache.tags, cache.tagtypes = self._findtags()
1991 cache.tags, cache.tagtypes = self._findtags()
1992
1992
1993 return cache
1993 return cache
1994
1994
1995 def tags(self):
1995 def tags(self):
1996 '''return a mapping of tag to node'''
1996 '''return a mapping of tag to node'''
1997 t = {}
1997 t = {}
1998 if self.changelog.filteredrevs:
1998 if self.changelog.filteredrevs:
1999 tags, tt = self._findtags()
1999 tags, tt = self._findtags()
2000 else:
2000 else:
2001 tags = self._tagscache.tags
2001 tags = self._tagscache.tags
2002 rev = self.changelog.rev
2002 rev = self.changelog.rev
2003 for k, v in pycompat.iteritems(tags):
2003 for k, v in pycompat.iteritems(tags):
2004 try:
2004 try:
2005 # ignore tags to unknown nodes
2005 # ignore tags to unknown nodes
2006 rev(v)
2006 rev(v)
2007 t[k] = v
2007 t[k] = v
2008 except (error.LookupError, ValueError):
2008 except (error.LookupError, ValueError):
2009 pass
2009 pass
2010 return t
2010 return t
2011
2011
2012 def _findtags(self):
2012 def _findtags(self):
2013 """Do the hard work of finding tags. Return a pair of dicts
2013 """Do the hard work of finding tags. Return a pair of dicts
2014 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2014 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2015 maps tag name to a string like \'global\' or \'local\'.
2015 maps tag name to a string like \'global\' or \'local\'.
2016 Subclasses or extensions are free to add their own tags, but
2016 Subclasses or extensions are free to add their own tags, but
2017 should be aware that the returned dicts will be retained for the
2017 should be aware that the returned dicts will be retained for the
2018 duration of the localrepo object."""
2018 duration of the localrepo object."""
2019
2019
2020 # XXX what tagtype should subclasses/extensions use? Currently
2020 # XXX what tagtype should subclasses/extensions use? Currently
2021 # mq and bookmarks add tags, but do not set the tagtype at all.
2021 # mq and bookmarks add tags, but do not set the tagtype at all.
2022 # Should each extension invent its own tag type? Should there
2022 # Should each extension invent its own tag type? Should there
2023 # be one tagtype for all such "virtual" tags? Or is the status
2023 # be one tagtype for all such "virtual" tags? Or is the status
2024 # quo fine?
2024 # quo fine?
2025
2025
2026 # map tag name to (node, hist)
2026 # map tag name to (node, hist)
2027 alltags = tagsmod.findglobaltags(self.ui, self)
2027 alltags = tagsmod.findglobaltags(self.ui, self)
2028 # map tag name to tag type
2028 # map tag name to tag type
2029 tagtypes = {tag: b'global' for tag in alltags}
2029 tagtypes = {tag: b'global' for tag in alltags}
2030
2030
2031 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2031 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2032
2032
2033 # Build the return dicts. Have to re-encode tag names because
2033 # Build the return dicts. Have to re-encode tag names because
2034 # the tags module always uses UTF-8 (in order not to lose info
2034 # the tags module always uses UTF-8 (in order not to lose info
2035 # writing to the cache), but the rest of Mercurial wants them in
2035 # writing to the cache), but the rest of Mercurial wants them in
2036 # local encoding.
2036 # local encoding.
2037 tags = {}
2037 tags = {}
2038 for (name, (node, hist)) in pycompat.iteritems(alltags):
2038 for (name, (node, hist)) in pycompat.iteritems(alltags):
2039 if node != self.nullid:
2039 if node != self.nullid:
2040 tags[encoding.tolocal(name)] = node
2040 tags[encoding.tolocal(name)] = node
2041 tags[b'tip'] = self.changelog.tip()
2041 tags[b'tip'] = self.changelog.tip()
2042 tagtypes = {
2042 tagtypes = {
2043 encoding.tolocal(name): value
2043 encoding.tolocal(name): value
2044 for (name, value) in pycompat.iteritems(tagtypes)
2044 for (name, value) in pycompat.iteritems(tagtypes)
2045 }
2045 }
2046 return (tags, tagtypes)
2046 return (tags, tagtypes)
2047
2047
2048 def tagtype(self, tagname):
2048 def tagtype(self, tagname):
2049 """
2049 """
2050 return the type of the given tag. result can be:
2050 return the type of the given tag. result can be:
2051
2051
2052 'local' : a local tag
2052 'local' : a local tag
2053 'global' : a global tag
2053 'global' : a global tag
2054 None : tag does not exist
2054 None : tag does not exist
2055 """
2055 """
2056
2056
2057 return self._tagscache.tagtypes.get(tagname)
2057 return self._tagscache.tagtypes.get(tagname)
2058
2058
2059 def tagslist(self):
2059 def tagslist(self):
2060 '''return a list of tags ordered by revision'''
2060 '''return a list of tags ordered by revision'''
2061 if not self._tagscache.tagslist:
2061 if not self._tagscache.tagslist:
2062 l = []
2062 l = []
2063 for t, n in pycompat.iteritems(self.tags()):
2063 for t, n in pycompat.iteritems(self.tags()):
2064 l.append((self.changelog.rev(n), t, n))
2064 l.append((self.changelog.rev(n), t, n))
2065 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2065 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2066
2066
2067 return self._tagscache.tagslist
2067 return self._tagscache.tagslist
2068
2068
2069 def nodetags(self, node):
2069 def nodetags(self, node):
2070 '''return the tags associated with a node'''
2070 '''return the tags associated with a node'''
2071 if not self._tagscache.nodetagscache:
2071 if not self._tagscache.nodetagscache:
2072 nodetagscache = {}
2072 nodetagscache = {}
2073 for t, n in pycompat.iteritems(self._tagscache.tags):
2073 for t, n in pycompat.iteritems(self._tagscache.tags):
2074 nodetagscache.setdefault(n, []).append(t)
2074 nodetagscache.setdefault(n, []).append(t)
2075 for tags in pycompat.itervalues(nodetagscache):
2075 for tags in pycompat.itervalues(nodetagscache):
2076 tags.sort()
2076 tags.sort()
2077 self._tagscache.nodetagscache = nodetagscache
2077 self._tagscache.nodetagscache = nodetagscache
2078 return self._tagscache.nodetagscache.get(node, [])
2078 return self._tagscache.nodetagscache.get(node, [])
2079
2079
2080 def nodebookmarks(self, node):
2080 def nodebookmarks(self, node):
2081 """return the list of bookmarks pointing to the specified node"""
2081 """return the list of bookmarks pointing to the specified node"""
2082 return self._bookmarks.names(node)
2082 return self._bookmarks.names(node)
2083
2083
2084 def branchmap(self):
2084 def branchmap(self):
2085 """returns a dictionary {branch: [branchheads]} with branchheads
2085 """returns a dictionary {branch: [branchheads]} with branchheads
2086 ordered by increasing revision number"""
2086 ordered by increasing revision number"""
2087 return self._branchcaches[self]
2087 return self._branchcaches[self]
2088
2088
2089 @unfilteredmethod
2089 @unfilteredmethod
2090 def revbranchcache(self):
2090 def revbranchcache(self):
2091 if not self._revbranchcache:
2091 if not self._revbranchcache:
2092 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2092 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2093 return self._revbranchcache
2093 return self._revbranchcache
2094
2094
2095 def register_changeset(self, rev, changelogrevision):
2095 def register_changeset(self, rev, changelogrevision):
2096 self.revbranchcache().setdata(rev, changelogrevision)
2096 self.revbranchcache().setdata(rev, changelogrevision)
2097
2097
2098 def branchtip(self, branch, ignoremissing=False):
2098 def branchtip(self, branch, ignoremissing=False):
2099 """return the tip node for a given branch
2099 """return the tip node for a given branch
2100
2100
2101 If ignoremissing is True, then this method will not raise an error.
2101 If ignoremissing is True, then this method will not raise an error.
2102 This is helpful for callers that only expect None for a missing branch
2102 This is helpful for callers that only expect None for a missing branch
2103 (e.g. namespace).
2103 (e.g. namespace).
2104
2104
2105 """
2105 """
2106 try:
2106 try:
2107 return self.branchmap().branchtip(branch)
2107 return self.branchmap().branchtip(branch)
2108 except KeyError:
2108 except KeyError:
2109 if not ignoremissing:
2109 if not ignoremissing:
2110 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2110 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2111 else:
2111 else:
2112 pass
2112 pass
2113
2113
2114 def lookup(self, key):
2114 def lookup(self, key):
2115 node = scmutil.revsymbol(self, key).node()
2115 node = scmutil.revsymbol(self, key).node()
2116 if node is None:
2116 if node is None:
2117 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2117 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2118 return node
2118 return node
2119
2119
2120 def lookupbranch(self, key):
2120 def lookupbranch(self, key):
2121 if self.branchmap().hasbranch(key):
2121 if self.branchmap().hasbranch(key):
2122 return key
2122 return key
2123
2123
2124 return scmutil.revsymbol(self, key).branch()
2124 return scmutil.revsymbol(self, key).branch()
2125
2125
2126 def known(self, nodes):
2126 def known(self, nodes):
2127 cl = self.changelog
2127 cl = self.changelog
2128 get_rev = cl.index.get_rev
2128 get_rev = cl.index.get_rev
2129 filtered = cl.filteredrevs
2129 filtered = cl.filteredrevs
2130 result = []
2130 result = []
2131 for n in nodes:
2131 for n in nodes:
2132 r = get_rev(n)
2132 r = get_rev(n)
2133 resp = not (r is None or r in filtered)
2133 resp = not (r is None or r in filtered)
2134 result.append(resp)
2134 result.append(resp)
2135 return result
2135 return result
2136
2136
2137 def local(self):
2137 def local(self):
2138 return self
2138 return self
2139
2139
2140 def publishing(self):
2140 def publishing(self):
2141 # it's safe (and desirable) to trust the publish flag unconditionally
2141 # it's safe (and desirable) to trust the publish flag unconditionally
2142 # so that we don't finalize changes shared between users via ssh or nfs
2142 # so that we don't finalize changes shared between users via ssh or nfs
2143 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2143 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2144
2144
2145 def cancopy(self):
2145 def cancopy(self):
2146 # so statichttprepo's override of local() works
2146 # so statichttprepo's override of local() works
2147 if not self.local():
2147 if not self.local():
2148 return False
2148 return False
2149 if not self.publishing():
2149 if not self.publishing():
2150 return True
2150 return True
2151 # if publishing we can't copy if there is filtered content
2151 # if publishing we can't copy if there is filtered content
2152 return not self.filtered(b'visible').changelog.filteredrevs
2152 return not self.filtered(b'visible').changelog.filteredrevs
2153
2153
2154 def shared(self):
2154 def shared(self):
2155 '''the type of shared repository (None if not shared)'''
2155 '''the type of shared repository (None if not shared)'''
2156 if self.sharedpath != self.path:
2156 if self.sharedpath != self.path:
2157 return b'store'
2157 return b'store'
2158 return None
2158 return None
2159
2159
2160 def wjoin(self, f, *insidef):
2160 def wjoin(self, f, *insidef):
2161 return self.vfs.reljoin(self.root, f, *insidef)
2161 return self.vfs.reljoin(self.root, f, *insidef)
2162
2162
2163 def setparents(self, p1, p2=None):
2163 def setparents(self, p1, p2=None):
2164 if p2 is None:
2164 if p2 is None:
2165 p2 = self.nullid
2165 p2 = self.nullid
2166 self[None].setparents(p1, p2)
2166 self[None].setparents(p1, p2)
2167 self._quick_access_changeid_invalidate()
2167 self._quick_access_changeid_invalidate()
2168
2168
2169 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2169 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2170 """changeid must be a changeset revision, if specified.
2170 """changeid must be a changeset revision, if specified.
2171 fileid can be a file revision or node."""
2171 fileid can be a file revision or node."""
2172 return context.filectx(
2172 return context.filectx(
2173 self, path, changeid, fileid, changectx=changectx
2173 self, path, changeid, fileid, changectx=changectx
2174 )
2174 )
2175
2175
2176 def getcwd(self):
2176 def getcwd(self):
2177 return self.dirstate.getcwd()
2177 return self.dirstate.getcwd()
2178
2178
2179 def pathto(self, f, cwd=None):
2179 def pathto(self, f, cwd=None):
2180 return self.dirstate.pathto(f, cwd)
2180 return self.dirstate.pathto(f, cwd)
2181
2181
2182 def _loadfilter(self, filter):
2182 def _loadfilter(self, filter):
2183 if filter not in self._filterpats:
2183 if filter not in self._filterpats:
2184 l = []
2184 l = []
2185 for pat, cmd in self.ui.configitems(filter):
2185 for pat, cmd in self.ui.configitems(filter):
2186 if cmd == b'!':
2186 if cmd == b'!':
2187 continue
2187 continue
2188 mf = matchmod.match(self.root, b'', [pat])
2188 mf = matchmod.match(self.root, b'', [pat])
2189 fn = None
2189 fn = None
2190 params = cmd
2190 params = cmd
2191 for name, filterfn in pycompat.iteritems(self._datafilters):
2191 for name, filterfn in pycompat.iteritems(self._datafilters):
2192 if cmd.startswith(name):
2192 if cmd.startswith(name):
2193 fn = filterfn
2193 fn = filterfn
2194 params = cmd[len(name) :].lstrip()
2194 params = cmd[len(name) :].lstrip()
2195 break
2195 break
2196 if not fn:
2196 if not fn:
2197 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2197 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2198 fn.__name__ = 'commandfilter'
2198 fn.__name__ = 'commandfilter'
2199 # Wrap old filters not supporting keyword arguments
2199 # Wrap old filters not supporting keyword arguments
2200 if not pycompat.getargspec(fn)[2]:
2200 if not pycompat.getargspec(fn)[2]:
2201 oldfn = fn
2201 oldfn = fn
2202 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2202 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2203 fn.__name__ = 'compat-' + oldfn.__name__
2203 fn.__name__ = 'compat-' + oldfn.__name__
2204 l.append((mf, fn, params))
2204 l.append((mf, fn, params))
2205 self._filterpats[filter] = l
2205 self._filterpats[filter] = l
2206 return self._filterpats[filter]
2206 return self._filterpats[filter]
2207
2207
2208 def _filter(self, filterpats, filename, data):
2208 def _filter(self, filterpats, filename, data):
2209 for mf, fn, cmd in filterpats:
2209 for mf, fn, cmd in filterpats:
2210 if mf(filename):
2210 if mf(filename):
2211 self.ui.debug(
2211 self.ui.debug(
2212 b"filtering %s through %s\n"
2212 b"filtering %s through %s\n"
2213 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2213 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2214 )
2214 )
2215 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2215 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2216 break
2216 break
2217
2217
2218 return data
2218 return data
2219
2219
2220 @unfilteredpropertycache
2220 @unfilteredpropertycache
2221 def _encodefilterpats(self):
2221 def _encodefilterpats(self):
2222 return self._loadfilter(b'encode')
2222 return self._loadfilter(b'encode')
2223
2223
2224 @unfilteredpropertycache
2224 @unfilteredpropertycache
2225 def _decodefilterpats(self):
2225 def _decodefilterpats(self):
2226 return self._loadfilter(b'decode')
2226 return self._loadfilter(b'decode')
2227
2227
2228 def adddatafilter(self, name, filter):
2228 def adddatafilter(self, name, filter):
2229 self._datafilters[name] = filter
2229 self._datafilters[name] = filter
2230
2230
2231 def wread(self, filename):
2231 def wread(self, filename):
2232 if self.wvfs.islink(filename):
2232 if self.wvfs.islink(filename):
2233 data = self.wvfs.readlink(filename)
2233 data = self.wvfs.readlink(filename)
2234 else:
2234 else:
2235 data = self.wvfs.read(filename)
2235 data = self.wvfs.read(filename)
2236 return self._filter(self._encodefilterpats, filename, data)
2236 return self._filter(self._encodefilterpats, filename, data)
2237
2237
2238 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2238 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2239 """write ``data`` into ``filename`` in the working directory
2239 """write ``data`` into ``filename`` in the working directory
2240
2240
2241 This returns length of written (maybe decoded) data.
2241 This returns length of written (maybe decoded) data.
2242 """
2242 """
2243 data = self._filter(self._decodefilterpats, filename, data)
2243 data = self._filter(self._decodefilterpats, filename, data)
2244 if b'l' in flags:
2244 if b'l' in flags:
2245 self.wvfs.symlink(data, filename)
2245 self.wvfs.symlink(data, filename)
2246 else:
2246 else:
2247 self.wvfs.write(
2247 self.wvfs.write(
2248 filename, data, backgroundclose=backgroundclose, **kwargs
2248 filename, data, backgroundclose=backgroundclose, **kwargs
2249 )
2249 )
2250 if b'x' in flags:
2250 if b'x' in flags:
2251 self.wvfs.setflags(filename, False, True)
2251 self.wvfs.setflags(filename, False, True)
2252 else:
2252 else:
2253 self.wvfs.setflags(filename, False, False)
2253 self.wvfs.setflags(filename, False, False)
2254 return len(data)
2254 return len(data)
2255
2255
2256 def wwritedata(self, filename, data):
2256 def wwritedata(self, filename, data):
2257 return self._filter(self._decodefilterpats, filename, data)
2257 return self._filter(self._decodefilterpats, filename, data)
2258
2258
2259 def currenttransaction(self):
2259 def currenttransaction(self):
2260 """return the current transaction or None if non exists"""
2260 """return the current transaction or None if non exists"""
2261 if self._transref:
2261 if self._transref:
2262 tr = self._transref()
2262 tr = self._transref()
2263 else:
2263 else:
2264 tr = None
2264 tr = None
2265
2265
2266 if tr and tr.running():
2266 if tr and tr.running():
2267 return tr
2267 return tr
2268 return None
2268 return None
2269
2269
2270 def transaction(self, desc, report=None):
2270 def transaction(self, desc, report=None):
2271 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2271 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2272 b'devel', b'check-locks'
2272 b'devel', b'check-locks'
2273 ):
2273 ):
2274 if self._currentlock(self._lockref) is None:
2274 if self._currentlock(self._lockref) is None:
2275 raise error.ProgrammingError(b'transaction requires locking')
2275 raise error.ProgrammingError(b'transaction requires locking')
2276 tr = self.currenttransaction()
2276 tr = self.currenttransaction()
2277 if tr is not None:
2277 if tr is not None:
2278 return tr.nest(name=desc)
2278 return tr.nest(name=desc)
2279
2279
2280 # abort here if the journal already exists
2280 # abort here if the journal already exists
2281 if self.svfs.exists(b"journal"):
2281 if self.svfs.exists(b"journal"):
2282 raise error.RepoError(
2282 raise error.RepoError(
2283 _(b"abandoned transaction found"),
2283 _(b"abandoned transaction found"),
2284 hint=_(b"run 'hg recover' to clean up transaction"),
2284 hint=_(b"run 'hg recover' to clean up transaction"),
2285 )
2285 )
2286
2286
2287 idbase = b"%.40f#%f" % (random.random(), time.time())
2287 idbase = b"%.40f#%f" % (random.random(), time.time())
2288 ha = hex(hashutil.sha1(idbase).digest())
2288 ha = hex(hashutil.sha1(idbase).digest())
2289 txnid = b'TXN:' + ha
2289 txnid = b'TXN:' + ha
2290 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2290 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2291
2291
2292 self._writejournal(desc)
2292 self._writejournal(desc)
2293 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2293 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2294 if report:
2294 if report:
2295 rp = report
2295 rp = report
2296 else:
2296 else:
2297 rp = self.ui.warn
2297 rp = self.ui.warn
2298 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2298 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2299 # we must avoid cyclic reference between repo and transaction.
2299 # we must avoid cyclic reference between repo and transaction.
2300 reporef = weakref.ref(self)
2300 reporef = weakref.ref(self)
2301 # Code to track tag movement
2301 # Code to track tag movement
2302 #
2302 #
2303 # Since tags are all handled as file content, it is actually quite hard
2303 # Since tags are all handled as file content, it is actually quite hard
2304 # to track these movement from a code perspective. So we fallback to a
2304 # to track these movement from a code perspective. So we fallback to a
2305 # tracking at the repository level. One could envision to track changes
2305 # tracking at the repository level. One could envision to track changes
2306 # to the '.hgtags' file through changegroup apply but that fails to
2306 # to the '.hgtags' file through changegroup apply but that fails to
2307 # cope with case where transaction expose new heads without changegroup
2307 # cope with case where transaction expose new heads without changegroup
2308 # being involved (eg: phase movement).
2308 # being involved (eg: phase movement).
2309 #
2309 #
2310 # For now, We gate the feature behind a flag since this likely comes
2310 # For now, We gate the feature behind a flag since this likely comes
2311 # with performance impacts. The current code run more often than needed
2311 # with performance impacts. The current code run more often than needed
2312 # and do not use caches as much as it could. The current focus is on
2312 # and do not use caches as much as it could. The current focus is on
2313 # the behavior of the feature so we disable it by default. The flag
2313 # the behavior of the feature so we disable it by default. The flag
2314 # will be removed when we are happy with the performance impact.
2314 # will be removed when we are happy with the performance impact.
2315 #
2315 #
2316 # Once this feature is no longer experimental move the following
2316 # Once this feature is no longer experimental move the following
2317 # documentation to the appropriate help section:
2317 # documentation to the appropriate help section:
2318 #
2318 #
2319 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2319 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2320 # tags (new or changed or deleted tags). In addition the details of
2320 # tags (new or changed or deleted tags). In addition the details of
2321 # these changes are made available in a file at:
2321 # these changes are made available in a file at:
2322 # ``REPOROOT/.hg/changes/tags.changes``.
2322 # ``REPOROOT/.hg/changes/tags.changes``.
2323 # Make sure you check for HG_TAG_MOVED before reading that file as it
2323 # Make sure you check for HG_TAG_MOVED before reading that file as it
2324 # might exist from a previous transaction even if no tag were touched
2324 # might exist from a previous transaction even if no tag were touched
2325 # in this one. Changes are recorded in a line base format::
2325 # in this one. Changes are recorded in a line base format::
2326 #
2326 #
2327 # <action> <hex-node> <tag-name>\n
2327 # <action> <hex-node> <tag-name>\n
2328 #
2328 #
2329 # Actions are defined as follow:
2329 # Actions are defined as follow:
2330 # "-R": tag is removed,
2330 # "-R": tag is removed,
2331 # "+A": tag is added,
2331 # "+A": tag is added,
2332 # "-M": tag is moved (old value),
2332 # "-M": tag is moved (old value),
2333 # "+M": tag is moved (new value),
2333 # "+M": tag is moved (new value),
2334 tracktags = lambda x: None
2334 tracktags = lambda x: None
2335 # experimental config: experimental.hook-track-tags
2335 # experimental config: experimental.hook-track-tags
2336 shouldtracktags = self.ui.configbool(
2336 shouldtracktags = self.ui.configbool(
2337 b'experimental', b'hook-track-tags'
2337 b'experimental', b'hook-track-tags'
2338 )
2338 )
2339 if desc != b'strip' and shouldtracktags:
2339 if desc != b'strip' and shouldtracktags:
2340 oldheads = self.changelog.headrevs()
2340 oldheads = self.changelog.headrevs()
2341
2341
2342 def tracktags(tr2):
2342 def tracktags(tr2):
2343 repo = reporef()
2343 repo = reporef()
2344 assert repo is not None # help pytype
2344 assert repo is not None # help pytype
2345 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2345 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2346 newheads = repo.changelog.headrevs()
2346 newheads = repo.changelog.headrevs()
2347 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2347 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2348 # notes: we compare lists here.
2348 # notes: we compare lists here.
2349 # As we do it only once buiding set would not be cheaper
2349 # As we do it only once buiding set would not be cheaper
2350 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2350 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2351 if changes:
2351 if changes:
2352 tr2.hookargs[b'tag_moved'] = b'1'
2352 tr2.hookargs[b'tag_moved'] = b'1'
2353 with repo.vfs(
2353 with repo.vfs(
2354 b'changes/tags.changes', b'w', atomictemp=True
2354 b'changes/tags.changes', b'w', atomictemp=True
2355 ) as changesfile:
2355 ) as changesfile:
2356 # note: we do not register the file to the transaction
2356 # note: we do not register the file to the transaction
2357 # because we needs it to still exist on the transaction
2357 # because we needs it to still exist on the transaction
2358 # is close (for txnclose hooks)
2358 # is close (for txnclose hooks)
2359 tagsmod.writediff(changesfile, changes)
2359 tagsmod.writediff(changesfile, changes)
2360
2360
2361 def validate(tr2):
2361 def validate(tr2):
2362 """will run pre-closing hooks"""
2362 """will run pre-closing hooks"""
2363 # XXX the transaction API is a bit lacking here so we take a hacky
2363 # XXX the transaction API is a bit lacking here so we take a hacky
2364 # path for now
2364 # path for now
2365 #
2365 #
2366 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2366 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2367 # dict is copied before these run. In addition we needs the data
2367 # dict is copied before these run. In addition we needs the data
2368 # available to in memory hooks too.
2368 # available to in memory hooks too.
2369 #
2369 #
2370 # Moreover, we also need to make sure this runs before txnclose
2370 # Moreover, we also need to make sure this runs before txnclose
2371 # hooks and there is no "pending" mechanism that would execute
2371 # hooks and there is no "pending" mechanism that would execute
2372 # logic only if hooks are about to run.
2372 # logic only if hooks are about to run.
2373 #
2373 #
2374 # Fixing this limitation of the transaction is also needed to track
2374 # Fixing this limitation of the transaction is also needed to track
2375 # other families of changes (bookmarks, phases, obsolescence).
2375 # other families of changes (bookmarks, phases, obsolescence).
2376 #
2376 #
2377 # This will have to be fixed before we remove the experimental
2377 # This will have to be fixed before we remove the experimental
2378 # gating.
2378 # gating.
2379 tracktags(tr2)
2379 tracktags(tr2)
2380 repo = reporef()
2380 repo = reporef()
2381 assert repo is not None # help pytype
2381 assert repo is not None # help pytype
2382
2382
2383 singleheadopt = (b'experimental', b'single-head-per-branch')
2383 singleheadopt = (b'experimental', b'single-head-per-branch')
2384 singlehead = repo.ui.configbool(*singleheadopt)
2384 singlehead = repo.ui.configbool(*singleheadopt)
2385 if singlehead:
2385 if singlehead:
2386 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2386 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2387 accountclosed = singleheadsub.get(
2387 accountclosed = singleheadsub.get(
2388 b"account-closed-heads", False
2388 b"account-closed-heads", False
2389 )
2389 )
2390 if singleheadsub.get(b"public-changes-only", False):
2390 if singleheadsub.get(b"public-changes-only", False):
2391 filtername = b"immutable"
2391 filtername = b"immutable"
2392 else:
2392 else:
2393 filtername = b"visible"
2393 filtername = b"visible"
2394 scmutil.enforcesinglehead(
2394 scmutil.enforcesinglehead(
2395 repo, tr2, desc, accountclosed, filtername
2395 repo, tr2, desc, accountclosed, filtername
2396 )
2396 )
2397 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2397 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2398 for name, (old, new) in sorted(
2398 for name, (old, new) in sorted(
2399 tr.changes[b'bookmarks'].items()
2399 tr.changes[b'bookmarks'].items()
2400 ):
2400 ):
2401 args = tr.hookargs.copy()
2401 args = tr.hookargs.copy()
2402 args.update(bookmarks.preparehookargs(name, old, new))
2402 args.update(bookmarks.preparehookargs(name, old, new))
2403 repo.hook(
2403 repo.hook(
2404 b'pretxnclose-bookmark',
2404 b'pretxnclose-bookmark',
2405 throw=True,
2405 throw=True,
2406 **pycompat.strkwargs(args)
2406 **pycompat.strkwargs(args)
2407 )
2407 )
2408 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2408 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2409 cl = repo.unfiltered().changelog
2409 cl = repo.unfiltered().changelog
2410 for revs, (old, new) in tr.changes[b'phases']:
2410 for revs, (old, new) in tr.changes[b'phases']:
2411 for rev in revs:
2411 for rev in revs:
2412 args = tr.hookargs.copy()
2412 args = tr.hookargs.copy()
2413 node = hex(cl.node(rev))
2413 node = hex(cl.node(rev))
2414 args.update(phases.preparehookargs(node, old, new))
2414 args.update(phases.preparehookargs(node, old, new))
2415 repo.hook(
2415 repo.hook(
2416 b'pretxnclose-phase',
2416 b'pretxnclose-phase',
2417 throw=True,
2417 throw=True,
2418 **pycompat.strkwargs(args)
2418 **pycompat.strkwargs(args)
2419 )
2419 )
2420
2420
2421 repo.hook(
2421 repo.hook(
2422 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2422 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2423 )
2423 )
2424
2424
2425 def releasefn(tr, success):
2425 def releasefn(tr, success):
2426 repo = reporef()
2426 repo = reporef()
2427 if repo is None:
2427 if repo is None:
2428 # If the repo has been GC'd (and this release function is being
2428 # If the repo has been GC'd (and this release function is being
2429 # called from transaction.__del__), there's not much we can do,
2429 # called from transaction.__del__), there's not much we can do,
2430 # so just leave the unfinished transaction there and let the
2430 # so just leave the unfinished transaction there and let the
2431 # user run `hg recover`.
2431 # user run `hg recover`.
2432 return
2432 return
2433 if success:
2433 if success:
2434 # this should be explicitly invoked here, because
2434 # this should be explicitly invoked here, because
2435 # in-memory changes aren't written out at closing
2435 # in-memory changes aren't written out at closing
2436 # transaction, if tr.addfilegenerator (via
2436 # transaction, if tr.addfilegenerator (via
2437 # dirstate.write or so) isn't invoked while
2437 # dirstate.write or so) isn't invoked while
2438 # transaction running
2438 # transaction running
2439 repo.dirstate.write(None)
2439 repo.dirstate.write(None)
2440 else:
2440 else:
2441 # discard all changes (including ones already written
2441 # discard all changes (including ones already written
2442 # out) in this transaction
2442 # out) in this transaction
2443 narrowspec.restorebackup(self, b'journal.narrowspec')
2443 narrowspec.restorebackup(self, b'journal.narrowspec')
2444 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2444 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2445 repo.dirstate.restorebackup(None, b'journal.dirstate')
2445 repo.dirstate.restorebackup(None, b'journal.dirstate')
2446
2446
2447 repo.invalidate(clearfilecache=True)
2447 repo.invalidate(clearfilecache=True)
2448
2448
2449 tr = transaction.transaction(
2449 tr = transaction.transaction(
2450 rp,
2450 rp,
2451 self.svfs,
2451 self.svfs,
2452 vfsmap,
2452 vfsmap,
2453 b"journal",
2453 b"journal",
2454 b"undo",
2454 b"undo",
2455 aftertrans(renames),
2455 aftertrans(renames),
2456 self.store.createmode,
2456 self.store.createmode,
2457 validator=validate,
2457 validator=validate,
2458 releasefn=releasefn,
2458 releasefn=releasefn,
2459 checkambigfiles=_cachedfiles,
2459 checkambigfiles=_cachedfiles,
2460 name=desc,
2460 name=desc,
2461 )
2461 )
2462 tr.changes[b'origrepolen'] = len(self)
2462 tr.changes[b'origrepolen'] = len(self)
2463 tr.changes[b'obsmarkers'] = set()
2463 tr.changes[b'obsmarkers'] = set()
2464 tr.changes[b'phases'] = []
2464 tr.changes[b'phases'] = []
2465 tr.changes[b'bookmarks'] = {}
2465 tr.changes[b'bookmarks'] = {}
2466
2466
2467 tr.hookargs[b'txnid'] = txnid
2467 tr.hookargs[b'txnid'] = txnid
2468 tr.hookargs[b'txnname'] = desc
2468 tr.hookargs[b'txnname'] = desc
2469 tr.hookargs[b'changes'] = tr.changes
2469 tr.hookargs[b'changes'] = tr.changes
2470 # note: writing the fncache only during finalize mean that the file is
2470 # note: writing the fncache only during finalize mean that the file is
2471 # outdated when running hooks. As fncache is used for streaming clone,
2471 # outdated when running hooks. As fncache is used for streaming clone,
2472 # this is not expected to break anything that happen during the hooks.
2472 # this is not expected to break anything that happen during the hooks.
2473 tr.addfinalize(b'flush-fncache', self.store.write)
2473 tr.addfinalize(b'flush-fncache', self.store.write)
2474
2474
2475 def txnclosehook(tr2):
2475 def txnclosehook(tr2):
2476 """To be run if transaction is successful, will schedule a hook run"""
2476 """To be run if transaction is successful, will schedule a hook run"""
2477 # Don't reference tr2 in hook() so we don't hold a reference.
2477 # Don't reference tr2 in hook() so we don't hold a reference.
2478 # This reduces memory consumption when there are multiple
2478 # This reduces memory consumption when there are multiple
2479 # transactions per lock. This can likely go away if issue5045
2479 # transactions per lock. This can likely go away if issue5045
2480 # fixes the function accumulation.
2480 # fixes the function accumulation.
2481 hookargs = tr2.hookargs
2481 hookargs = tr2.hookargs
2482
2482
2483 def hookfunc(unused_success):
2483 def hookfunc(unused_success):
2484 repo = reporef()
2484 repo = reporef()
2485 assert repo is not None # help pytype
2485 assert repo is not None # help pytype
2486
2486
2487 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2487 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2488 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2488 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2489 for name, (old, new) in bmchanges:
2489 for name, (old, new) in bmchanges:
2490 args = tr.hookargs.copy()
2490 args = tr.hookargs.copy()
2491 args.update(bookmarks.preparehookargs(name, old, new))
2491 args.update(bookmarks.preparehookargs(name, old, new))
2492 repo.hook(
2492 repo.hook(
2493 b'txnclose-bookmark',
2493 b'txnclose-bookmark',
2494 throw=False,
2494 throw=False,
2495 **pycompat.strkwargs(args)
2495 **pycompat.strkwargs(args)
2496 )
2496 )
2497
2497
2498 if hook.hashook(repo.ui, b'txnclose-phase'):
2498 if hook.hashook(repo.ui, b'txnclose-phase'):
2499 cl = repo.unfiltered().changelog
2499 cl = repo.unfiltered().changelog
2500 phasemv = sorted(
2500 phasemv = sorted(
2501 tr.changes[b'phases'], key=lambda r: r[0][0]
2501 tr.changes[b'phases'], key=lambda r: r[0][0]
2502 )
2502 )
2503 for revs, (old, new) in phasemv:
2503 for revs, (old, new) in phasemv:
2504 for rev in revs:
2504 for rev in revs:
2505 args = tr.hookargs.copy()
2505 args = tr.hookargs.copy()
2506 node = hex(cl.node(rev))
2506 node = hex(cl.node(rev))
2507 args.update(phases.preparehookargs(node, old, new))
2507 args.update(phases.preparehookargs(node, old, new))
2508 repo.hook(
2508 repo.hook(
2509 b'txnclose-phase',
2509 b'txnclose-phase',
2510 throw=False,
2510 throw=False,
2511 **pycompat.strkwargs(args)
2511 **pycompat.strkwargs(args)
2512 )
2512 )
2513
2513
2514 repo.hook(
2514 repo.hook(
2515 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2515 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2516 )
2516 )
2517
2517
2518 repo = reporef()
2518 repo = reporef()
2519 assert repo is not None # help pytype
2519 assert repo is not None # help pytype
2520 repo._afterlock(hookfunc)
2520 repo._afterlock(hookfunc)
2521
2521
2522 tr.addfinalize(b'txnclose-hook', txnclosehook)
2522 tr.addfinalize(b'txnclose-hook', txnclosehook)
2523 # Include a leading "-" to make it happen before the transaction summary
2523 # Include a leading "-" to make it happen before the transaction summary
2524 # reports registered via scmutil.registersummarycallback() whose names
2524 # reports registered via scmutil.registersummarycallback() whose names
2525 # are 00-txnreport etc. That way, the caches will be warm when the
2525 # are 00-txnreport etc. That way, the caches will be warm when the
2526 # callbacks run.
2526 # callbacks run.
2527 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2527 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2528
2528
2529 def txnaborthook(tr2):
2529 def txnaborthook(tr2):
2530 """To be run if transaction is aborted"""
2530 """To be run if transaction is aborted"""
2531 repo = reporef()
2531 repo = reporef()
2532 assert repo is not None # help pytype
2532 assert repo is not None # help pytype
2533 repo.hook(
2533 repo.hook(
2534 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2534 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2535 )
2535 )
2536
2536
2537 tr.addabort(b'txnabort-hook', txnaborthook)
2537 tr.addabort(b'txnabort-hook', txnaborthook)
2538 # avoid eager cache invalidation. in-memory data should be identical
2538 # avoid eager cache invalidation. in-memory data should be identical
2539 # to stored data if transaction has no error.
2539 # to stored data if transaction has no error.
2540 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2540 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2541 self._transref = weakref.ref(tr)
2541 self._transref = weakref.ref(tr)
2542 scmutil.registersummarycallback(self, tr, desc)
2542 scmutil.registersummarycallback(self, tr, desc)
2543 return tr
2543 return tr
2544
2544
2545 def _journalfiles(self):
2545 def _journalfiles(self):
2546 return (
2546 return (
2547 (self.svfs, b'journal'),
2547 (self.svfs, b'journal'),
2548 (self.svfs, b'journal.narrowspec'),
2548 (self.svfs, b'journal.narrowspec'),
2549 (self.vfs, b'journal.narrowspec.dirstate'),
2549 (self.vfs, b'journal.narrowspec.dirstate'),
2550 (self.vfs, b'journal.dirstate'),
2550 (self.vfs, b'journal.dirstate'),
2551 (self.vfs, b'journal.branch'),
2551 (self.vfs, b'journal.branch'),
2552 (self.vfs, b'journal.desc'),
2552 (self.vfs, b'journal.desc'),
2553 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2553 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2554 (self.svfs, b'journal.phaseroots'),
2554 (self.svfs, b'journal.phaseroots'),
2555 )
2555 )
2556
2556
2557 def undofiles(self):
2557 def undofiles(self):
2558 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2558 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2559
2559
2560 @unfilteredmethod
2560 @unfilteredmethod
2561 def _writejournal(self, desc):
2561 def _writejournal(self, desc):
2562 self.dirstate.savebackup(None, b'journal.dirstate')
2562 self.dirstate.savebackup(None, b'journal.dirstate')
2563 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2563 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2564 narrowspec.savebackup(self, b'journal.narrowspec')
2564 narrowspec.savebackup(self, b'journal.narrowspec')
2565 self.vfs.write(
2565 self.vfs.write(
2566 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2566 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2567 )
2567 )
2568 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2568 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2569 bookmarksvfs = bookmarks.bookmarksvfs(self)
2569 bookmarksvfs = bookmarks.bookmarksvfs(self)
2570 bookmarksvfs.write(
2570 bookmarksvfs.write(
2571 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2571 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2572 )
2572 )
2573 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2573 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2574
2574
2575 def recover(self):
2575 def recover(self):
2576 with self.lock():
2576 with self.lock():
2577 if self.svfs.exists(b"journal"):
2577 if self.svfs.exists(b"journal"):
2578 self.ui.status(_(b"rolling back interrupted transaction\n"))
2578 self.ui.status(_(b"rolling back interrupted transaction\n"))
2579 vfsmap = {
2579 vfsmap = {
2580 b'': self.svfs,
2580 b'': self.svfs,
2581 b'plain': self.vfs,
2581 b'plain': self.vfs,
2582 }
2582 }
2583 transaction.rollback(
2583 transaction.rollback(
2584 self.svfs,
2584 self.svfs,
2585 vfsmap,
2585 vfsmap,
2586 b"journal",
2586 b"journal",
2587 self.ui.warn,
2587 self.ui.warn,
2588 checkambigfiles=_cachedfiles,
2588 checkambigfiles=_cachedfiles,
2589 )
2589 )
2590 self.invalidate()
2590 self.invalidate()
2591 return True
2591 return True
2592 else:
2592 else:
2593 self.ui.warn(_(b"no interrupted transaction available\n"))
2593 self.ui.warn(_(b"no interrupted transaction available\n"))
2594 return False
2594 return False
2595
2595
2596 def rollback(self, dryrun=False, force=False):
2596 def rollback(self, dryrun=False, force=False):
2597 wlock = lock = dsguard = None
2597 wlock = lock = dsguard = None
2598 try:
2598 try:
2599 wlock = self.wlock()
2599 wlock = self.wlock()
2600 lock = self.lock()
2600 lock = self.lock()
2601 if self.svfs.exists(b"undo"):
2601 if self.svfs.exists(b"undo"):
2602 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2602 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2603
2603
2604 return self._rollback(dryrun, force, dsguard)
2604 return self._rollback(dryrun, force, dsguard)
2605 else:
2605 else:
2606 self.ui.warn(_(b"no rollback information available\n"))
2606 self.ui.warn(_(b"no rollback information available\n"))
2607 return 1
2607 return 1
2608 finally:
2608 finally:
2609 release(dsguard, lock, wlock)
2609 release(dsguard, lock, wlock)
2610
2610
2611 @unfilteredmethod # Until we get smarter cache management
2611 @unfilteredmethod # Until we get smarter cache management
2612 def _rollback(self, dryrun, force, dsguard):
2612 def _rollback(self, dryrun, force, dsguard):
2613 ui = self.ui
2613 ui = self.ui
2614 try:
2614 try:
2615 args = self.vfs.read(b'undo.desc').splitlines()
2615 args = self.vfs.read(b'undo.desc').splitlines()
2616 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2616 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2617 if len(args) >= 3:
2617 if len(args) >= 3:
2618 detail = args[2]
2618 detail = args[2]
2619 oldtip = oldlen - 1
2619 oldtip = oldlen - 1
2620
2620
2621 if detail and ui.verbose:
2621 if detail and ui.verbose:
2622 msg = _(
2622 msg = _(
2623 b'repository tip rolled back to revision %d'
2623 b'repository tip rolled back to revision %d'
2624 b' (undo %s: %s)\n'
2624 b' (undo %s: %s)\n'
2625 ) % (oldtip, desc, detail)
2625 ) % (oldtip, desc, detail)
2626 else:
2626 else:
2627 msg = _(
2627 msg = _(
2628 b'repository tip rolled back to revision %d (undo %s)\n'
2628 b'repository tip rolled back to revision %d (undo %s)\n'
2629 ) % (oldtip, desc)
2629 ) % (oldtip, desc)
2630 except IOError:
2630 except IOError:
2631 msg = _(b'rolling back unknown transaction\n')
2631 msg = _(b'rolling back unknown transaction\n')
2632 desc = None
2632 desc = None
2633
2633
2634 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2634 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2635 raise error.Abort(
2635 raise error.Abort(
2636 _(
2636 _(
2637 b'rollback of last commit while not checked out '
2637 b'rollback of last commit while not checked out '
2638 b'may lose data'
2638 b'may lose data'
2639 ),
2639 ),
2640 hint=_(b'use -f to force'),
2640 hint=_(b'use -f to force'),
2641 )
2641 )
2642
2642
2643 ui.status(msg)
2643 ui.status(msg)
2644 if dryrun:
2644 if dryrun:
2645 return 0
2645 return 0
2646
2646
2647 parents = self.dirstate.parents()
2647 parents = self.dirstate.parents()
2648 self.destroying()
2648 self.destroying()
2649 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2649 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2650 transaction.rollback(
2650 transaction.rollback(
2651 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2651 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2652 )
2652 )
2653 bookmarksvfs = bookmarks.bookmarksvfs(self)
2653 bookmarksvfs = bookmarks.bookmarksvfs(self)
2654 if bookmarksvfs.exists(b'undo.bookmarks'):
2654 if bookmarksvfs.exists(b'undo.bookmarks'):
2655 bookmarksvfs.rename(
2655 bookmarksvfs.rename(
2656 b'undo.bookmarks', b'bookmarks', checkambig=True
2656 b'undo.bookmarks', b'bookmarks', checkambig=True
2657 )
2657 )
2658 if self.svfs.exists(b'undo.phaseroots'):
2658 if self.svfs.exists(b'undo.phaseroots'):
2659 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2659 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2660 self.invalidate()
2660 self.invalidate()
2661
2661
2662 has_node = self.changelog.index.has_node
2662 has_node = self.changelog.index.has_node
2663 parentgone = any(not has_node(p) for p in parents)
2663 parentgone = any(not has_node(p) for p in parents)
2664 if parentgone:
2664 if parentgone:
2665 # prevent dirstateguard from overwriting already restored one
2665 # prevent dirstateguard from overwriting already restored one
2666 dsguard.close()
2666 dsguard.close()
2667
2667
2668 narrowspec.restorebackup(self, b'undo.narrowspec')
2668 narrowspec.restorebackup(self, b'undo.narrowspec')
2669 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2669 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2670 self.dirstate.restorebackup(None, b'undo.dirstate')
2670 self.dirstate.restorebackup(None, b'undo.dirstate')
2671 try:
2671 try:
2672 branch = self.vfs.read(b'undo.branch')
2672 branch = self.vfs.read(b'undo.branch')
2673 self.dirstate.setbranch(encoding.tolocal(branch))
2673 self.dirstate.setbranch(encoding.tolocal(branch))
2674 except IOError:
2674 except IOError:
2675 ui.warn(
2675 ui.warn(
2676 _(
2676 _(
2677 b'named branch could not be reset: '
2677 b'named branch could not be reset: '
2678 b'current branch is still \'%s\'\n'
2678 b'current branch is still \'%s\'\n'
2679 )
2679 )
2680 % self.dirstate.branch()
2680 % self.dirstate.branch()
2681 )
2681 )
2682
2682
2683 parents = tuple([p.rev() for p in self[None].parents()])
2683 parents = tuple([p.rev() for p in self[None].parents()])
2684 if len(parents) > 1:
2684 if len(parents) > 1:
2685 ui.status(
2685 ui.status(
2686 _(
2686 _(
2687 b'working directory now based on '
2687 b'working directory now based on '
2688 b'revisions %d and %d\n'
2688 b'revisions %d and %d\n'
2689 )
2689 )
2690 % parents
2690 % parents
2691 )
2691 )
2692 else:
2692 else:
2693 ui.status(
2693 ui.status(
2694 _(b'working directory now based on revision %d\n') % parents
2694 _(b'working directory now based on revision %d\n') % parents
2695 )
2695 )
2696 mergestatemod.mergestate.clean(self)
2696 mergestatemod.mergestate.clean(self)
2697
2697
2698 # TODO: if we know which new heads may result from this rollback, pass
2698 # TODO: if we know which new heads may result from this rollback, pass
2699 # them to destroy(), which will prevent the branchhead cache from being
2699 # them to destroy(), which will prevent the branchhead cache from being
2700 # invalidated.
2700 # invalidated.
2701 self.destroyed()
2701 self.destroyed()
2702 return 0
2702 return 0
2703
2703
2704 def _buildcacheupdater(self, newtransaction):
2704 def _buildcacheupdater(self, newtransaction):
2705 """called during transaction to build the callback updating cache
2705 """called during transaction to build the callback updating cache
2706
2706
2707 Lives on the repository to help extension who might want to augment
2707 Lives on the repository to help extension who might want to augment
2708 this logic. For this purpose, the created transaction is passed to the
2708 this logic. For this purpose, the created transaction is passed to the
2709 method.
2709 method.
2710 """
2710 """
2711 # we must avoid cyclic reference between repo and transaction.
2711 # we must avoid cyclic reference between repo and transaction.
2712 reporef = weakref.ref(self)
2712 reporef = weakref.ref(self)
2713
2713
2714 def updater(tr):
2714 def updater(tr):
2715 repo = reporef()
2715 repo = reporef()
2716 assert repo is not None # help pytype
2716 assert repo is not None # help pytype
2717 repo.updatecaches(tr)
2717 repo.updatecaches(tr)
2718
2718
2719 return updater
2719 return updater
2720
2720
2721 @unfilteredmethod
2721 @unfilteredmethod
2722 def updatecaches(self, tr=None, full=False):
2722 def updatecaches(self, tr=None, full=False):
2723 """warm appropriate caches
2723 """warm appropriate caches
2724
2724
2725 If this function is called after a transaction closed. The transaction
2725 If this function is called after a transaction closed. The transaction
2726 will be available in the 'tr' argument. This can be used to selectively
2726 will be available in the 'tr' argument. This can be used to selectively
2727 update caches relevant to the changes in that transaction.
2727 update caches relevant to the changes in that transaction.
2728
2728
2729 If 'full' is set, make sure all caches the function knows about have
2729 If 'full' is set, make sure all caches the function knows about have
2730 up-to-date data. Even the ones usually loaded more lazily.
2730 up-to-date data. Even the ones usually loaded more lazily.
2731 """
2731 """
2732 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2732 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2733 # During strip, many caches are invalid but
2733 # During strip, many caches are invalid but
2734 # later call to `destroyed` will refresh them.
2734 # later call to `destroyed` will refresh them.
2735 return
2735 return
2736
2736
2737 if tr is None or tr.changes[b'origrepolen'] < len(self):
2737 if tr is None or tr.changes[b'origrepolen'] < len(self):
2738 # accessing the 'served' branchmap should refresh all the others,
2738 # accessing the 'served' branchmap should refresh all the others,
2739 self.ui.debug(b'updating the branch cache\n')
2739 self.ui.debug(b'updating the branch cache\n')
2740 self.filtered(b'served').branchmap()
2740 self.filtered(b'served').branchmap()
2741 self.filtered(b'served.hidden').branchmap()
2741 self.filtered(b'served.hidden').branchmap()
2742
2742
2743 if full:
2743 if full:
2744 unfi = self.unfiltered()
2744 unfi = self.unfiltered()
2745
2745
2746 self.changelog.update_caches(transaction=tr)
2746 self.changelog.update_caches(transaction=tr)
2747 self.manifestlog.update_caches(transaction=tr)
2747 self.manifestlog.update_caches(transaction=tr)
2748
2748
2749 rbc = unfi.revbranchcache()
2749 rbc = unfi.revbranchcache()
2750 for r in unfi.changelog:
2750 for r in unfi.changelog:
2751 rbc.branchinfo(r)
2751 rbc.branchinfo(r)
2752 rbc.write()
2752 rbc.write()
2753
2753
2754 # ensure the working copy parents are in the manifestfulltextcache
2754 # ensure the working copy parents are in the manifestfulltextcache
2755 for ctx in self[b'.'].parents():
2755 for ctx in self[b'.'].parents():
2756 ctx.manifest() # accessing the manifest is enough
2756 ctx.manifest() # accessing the manifest is enough
2757
2757
2758 # accessing fnode cache warms the cache
2758 # accessing fnode cache warms the cache
2759 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2759 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2760 # accessing tags warm the cache
2760 # accessing tags warm the cache
2761 self.tags()
2761 self.tags()
2762 self.filtered(b'served').tags()
2762 self.filtered(b'served').tags()
2763
2763
2764 # The `full` arg is documented as updating even the lazily-loaded
2764 # The `full` arg is documented as updating even the lazily-loaded
2765 # caches immediately, so we're forcing a write to cause these caches
2765 # caches immediately, so we're forcing a write to cause these caches
2766 # to be warmed up even if they haven't explicitly been requested
2766 # to be warmed up even if they haven't explicitly been requested
2767 # yet (if they've never been used by hg, they won't ever have been
2767 # yet (if they've never been used by hg, they won't ever have been
2768 # written, even if they're a subset of another kind of cache that
2768 # written, even if they're a subset of another kind of cache that
2769 # *has* been used).
2769 # *has* been used).
2770 for filt in repoview.filtertable.keys():
2770 for filt in repoview.filtertable.keys():
2771 filtered = self.filtered(filt)
2771 filtered = self.filtered(filt)
2772 filtered.branchmap().write(filtered)
2772 filtered.branchmap().write(filtered)
2773
2773
2774 def invalidatecaches(self):
2774 def invalidatecaches(self):
2775
2775
2776 if '_tagscache' in vars(self):
2776 if '_tagscache' in vars(self):
2777 # can't use delattr on proxy
2777 # can't use delattr on proxy
2778 del self.__dict__['_tagscache']
2778 del self.__dict__['_tagscache']
2779
2779
2780 self._branchcaches.clear()
2780 self._branchcaches.clear()
2781 self.invalidatevolatilesets()
2781 self.invalidatevolatilesets()
2782 self._sparsesignaturecache.clear()
2782 self._sparsesignaturecache.clear()
2783
2783
2784 def invalidatevolatilesets(self):
2784 def invalidatevolatilesets(self):
2785 self.filteredrevcache.clear()
2785 self.filteredrevcache.clear()
2786 obsolete.clearobscaches(self)
2786 obsolete.clearobscaches(self)
2787 self._quick_access_changeid_invalidate()
2787 self._quick_access_changeid_invalidate()
2788
2788
2789 def invalidatedirstate(self):
2789 def invalidatedirstate(self):
2790 """Invalidates the dirstate, causing the next call to dirstate
2790 """Invalidates the dirstate, causing the next call to dirstate
2791 to check if it was modified since the last time it was read,
2791 to check if it was modified since the last time it was read,
2792 rereading it if it has.
2792 rereading it if it has.
2793
2793
2794 This is different to dirstate.invalidate() that it doesn't always
2794 This is different to dirstate.invalidate() that it doesn't always
2795 rereads the dirstate. Use dirstate.invalidate() if you want to
2795 rereads the dirstate. Use dirstate.invalidate() if you want to
2796 explicitly read the dirstate again (i.e. restoring it to a previous
2796 explicitly read the dirstate again (i.e. restoring it to a previous
2797 known good state)."""
2797 known good state)."""
2798 if hasunfilteredcache(self, 'dirstate'):
2798 if hasunfilteredcache(self, 'dirstate'):
2799 for k in self.dirstate._filecache:
2799 for k in self.dirstate._filecache:
2800 try:
2800 try:
2801 delattr(self.dirstate, k)
2801 delattr(self.dirstate, k)
2802 except AttributeError:
2802 except AttributeError:
2803 pass
2803 pass
2804 delattr(self.unfiltered(), 'dirstate')
2804 delattr(self.unfiltered(), 'dirstate')
2805
2805
2806 def invalidate(self, clearfilecache=False):
2806 def invalidate(self, clearfilecache=False):
2807 """Invalidates both store and non-store parts other than dirstate
2807 """Invalidates both store and non-store parts other than dirstate
2808
2808
2809 If a transaction is running, invalidation of store is omitted,
2809 If a transaction is running, invalidation of store is omitted,
2810 because discarding in-memory changes might cause inconsistency
2810 because discarding in-memory changes might cause inconsistency
2811 (e.g. incomplete fncache causes unintentional failure, but
2811 (e.g. incomplete fncache causes unintentional failure, but
2812 redundant one doesn't).
2812 redundant one doesn't).
2813 """
2813 """
2814 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2814 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2815 for k in list(self._filecache.keys()):
2815 for k in list(self._filecache.keys()):
2816 # dirstate is invalidated separately in invalidatedirstate()
2816 # dirstate is invalidated separately in invalidatedirstate()
2817 if k == b'dirstate':
2817 if k == b'dirstate':
2818 continue
2818 continue
2819 if (
2819 if (
2820 k == b'changelog'
2820 k == b'changelog'
2821 and self.currenttransaction()
2821 and self.currenttransaction()
2822 and self.changelog._delayed
2822 and self.changelog._delayed
2823 ):
2823 ):
2824 # The changelog object may store unwritten revisions. We don't
2824 # The changelog object may store unwritten revisions. We don't
2825 # want to lose them.
2825 # want to lose them.
2826 # TODO: Solve the problem instead of working around it.
2826 # TODO: Solve the problem instead of working around it.
2827 continue
2827 continue
2828
2828
2829 if clearfilecache:
2829 if clearfilecache:
2830 del self._filecache[k]
2830 del self._filecache[k]
2831 try:
2831 try:
2832 delattr(unfiltered, k)
2832 delattr(unfiltered, k)
2833 except AttributeError:
2833 except AttributeError:
2834 pass
2834 pass
2835 self.invalidatecaches()
2835 self.invalidatecaches()
2836 if not self.currenttransaction():
2836 if not self.currenttransaction():
2837 # TODO: Changing contents of store outside transaction
2837 # TODO: Changing contents of store outside transaction
2838 # causes inconsistency. We should make in-memory store
2838 # causes inconsistency. We should make in-memory store
2839 # changes detectable, and abort if changed.
2839 # changes detectable, and abort if changed.
2840 self.store.invalidatecaches()
2840 self.store.invalidatecaches()
2841
2841
2842 def invalidateall(self):
2842 def invalidateall(self):
2843 """Fully invalidates both store and non-store parts, causing the
2843 """Fully invalidates both store and non-store parts, causing the
2844 subsequent operation to reread any outside changes."""
2844 subsequent operation to reread any outside changes."""
2845 # extension should hook this to invalidate its caches
2845 # extension should hook this to invalidate its caches
2846 self.invalidate()
2846 self.invalidate()
2847 self.invalidatedirstate()
2847 self.invalidatedirstate()
2848
2848
2849 @unfilteredmethod
2849 @unfilteredmethod
2850 def _refreshfilecachestats(self, tr):
2850 def _refreshfilecachestats(self, tr):
2851 """Reload stats of cached files so that they are flagged as valid"""
2851 """Reload stats of cached files so that they are flagged as valid"""
2852 for k, ce in self._filecache.items():
2852 for k, ce in self._filecache.items():
2853 k = pycompat.sysstr(k)
2853 k = pycompat.sysstr(k)
2854 if k == 'dirstate' or k not in self.__dict__:
2854 if k == 'dirstate' or k not in self.__dict__:
2855 continue
2855 continue
2856 ce.refresh()
2856 ce.refresh()
2857
2857
2858 def _lock(
2858 def _lock(
2859 self,
2859 self,
2860 vfs,
2860 vfs,
2861 lockname,
2861 lockname,
2862 wait,
2862 wait,
2863 releasefn,
2863 releasefn,
2864 acquirefn,
2864 acquirefn,
2865 desc,
2865 desc,
2866 ):
2866 ):
2867 timeout = 0
2867 timeout = 0
2868 warntimeout = 0
2868 warntimeout = 0
2869 if wait:
2869 if wait:
2870 timeout = self.ui.configint(b"ui", b"timeout")
2870 timeout = self.ui.configint(b"ui", b"timeout")
2871 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2871 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2872 # internal config: ui.signal-safe-lock
2872 # internal config: ui.signal-safe-lock
2873 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2873 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2874
2874
2875 l = lockmod.trylock(
2875 l = lockmod.trylock(
2876 self.ui,
2876 self.ui,
2877 vfs,
2877 vfs,
2878 lockname,
2878 lockname,
2879 timeout,
2879 timeout,
2880 warntimeout,
2880 warntimeout,
2881 releasefn=releasefn,
2881 releasefn=releasefn,
2882 acquirefn=acquirefn,
2882 acquirefn=acquirefn,
2883 desc=desc,
2883 desc=desc,
2884 signalsafe=signalsafe,
2884 signalsafe=signalsafe,
2885 )
2885 )
2886 return l
2886 return l
2887
2887
2888 def _afterlock(self, callback):
2888 def _afterlock(self, callback):
2889 """add a callback to be run when the repository is fully unlocked
2889 """add a callback to be run when the repository is fully unlocked
2890
2890
2891 The callback will be executed when the outermost lock is released
2891 The callback will be executed when the outermost lock is released
2892 (with wlock being higher level than 'lock')."""
2892 (with wlock being higher level than 'lock')."""
2893 for ref in (self._wlockref, self._lockref):
2893 for ref in (self._wlockref, self._lockref):
2894 l = ref and ref()
2894 l = ref and ref()
2895 if l and l.held:
2895 if l and l.held:
2896 l.postrelease.append(callback)
2896 l.postrelease.append(callback)
2897 break
2897 break
2898 else: # no lock have been found.
2898 else: # no lock have been found.
2899 callback(True)
2899 callback(True)
2900
2900
2901 def lock(self, wait=True):
2901 def lock(self, wait=True):
2902 """Lock the repository store (.hg/store) and return a weak reference
2902 """Lock the repository store (.hg/store) and return a weak reference
2903 to the lock. Use this before modifying the store (e.g. committing or
2903 to the lock. Use this before modifying the store (e.g. committing or
2904 stripping). If you are opening a transaction, get a lock as well.)
2904 stripping). If you are opening a transaction, get a lock as well.)
2905
2905
2906 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2906 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2907 'wlock' first to avoid a dead-lock hazard."""
2907 'wlock' first to avoid a dead-lock hazard."""
2908 l = self._currentlock(self._lockref)
2908 l = self._currentlock(self._lockref)
2909 if l is not None:
2909 if l is not None:
2910 l.lock()
2910 l.lock()
2911 return l
2911 return l
2912
2912
2913 l = self._lock(
2913 l = self._lock(
2914 vfs=self.svfs,
2914 vfs=self.svfs,
2915 lockname=b"lock",
2915 lockname=b"lock",
2916 wait=wait,
2916 wait=wait,
2917 releasefn=None,
2917 releasefn=None,
2918 acquirefn=self.invalidate,
2918 acquirefn=self.invalidate,
2919 desc=_(b'repository %s') % self.origroot,
2919 desc=_(b'repository %s') % self.origroot,
2920 )
2920 )
2921 self._lockref = weakref.ref(l)
2921 self._lockref = weakref.ref(l)
2922 return l
2922 return l
2923
2923
2924 def wlock(self, wait=True):
2924 def wlock(self, wait=True):
2925 """Lock the non-store parts of the repository (everything under
2925 """Lock the non-store parts of the repository (everything under
2926 .hg except .hg/store) and return a weak reference to the lock.
2926 .hg except .hg/store) and return a weak reference to the lock.
2927
2927
2928 Use this before modifying files in .hg.
2928 Use this before modifying files in .hg.
2929
2929
2930 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2930 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2931 'wlock' first to avoid a dead-lock hazard."""
2931 'wlock' first to avoid a dead-lock hazard."""
2932 l = self._wlockref() if self._wlockref else None
2932 l = self._wlockref() if self._wlockref else None
2933 if l is not None and l.held:
2933 if l is not None and l.held:
2934 l.lock()
2934 l.lock()
2935 return l
2935 return l
2936
2936
2937 # We do not need to check for non-waiting lock acquisition. Such
2937 # We do not need to check for non-waiting lock acquisition. Such
2938 # acquisition would not cause dead-lock as they would just fail.
2938 # acquisition would not cause dead-lock as they would just fail.
2939 if wait and (
2939 if wait and (
2940 self.ui.configbool(b'devel', b'all-warnings')
2940 self.ui.configbool(b'devel', b'all-warnings')
2941 or self.ui.configbool(b'devel', b'check-locks')
2941 or self.ui.configbool(b'devel', b'check-locks')
2942 ):
2942 ):
2943 if self._currentlock(self._lockref) is not None:
2943 if self._currentlock(self._lockref) is not None:
2944 self.ui.develwarn(b'"wlock" acquired after "lock"')
2944 self.ui.develwarn(b'"wlock" acquired after "lock"')
2945
2945
2946 def unlock():
2946 def unlock():
2947 if self.dirstate.pendingparentchange():
2947 if self.dirstate.pendingparentchange():
2948 self.dirstate.invalidate()
2948 self.dirstate.invalidate()
2949 else:
2949 else:
2950 self.dirstate.write(None)
2950 self.dirstate.write(None)
2951
2951
2952 self._filecache[b'dirstate'].refresh()
2952 self._filecache[b'dirstate'].refresh()
2953
2953
2954 l = self._lock(
2954 l = self._lock(
2955 self.vfs,
2955 self.vfs,
2956 b"wlock",
2956 b"wlock",
2957 wait,
2957 wait,
2958 unlock,
2958 unlock,
2959 self.invalidatedirstate,
2959 self.invalidatedirstate,
2960 _(b'working directory of %s') % self.origroot,
2960 _(b'working directory of %s') % self.origroot,
2961 )
2961 )
2962 self._wlockref = weakref.ref(l)
2962 self._wlockref = weakref.ref(l)
2963 return l
2963 return l
2964
2964
2965 def _currentlock(self, lockref):
2965 def _currentlock(self, lockref):
2966 """Returns the lock if it's held, or None if it's not."""
2966 """Returns the lock if it's held, or None if it's not."""
2967 if lockref is None:
2967 if lockref is None:
2968 return None
2968 return None
2969 l = lockref()
2969 l = lockref()
2970 if l is None or not l.held:
2970 if l is None or not l.held:
2971 return None
2971 return None
2972 return l
2972 return l
2973
2973
2974 def currentwlock(self):
2974 def currentwlock(self):
2975 """Returns the wlock if it's held, or None if it's not."""
2975 """Returns the wlock if it's held, or None if it's not."""
2976 return self._currentlock(self._wlockref)
2976 return self._currentlock(self._wlockref)
2977
2977
2978 def checkcommitpatterns(self, wctx, match, status, fail):
2978 def checkcommitpatterns(self, wctx, match, status, fail):
2979 """check for commit arguments that aren't committable"""
2979 """check for commit arguments that aren't committable"""
2980 if match.isexact() or match.prefix():
2980 if match.isexact() or match.prefix():
2981 matched = set(status.modified + status.added + status.removed)
2981 matched = set(status.modified + status.added + status.removed)
2982
2982
2983 for f in match.files():
2983 for f in match.files():
2984 f = self.dirstate.normalize(f)
2984 f = self.dirstate.normalize(f)
2985 if f == b'.' or f in matched or f in wctx.substate:
2985 if f == b'.' or f in matched or f in wctx.substate:
2986 continue
2986 continue
2987 if f in status.deleted:
2987 if f in status.deleted:
2988 fail(f, _(b'file not found!'))
2988 fail(f, _(b'file not found!'))
2989 # Is it a directory that exists or used to exist?
2989 # Is it a directory that exists or used to exist?
2990 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2990 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2991 d = f + b'/'
2991 d = f + b'/'
2992 for mf in matched:
2992 for mf in matched:
2993 if mf.startswith(d):
2993 if mf.startswith(d):
2994 break
2994 break
2995 else:
2995 else:
2996 fail(f, _(b"no match under directory!"))
2996 fail(f, _(b"no match under directory!"))
2997 elif f not in self.dirstate:
2997 elif f not in self.dirstate:
2998 fail(f, _(b"file not tracked!"))
2998 fail(f, _(b"file not tracked!"))
2999
2999
3000 @unfilteredmethod
3000 @unfilteredmethod
3001 def commit(
3001 def commit(
3002 self,
3002 self,
3003 text=b"",
3003 text=b"",
3004 user=None,
3004 user=None,
3005 date=None,
3005 date=None,
3006 match=None,
3006 match=None,
3007 force=False,
3007 force=False,
3008 editor=None,
3008 editor=None,
3009 extra=None,
3009 extra=None,
3010 ):
3010 ):
3011 """Add a new revision to current repository.
3011 """Add a new revision to current repository.
3012
3012
3013 Revision information is gathered from the working directory,
3013 Revision information is gathered from the working directory,
3014 match can be used to filter the committed files. If editor is
3014 match can be used to filter the committed files. If editor is
3015 supplied, it is called to get a commit message.
3015 supplied, it is called to get a commit message.
3016 """
3016 """
3017 if extra is None:
3017 if extra is None:
3018 extra = {}
3018 extra = {}
3019
3019
3020 def fail(f, msg):
3020 def fail(f, msg):
3021 raise error.InputError(b'%s: %s' % (f, msg))
3021 raise error.InputError(b'%s: %s' % (f, msg))
3022
3022
3023 if not match:
3023 if not match:
3024 match = matchmod.always()
3024 match = matchmod.always()
3025
3025
3026 if not force:
3026 if not force:
3027 match.bad = fail
3027 match.bad = fail
3028
3028
3029 # lock() for recent changelog (see issue4368)
3029 # lock() for recent changelog (see issue4368)
3030 with self.wlock(), self.lock():
3030 with self.wlock(), self.lock():
3031 wctx = self[None]
3031 wctx = self[None]
3032 merge = len(wctx.parents()) > 1
3032 merge = len(wctx.parents()) > 1
3033
3033
3034 if not force and merge and not match.always():
3034 if not force and merge and not match.always():
3035 raise error.Abort(
3035 raise error.Abort(
3036 _(
3036 _(
3037 b'cannot partially commit a merge '
3037 b'cannot partially commit a merge '
3038 b'(do not specify files or patterns)'
3038 b'(do not specify files or patterns)'
3039 )
3039 )
3040 )
3040 )
3041
3041
3042 status = self.status(match=match, clean=force)
3042 status = self.status(match=match, clean=force)
3043 if force:
3043 if force:
3044 status.modified.extend(
3044 status.modified.extend(
3045 status.clean
3045 status.clean
3046 ) # mq may commit clean files
3046 ) # mq may commit clean files
3047
3047
3048 # check subrepos
3048 # check subrepos
3049 subs, commitsubs, newstate = subrepoutil.precommit(
3049 subs, commitsubs, newstate = subrepoutil.precommit(
3050 self.ui, wctx, status, match, force=force
3050 self.ui, wctx, status, match, force=force
3051 )
3051 )
3052
3052
3053 # make sure all explicit patterns are matched
3053 # make sure all explicit patterns are matched
3054 if not force:
3054 if not force:
3055 self.checkcommitpatterns(wctx, match, status, fail)
3055 self.checkcommitpatterns(wctx, match, status, fail)
3056
3056
3057 cctx = context.workingcommitctx(
3057 cctx = context.workingcommitctx(
3058 self, status, text, user, date, extra
3058 self, status, text, user, date, extra
3059 )
3059 )
3060
3060
3061 ms = mergestatemod.mergestate.read(self)
3061 ms = mergestatemod.mergestate.read(self)
3062 mergeutil.checkunresolved(ms)
3062 mergeutil.checkunresolved(ms)
3063
3063
3064 # internal config: ui.allowemptycommit
3064 # internal config: ui.allowemptycommit
3065 if cctx.isempty() and not self.ui.configbool(
3065 if cctx.isempty() and not self.ui.configbool(
3066 b'ui', b'allowemptycommit'
3066 b'ui', b'allowemptycommit'
3067 ):
3067 ):
3068 self.ui.debug(b'nothing to commit, clearing merge state\n')
3068 self.ui.debug(b'nothing to commit, clearing merge state\n')
3069 ms.reset()
3069 ms.reset()
3070 return None
3070 return None
3071
3071
3072 if merge and cctx.deleted():
3072 if merge and cctx.deleted():
3073 raise error.Abort(_(b"cannot commit merge with missing files"))
3073 raise error.Abort(_(b"cannot commit merge with missing files"))
3074
3074
3075 if editor:
3075 if editor:
3076 cctx._text = editor(self, cctx, subs)
3076 cctx._text = editor(self, cctx, subs)
3077 edited = text != cctx._text
3077 edited = text != cctx._text
3078
3078
3079 # Save commit message in case this transaction gets rolled back
3079 # Save commit message in case this transaction gets rolled back
3080 # (e.g. by a pretxncommit hook). Leave the content alone on
3080 # (e.g. by a pretxncommit hook). Leave the content alone on
3081 # the assumption that the user will use the same editor again.
3081 # the assumption that the user will use the same editor again.
3082 msgfn = self.savecommitmessage(cctx._text)
3082 msgfn = self.savecommitmessage(cctx._text)
3083
3083
3084 # commit subs and write new state
3084 # commit subs and write new state
3085 if subs:
3085 if subs:
3086 uipathfn = scmutil.getuipathfn(self)
3086 uipathfn = scmutil.getuipathfn(self)
3087 for s in sorted(commitsubs):
3087 for s in sorted(commitsubs):
3088 sub = wctx.sub(s)
3088 sub = wctx.sub(s)
3089 self.ui.status(
3089 self.ui.status(
3090 _(b'committing subrepository %s\n')
3090 _(b'committing subrepository %s\n')
3091 % uipathfn(subrepoutil.subrelpath(sub))
3091 % uipathfn(subrepoutil.subrelpath(sub))
3092 )
3092 )
3093 sr = sub.commit(cctx._text, user, date)
3093 sr = sub.commit(cctx._text, user, date)
3094 newstate[s] = (newstate[s][0], sr)
3094 newstate[s] = (newstate[s][0], sr)
3095 subrepoutil.writestate(self, newstate)
3095 subrepoutil.writestate(self, newstate)
3096
3096
3097 p1, p2 = self.dirstate.parents()
3097 p1, p2 = self.dirstate.parents()
3098 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3098 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3099 try:
3099 try:
3100 self.hook(
3100 self.hook(
3101 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3101 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3102 )
3102 )
3103 with self.transaction(b'commit'):
3103 with self.transaction(b'commit'):
3104 ret = self.commitctx(cctx, True)
3104 ret = self.commitctx(cctx, True)
3105 # update bookmarks, dirstate and mergestate
3105 # update bookmarks, dirstate and mergestate
3106 bookmarks.update(self, [p1, p2], ret)
3106 bookmarks.update(self, [p1, p2], ret)
3107 cctx.markcommitted(ret)
3107 cctx.markcommitted(ret)
3108 ms.reset()
3108 ms.reset()
3109 except: # re-raises
3109 except: # re-raises
3110 if edited:
3110 if edited:
3111 self.ui.write(
3111 self.ui.write(
3112 _(b'note: commit message saved in %s\n') % msgfn
3112 _(b'note: commit message saved in %s\n') % msgfn
3113 )
3113 )
3114 self.ui.write(
3114 self.ui.write(
3115 _(
3115 _(
3116 b"note: use 'hg commit --logfile "
3116 b"note: use 'hg commit --logfile "
3117 b".hg/last-message.txt --edit' to reuse it\n"
3117 b".hg/last-message.txt --edit' to reuse it\n"
3118 )
3118 )
3119 )
3119 )
3120 raise
3120 raise
3121
3121
3122 def commithook(unused_success):
3122 def commithook(unused_success):
3123 # hack for command that use a temporary commit (eg: histedit)
3123 # hack for command that use a temporary commit (eg: histedit)
3124 # temporary commit got stripped before hook release
3124 # temporary commit got stripped before hook release
3125 if self.changelog.hasnode(ret):
3125 if self.changelog.hasnode(ret):
3126 self.hook(
3126 self.hook(
3127 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3127 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3128 )
3128 )
3129
3129
3130 self._afterlock(commithook)
3130 self._afterlock(commithook)
3131 return ret
3131 return ret
3132
3132
3133 @unfilteredmethod
3133 @unfilteredmethod
3134 def commitctx(self, ctx, error=False, origctx=None):
3134 def commitctx(self, ctx, error=False, origctx=None):
3135 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3135 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3136
3136
3137 @unfilteredmethod
3137 @unfilteredmethod
3138 def destroying(self):
3138 def destroying(self):
3139 """Inform the repository that nodes are about to be destroyed.
3139 """Inform the repository that nodes are about to be destroyed.
3140 Intended for use by strip and rollback, so there's a common
3140 Intended for use by strip and rollback, so there's a common
3141 place for anything that has to be done before destroying history.
3141 place for anything that has to be done before destroying history.
3142
3142
3143 This is mostly useful for saving state that is in memory and waiting
3143 This is mostly useful for saving state that is in memory and waiting
3144 to be flushed when the current lock is released. Because a call to
3144 to be flushed when the current lock is released. Because a call to
3145 destroyed is imminent, the repo will be invalidated causing those
3145 destroyed is imminent, the repo will be invalidated causing those
3146 changes to stay in memory (waiting for the next unlock), or vanish
3146 changes to stay in memory (waiting for the next unlock), or vanish
3147 completely.
3147 completely.
3148 """
3148 """
3149 # When using the same lock to commit and strip, the phasecache is left
3149 # When using the same lock to commit and strip, the phasecache is left
3150 # dirty after committing. Then when we strip, the repo is invalidated,
3150 # dirty after committing. Then when we strip, the repo is invalidated,
3151 # causing those changes to disappear.
3151 # causing those changes to disappear.
3152 if '_phasecache' in vars(self):
3152 if '_phasecache' in vars(self):
3153 self._phasecache.write()
3153 self._phasecache.write()
3154
3154
3155 @unfilteredmethod
3155 @unfilteredmethod
3156 def destroyed(self):
3156 def destroyed(self):
3157 """Inform the repository that nodes have been destroyed.
3157 """Inform the repository that nodes have been destroyed.
3158 Intended for use by strip and rollback, so there's a common
3158 Intended for use by strip and rollback, so there's a common
3159 place for anything that has to be done after destroying history.
3159 place for anything that has to be done after destroying history.
3160 """
3160 """
3161 # When one tries to:
3161 # When one tries to:
3162 # 1) destroy nodes thus calling this method (e.g. strip)
3162 # 1) destroy nodes thus calling this method (e.g. strip)
3163 # 2) use phasecache somewhere (e.g. commit)
3163 # 2) use phasecache somewhere (e.g. commit)
3164 #
3164 #
3165 # then 2) will fail because the phasecache contains nodes that were
3165 # then 2) will fail because the phasecache contains nodes that were
3166 # removed. We can either remove phasecache from the filecache,
3166 # removed. We can either remove phasecache from the filecache,
3167 # causing it to reload next time it is accessed, or simply filter
3167 # causing it to reload next time it is accessed, or simply filter
3168 # the removed nodes now and write the updated cache.
3168 # the removed nodes now and write the updated cache.
3169 self._phasecache.filterunknown(self)
3169 self._phasecache.filterunknown(self)
3170 self._phasecache.write()
3170 self._phasecache.write()
3171
3171
3172 # refresh all repository caches
3172 # refresh all repository caches
3173 self.updatecaches()
3173 self.updatecaches()
3174
3174
3175 # Ensure the persistent tag cache is updated. Doing it now
3175 # Ensure the persistent tag cache is updated. Doing it now
3176 # means that the tag cache only has to worry about destroyed
3176 # means that the tag cache only has to worry about destroyed
3177 # heads immediately after a strip/rollback. That in turn
3177 # heads immediately after a strip/rollback. That in turn
3178 # guarantees that "cachetip == currenttip" (comparing both rev
3178 # guarantees that "cachetip == currenttip" (comparing both rev
3179 # and node) always means no nodes have been added or destroyed.
3179 # and node) always means no nodes have been added or destroyed.
3180
3180
3181 # XXX this is suboptimal when qrefresh'ing: we strip the current
3181 # XXX this is suboptimal when qrefresh'ing: we strip the current
3182 # head, refresh the tag cache, then immediately add a new head.
3182 # head, refresh the tag cache, then immediately add a new head.
3183 # But I think doing it this way is necessary for the "instant
3183 # But I think doing it this way is necessary for the "instant
3184 # tag cache retrieval" case to work.
3184 # tag cache retrieval" case to work.
3185 self.invalidate()
3185 self.invalidate()
3186
3186
3187 def status(
3187 def status(
3188 self,
3188 self,
3189 node1=b'.',
3189 node1=b'.',
3190 node2=None,
3190 node2=None,
3191 match=None,
3191 match=None,
3192 ignored=False,
3192 ignored=False,
3193 clean=False,
3193 clean=False,
3194 unknown=False,
3194 unknown=False,
3195 listsubrepos=False,
3195 listsubrepos=False,
3196 ):
3196 ):
3197 '''a convenience method that calls node1.status(node2)'''
3197 '''a convenience method that calls node1.status(node2)'''
3198 return self[node1].status(
3198 return self[node1].status(
3199 node2, match, ignored, clean, unknown, listsubrepos
3199 node2, match, ignored, clean, unknown, listsubrepos
3200 )
3200 )
3201
3201
3202 def addpostdsstatus(self, ps):
3202 def addpostdsstatus(self, ps):
3203 """Add a callback to run within the wlock, at the point at which status
3203 """Add a callback to run within the wlock, at the point at which status
3204 fixups happen.
3204 fixups happen.
3205
3205
3206 On status completion, callback(wctx, status) will be called with the
3206 On status completion, callback(wctx, status) will be called with the
3207 wlock held, unless the dirstate has changed from underneath or the wlock
3207 wlock held, unless the dirstate has changed from underneath or the wlock
3208 couldn't be grabbed.
3208 couldn't be grabbed.
3209
3209
3210 Callbacks should not capture and use a cached copy of the dirstate --
3210 Callbacks should not capture and use a cached copy of the dirstate --
3211 it might change in the meanwhile. Instead, they should access the
3211 it might change in the meanwhile. Instead, they should access the
3212 dirstate via wctx.repo().dirstate.
3212 dirstate via wctx.repo().dirstate.
3213
3213
3214 This list is emptied out after each status run -- extensions should
3214 This list is emptied out after each status run -- extensions should
3215 make sure it adds to this list each time dirstate.status is called.
3215 make sure it adds to this list each time dirstate.status is called.
3216 Extensions should also make sure they don't call this for statuses
3216 Extensions should also make sure they don't call this for statuses
3217 that don't involve the dirstate.
3217 that don't involve the dirstate.
3218 """
3218 """
3219
3219
3220 # The list is located here for uniqueness reasons -- it is actually
3220 # The list is located here for uniqueness reasons -- it is actually
3221 # managed by the workingctx, but that isn't unique per-repo.
3221 # managed by the workingctx, but that isn't unique per-repo.
3222 self._postdsstatus.append(ps)
3222 self._postdsstatus.append(ps)
3223
3223
3224 def postdsstatus(self):
3224 def postdsstatus(self):
3225 """Used by workingctx to get the list of post-dirstate-status hooks."""
3225 """Used by workingctx to get the list of post-dirstate-status hooks."""
3226 return self._postdsstatus
3226 return self._postdsstatus
3227
3227
3228 def clearpostdsstatus(self):
3228 def clearpostdsstatus(self):
3229 """Used by workingctx to clear post-dirstate-status hooks."""
3229 """Used by workingctx to clear post-dirstate-status hooks."""
3230 del self._postdsstatus[:]
3230 del self._postdsstatus[:]
3231
3231
3232 def heads(self, start=None):
3232 def heads(self, start=None):
3233 if start is None:
3233 if start is None:
3234 cl = self.changelog
3234 cl = self.changelog
3235 headrevs = reversed(cl.headrevs())
3235 headrevs = reversed(cl.headrevs())
3236 return [cl.node(rev) for rev in headrevs]
3236 return [cl.node(rev) for rev in headrevs]
3237
3237
3238 heads = self.changelog.heads(start)
3238 heads = self.changelog.heads(start)
3239 # sort the output in rev descending order
3239 # sort the output in rev descending order
3240 return sorted(heads, key=self.changelog.rev, reverse=True)
3240 return sorted(heads, key=self.changelog.rev, reverse=True)
3241
3241
3242 def branchheads(self, branch=None, start=None, closed=False):
3242 def branchheads(self, branch=None, start=None, closed=False):
3243 """return a (possibly filtered) list of heads for the given branch
3243 """return a (possibly filtered) list of heads for the given branch
3244
3244
3245 Heads are returned in topological order, from newest to oldest.
3245 Heads are returned in topological order, from newest to oldest.
3246 If branch is None, use the dirstate branch.
3246 If branch is None, use the dirstate branch.
3247 If start is not None, return only heads reachable from start.
3247 If start is not None, return only heads reachable from start.
3248 If closed is True, return heads that are marked as closed as well.
3248 If closed is True, return heads that are marked as closed as well.
3249 """
3249 """
3250 if branch is None:
3250 if branch is None:
3251 branch = self[None].branch()
3251 branch = self[None].branch()
3252 branches = self.branchmap()
3252 branches = self.branchmap()
3253 if not branches.hasbranch(branch):
3253 if not branches.hasbranch(branch):
3254 return []
3254 return []
3255 # the cache returns heads ordered lowest to highest
3255 # the cache returns heads ordered lowest to highest
3256 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3256 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3257 if start is not None:
3257 if start is not None:
3258 # filter out the heads that cannot be reached from startrev
3258 # filter out the heads that cannot be reached from startrev
3259 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3259 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3260 bheads = [h for h in bheads if h in fbheads]
3260 bheads = [h for h in bheads if h in fbheads]
3261 return bheads
3261 return bheads
3262
3262
3263 def branches(self, nodes):
3263 def branches(self, nodes):
3264 if not nodes:
3264 if not nodes:
3265 nodes = [self.changelog.tip()]
3265 nodes = [self.changelog.tip()]
3266 b = []
3266 b = []
3267 for n in nodes:
3267 for n in nodes:
3268 t = n
3268 t = n
3269 while True:
3269 while True:
3270 p = self.changelog.parents(n)
3270 p = self.changelog.parents(n)
3271 if p[1] != self.nullid or p[0] == self.nullid:
3271 if p[1] != self.nullid or p[0] == self.nullid:
3272 b.append((t, n, p[0], p[1]))
3272 b.append((t, n, p[0], p[1]))
3273 break
3273 break
3274 n = p[0]
3274 n = p[0]
3275 return b
3275 return b
3276
3276
3277 def between(self, pairs):
3277 def between(self, pairs):
3278 r = []
3278 r = []
3279
3279
3280 for top, bottom in pairs:
3280 for top, bottom in pairs:
3281 n, l, i = top, [], 0
3281 n, l, i = top, [], 0
3282 f = 1
3282 f = 1
3283
3283
3284 while n != bottom and n != self.nullid:
3284 while n != bottom and n != self.nullid:
3285 p = self.changelog.parents(n)[0]
3285 p = self.changelog.parents(n)[0]
3286 if i == f:
3286 if i == f:
3287 l.append(n)
3287 l.append(n)
3288 f = f * 2
3288 f = f * 2
3289 n = p
3289 n = p
3290 i += 1
3290 i += 1
3291
3291
3292 r.append(l)
3292 r.append(l)
3293
3293
3294 return r
3294 return r
3295
3295
3296 def checkpush(self, pushop):
3296 def checkpush(self, pushop):
3297 """Extensions can override this function if additional checks have
3297 """Extensions can override this function if additional checks have
3298 to be performed before pushing, or call it if they override push
3298 to be performed before pushing, or call it if they override push
3299 command.
3299 command.
3300 """
3300 """
3301
3301
3302 @unfilteredpropertycache
3302 @unfilteredpropertycache
3303 def prepushoutgoinghooks(self):
3303 def prepushoutgoinghooks(self):
3304 """Return util.hooks consists of a pushop with repo, remote, outgoing
3304 """Return util.hooks consists of a pushop with repo, remote, outgoing
3305 methods, which are called before pushing changesets.
3305 methods, which are called before pushing changesets.
3306 """
3306 """
3307 return util.hooks()
3307 return util.hooks()
3308
3308
3309 def pushkey(self, namespace, key, old, new):
3309 def pushkey(self, namespace, key, old, new):
3310 try:
3310 try:
3311 tr = self.currenttransaction()
3311 tr = self.currenttransaction()
3312 hookargs = {}
3312 hookargs = {}
3313 if tr is not None:
3313 if tr is not None:
3314 hookargs.update(tr.hookargs)
3314 hookargs.update(tr.hookargs)
3315 hookargs = pycompat.strkwargs(hookargs)
3315 hookargs = pycompat.strkwargs(hookargs)
3316 hookargs['namespace'] = namespace
3316 hookargs['namespace'] = namespace
3317 hookargs['key'] = key
3317 hookargs['key'] = key
3318 hookargs['old'] = old
3318 hookargs['old'] = old
3319 hookargs['new'] = new
3319 hookargs['new'] = new
3320 self.hook(b'prepushkey', throw=True, **hookargs)
3320 self.hook(b'prepushkey', throw=True, **hookargs)
3321 except error.HookAbort as exc:
3321 except error.HookAbort as exc:
3322 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3322 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3323 if exc.hint:
3323 if exc.hint:
3324 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3324 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3325 return False
3325 return False
3326 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3326 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3327 ret = pushkey.push(self, namespace, key, old, new)
3327 ret = pushkey.push(self, namespace, key, old, new)
3328
3328
3329 def runhook(unused_success):
3329 def runhook(unused_success):
3330 self.hook(
3330 self.hook(
3331 b'pushkey',
3331 b'pushkey',
3332 namespace=namespace,
3332 namespace=namespace,
3333 key=key,
3333 key=key,
3334 old=old,
3334 old=old,
3335 new=new,
3335 new=new,
3336 ret=ret,
3336 ret=ret,
3337 )
3337 )
3338
3338
3339 self._afterlock(runhook)
3339 self._afterlock(runhook)
3340 return ret
3340 return ret
3341
3341
3342 def listkeys(self, namespace):
3342 def listkeys(self, namespace):
3343 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3343 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3344 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3344 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3345 values = pushkey.list(self, namespace)
3345 values = pushkey.list(self, namespace)
3346 self.hook(b'listkeys', namespace=namespace, values=values)
3346 self.hook(b'listkeys', namespace=namespace, values=values)
3347 return values
3347 return values
3348
3348
3349 def debugwireargs(self, one, two, three=None, four=None, five=None):
3349 def debugwireargs(self, one, two, three=None, four=None, five=None):
3350 '''used to test argument passing over the wire'''
3350 '''used to test argument passing over the wire'''
3351 return b"%s %s %s %s %s" % (
3351 return b"%s %s %s %s %s" % (
3352 one,
3352 one,
3353 two,
3353 two,
3354 pycompat.bytestr(three),
3354 pycompat.bytestr(three),
3355 pycompat.bytestr(four),
3355 pycompat.bytestr(four),
3356 pycompat.bytestr(five),
3356 pycompat.bytestr(five),
3357 )
3357 )
3358
3358
3359 def savecommitmessage(self, text):
3359 def savecommitmessage(self, text):
3360 fp = self.vfs(b'last-message.txt', b'wb')
3360 fp = self.vfs(b'last-message.txt', b'wb')
3361 try:
3361 try:
3362 fp.write(text)
3362 fp.write(text)
3363 finally:
3363 finally:
3364 fp.close()
3364 fp.close()
3365 return self.pathto(fp.name[len(self.root) + 1 :])
3365 return self.pathto(fp.name[len(self.root) + 1 :])
3366
3366
3367 def register_wanted_sidedata(self, category):
3367 def register_wanted_sidedata(self, category):
3368 if requirementsmod.REVLOGV2_REQUIREMENT not in self.requirements:
3368 if requirementsmod.REVLOGV2_REQUIREMENT not in self.requirements:
3369 # Only revlogv2 repos can want sidedata.
3369 # Only revlogv2 repos can want sidedata.
3370 return
3370 return
3371 self._wanted_sidedata.add(pycompat.bytestr(category))
3371 self._wanted_sidedata.add(pycompat.bytestr(category))
3372
3372
3373 def register_sidedata_computer(
3373 def register_sidedata_computer(
3374 self, kind, category, keys, computer, flags, replace=False
3374 self, kind, category, keys, computer, flags, replace=False
3375 ):
3375 ):
3376 if kind not in revlogconst.ALL_KINDS:
3376 if kind not in revlogconst.ALL_KINDS:
3377 msg = _(b"unexpected revlog kind '%s'.")
3377 msg = _(b"unexpected revlog kind '%s'.")
3378 raise error.ProgrammingError(msg % kind)
3378 raise error.ProgrammingError(msg % kind)
3379 category = pycompat.bytestr(category)
3379 category = pycompat.bytestr(category)
3380 already_registered = category in self._sidedata_computers.get(kind, [])
3380 already_registered = category in self._sidedata_computers.get(kind, [])
3381 if already_registered and not replace:
3381 if already_registered and not replace:
3382 msg = _(
3382 msg = _(
3383 b"cannot register a sidedata computer twice for category '%s'."
3383 b"cannot register a sidedata computer twice for category '%s'."
3384 )
3384 )
3385 raise error.ProgrammingError(msg % category)
3385 raise error.ProgrammingError(msg % category)
3386 if replace and not already_registered:
3386 if replace and not already_registered:
3387 msg = _(
3387 msg = _(
3388 b"cannot replace a sidedata computer that isn't registered "
3388 b"cannot replace a sidedata computer that isn't registered "
3389 b"for category '%s'."
3389 b"for category '%s'."
3390 )
3390 )
3391 raise error.ProgrammingError(msg % category)
3391 raise error.ProgrammingError(msg % category)
3392 self._sidedata_computers.setdefault(kind, {})
3392 self._sidedata_computers.setdefault(kind, {})
3393 self._sidedata_computers[kind][category] = (keys, computer, flags)
3393 self._sidedata_computers[kind][category] = (keys, computer, flags)
3394
3394
3395
3395
3396 # used to avoid circular references so destructors work
3396 # used to avoid circular references so destructors work
3397 def aftertrans(files):
3397 def aftertrans(files):
3398 renamefiles = [tuple(t) for t in files]
3398 renamefiles = [tuple(t) for t in files]
3399
3399
3400 def a():
3400 def a():
3401 for vfs, src, dest in renamefiles:
3401 for vfs, src, dest in renamefiles:
3402 # if src and dest refer to a same file, vfs.rename is a no-op,
3402 # if src and dest refer to a same file, vfs.rename is a no-op,
3403 # leaving both src and dest on disk. delete dest to make sure
3403 # leaving both src and dest on disk. delete dest to make sure
3404 # the rename couldn't be such a no-op.
3404 # the rename couldn't be such a no-op.
3405 vfs.tryunlink(dest)
3405 vfs.tryunlink(dest)
3406 try:
3406 try:
3407 vfs.rename(src, dest)
3407 vfs.rename(src, dest)
3408 except OSError: # journal file does not yet exist
3408 except OSError: # journal file does not yet exist
3409 pass
3409 pass
3410
3410
3411 return a
3411 return a
3412
3412
3413
3413
3414 def undoname(fn):
3414 def undoname(fn):
3415 base, name = os.path.split(fn)
3415 base, name = os.path.split(fn)
3416 assert name.startswith(b'journal')
3416 assert name.startswith(b'journal')
3417 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3417 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3418
3418
3419
3419
3420 def instance(ui, path, create, intents=None, createopts=None):
3420 def instance(ui, path, create, intents=None, createopts=None):
3421 localpath = urlutil.urllocalpath(path)
3421 localpath = urlutil.urllocalpath(path)
3422 if create:
3422 if create:
3423 createrepository(ui, localpath, createopts=createopts)
3423 createrepository(ui, localpath, createopts=createopts)
3424
3424
3425 return makelocalrepository(ui, localpath, intents=intents)
3425 return makelocalrepository(ui, localpath, intents=intents)
3426
3426
3427
3427
3428 def islocal(path):
3428 def islocal(path):
3429 return True
3429 return True
3430
3430
3431
3431
3432 def defaultcreateopts(ui, createopts=None):
3432 def defaultcreateopts(ui, createopts=None):
3433 """Populate the default creation options for a repository.
3433 """Populate the default creation options for a repository.
3434
3434
3435 A dictionary of explicitly requested creation options can be passed
3435 A dictionary of explicitly requested creation options can be passed
3436 in. Missing keys will be populated.
3436 in. Missing keys will be populated.
3437 """
3437 """
3438 createopts = dict(createopts or {})
3438 createopts = dict(createopts or {})
3439
3439
3440 if b'backend' not in createopts:
3440 if b'backend' not in createopts:
3441 # experimental config: storage.new-repo-backend
3441 # experimental config: storage.new-repo-backend
3442 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3442 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3443
3443
3444 return createopts
3444 return createopts
3445
3445
3446
3446
3447 def newreporequirements(ui, createopts):
3447 def newreporequirements(ui, createopts):
3448 """Determine the set of requirements for a new local repository.
3448 """Determine the set of requirements for a new local repository.
3449
3449
3450 Extensions can wrap this function to specify custom requirements for
3450 Extensions can wrap this function to specify custom requirements for
3451 new repositories.
3451 new repositories.
3452 """
3452 """
3453 # If the repo is being created from a shared repository, we copy
3453 # If the repo is being created from a shared repository, we copy
3454 # its requirements.
3454 # its requirements.
3455 if b'sharedrepo' in createopts:
3455 if b'sharedrepo' in createopts:
3456 requirements = set(createopts[b'sharedrepo'].requirements)
3456 requirements = set(createopts[b'sharedrepo'].requirements)
3457 if createopts.get(b'sharedrelative'):
3457 if createopts.get(b'sharedrelative'):
3458 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3458 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3459 else:
3459 else:
3460 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3460 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3461
3461
3462 return requirements
3462 return requirements
3463
3463
3464 if b'backend' not in createopts:
3464 if b'backend' not in createopts:
3465 raise error.ProgrammingError(
3465 raise error.ProgrammingError(
3466 b'backend key not present in createopts; '
3466 b'backend key not present in createopts; '
3467 b'was defaultcreateopts() called?'
3467 b'was defaultcreateopts() called?'
3468 )
3468 )
3469
3469
3470 if createopts[b'backend'] != b'revlogv1':
3470 if createopts[b'backend'] != b'revlogv1':
3471 raise error.Abort(
3471 raise error.Abort(
3472 _(
3472 _(
3473 b'unable to determine repository requirements for '
3473 b'unable to determine repository requirements for '
3474 b'storage backend: %s'
3474 b'storage backend: %s'
3475 )
3475 )
3476 % createopts[b'backend']
3476 % createopts[b'backend']
3477 )
3477 )
3478
3478
3479 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3479 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3480 if ui.configbool(b'format', b'usestore'):
3480 if ui.configbool(b'format', b'usestore'):
3481 requirements.add(requirementsmod.STORE_REQUIREMENT)
3481 requirements.add(requirementsmod.STORE_REQUIREMENT)
3482 if ui.configbool(b'format', b'usefncache'):
3482 if ui.configbool(b'format', b'usefncache'):
3483 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3483 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3484 if ui.configbool(b'format', b'dotencode'):
3484 if ui.configbool(b'format', b'dotencode'):
3485 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3485 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3486
3486
3487 compengines = ui.configlist(b'format', b'revlog-compression')
3487 compengines = ui.configlist(b'format', b'revlog-compression')
3488 for compengine in compengines:
3488 for compengine in compengines:
3489 if compengine in util.compengines:
3489 if compengine in util.compengines:
3490 engine = util.compengines[compengine]
3490 engine = util.compengines[compengine]
3491 if engine.available() and engine.revlogheader():
3491 if engine.available() and engine.revlogheader():
3492 break
3492 break
3493 else:
3493 else:
3494 raise error.Abort(
3494 raise error.Abort(
3495 _(
3495 _(
3496 b'compression engines %s defined by '
3496 b'compression engines %s defined by '
3497 b'format.revlog-compression not available'
3497 b'format.revlog-compression not available'
3498 )
3498 )
3499 % b', '.join(b'"%s"' % e for e in compengines),
3499 % b', '.join(b'"%s"' % e for e in compengines),
3500 hint=_(
3500 hint=_(
3501 b'run "hg debuginstall" to list available '
3501 b'run "hg debuginstall" to list available '
3502 b'compression engines'
3502 b'compression engines'
3503 ),
3503 ),
3504 )
3504 )
3505
3505
3506 # zlib is the historical default and doesn't need an explicit requirement.
3506 # zlib is the historical default and doesn't need an explicit requirement.
3507 if compengine == b'zstd':
3507 if compengine == b'zstd':
3508 requirements.add(b'revlog-compression-zstd')
3508 requirements.add(b'revlog-compression-zstd')
3509 elif compengine != b'zlib':
3509 elif compengine != b'zlib':
3510 requirements.add(b'exp-compression-%s' % compengine)
3510 requirements.add(b'exp-compression-%s' % compengine)
3511
3511
3512 if scmutil.gdinitconfig(ui):
3512 if scmutil.gdinitconfig(ui):
3513 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3513 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3514 if ui.configbool(b'format', b'sparse-revlog'):
3514 if ui.configbool(b'format', b'sparse-revlog'):
3515 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3515 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3516
3516
3517 # experimental config: format.exp-use-side-data
3517 # experimental config: format.exp-use-side-data
3518 if ui.configbool(b'format', b'exp-use-side-data'):
3518 if ui.configbool(b'format', b'exp-use-side-data'):
3519 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3519 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3520 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3520 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3521 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3521 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3522 # experimental config: format.exp-use-copies-side-data-changeset
3522 # experimental config: format.exp-use-copies-side-data-changeset
3523 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3523 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3524 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3524 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3525 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3525 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3526 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3526 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3527 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3527 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3528 if ui.configbool(b'experimental', b'treemanifest'):
3528 if ui.configbool(b'experimental', b'treemanifest'):
3529 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3529 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3530
3530
3531 revlogv2 = ui.config(b'experimental', b'revlogv2')
3531 revlogv2 = ui.config(b'experimental', b'revlogv2')
3532 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3532 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3533 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3533 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3534 # generaldelta is implied by revlogv2.
3534 # generaldelta is implied by revlogv2.
3535 requirements.discard(requirementsmod.GENERALDELTA_REQUIREMENT)
3535 requirements.discard(requirementsmod.GENERALDELTA_REQUIREMENT)
3536 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3536 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3537 # experimental config: format.internal-phase
3537 # experimental config: format.internal-phase
3538 if ui.configbool(b'format', b'internal-phase'):
3538 if ui.configbool(b'format', b'internal-phase'):
3539 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3539 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3540
3540
3541 if createopts.get(b'narrowfiles'):
3541 if createopts.get(b'narrowfiles'):
3542 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3542 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3543
3543
3544 if createopts.get(b'lfs'):
3544 if createopts.get(b'lfs'):
3545 requirements.add(b'lfs')
3545 requirements.add(b'lfs')
3546
3546
3547 if ui.configbool(b'format', b'bookmarks-in-store'):
3547 if ui.configbool(b'format', b'bookmarks-in-store'):
3548 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3548 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3549
3549
3550 if ui.configbool(b'format', b'use-persistent-nodemap'):
3550 if ui.configbool(b'format', b'use-persistent-nodemap'):
3551 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3551 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3552
3552
3553 # if share-safe is enabled, let's create the new repository with the new
3553 # if share-safe is enabled, let's create the new repository with the new
3554 # requirement
3554 # requirement
3555 if ui.configbool(b'format', b'use-share-safe'):
3555 if ui.configbool(b'format', b'use-share-safe'):
3556 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3556 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3557
3557
3558 return requirements
3558 return requirements
3559
3559
3560
3560
3561 def checkrequirementscompat(ui, requirements):
3561 def checkrequirementscompat(ui, requirements):
3562 """Checks compatibility of repository requirements enabled and disabled.
3562 """Checks compatibility of repository requirements enabled and disabled.
3563
3563
3564 Returns a set of requirements which needs to be dropped because dependend
3564 Returns a set of requirements which needs to be dropped because dependend
3565 requirements are not enabled. Also warns users about it"""
3565 requirements are not enabled. Also warns users about it"""
3566
3566
3567 dropped = set()
3567 dropped = set()
3568
3568
3569 if requirementsmod.STORE_REQUIREMENT not in requirements:
3569 if requirementsmod.STORE_REQUIREMENT not in requirements:
3570 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3570 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3571 ui.warn(
3571 ui.warn(
3572 _(
3572 _(
3573 b'ignoring enabled \'format.bookmarks-in-store\' config '
3573 b'ignoring enabled \'format.bookmarks-in-store\' config '
3574 b'beacuse it is incompatible with disabled '
3574 b'beacuse it is incompatible with disabled '
3575 b'\'format.usestore\' config\n'
3575 b'\'format.usestore\' config\n'
3576 )
3576 )
3577 )
3577 )
3578 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3578 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3579
3579
3580 if (
3580 if (
3581 requirementsmod.SHARED_REQUIREMENT in requirements
3581 requirementsmod.SHARED_REQUIREMENT in requirements
3582 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3582 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3583 ):
3583 ):
3584 raise error.Abort(
3584 raise error.Abort(
3585 _(
3585 _(
3586 b"cannot create shared repository as source was created"
3586 b"cannot create shared repository as source was created"
3587 b" with 'format.usestore' config disabled"
3587 b" with 'format.usestore' config disabled"
3588 )
3588 )
3589 )
3589 )
3590
3590
3591 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3591 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3592 ui.warn(
3592 ui.warn(
3593 _(
3593 _(
3594 b"ignoring enabled 'format.use-share-safe' config because "
3594 b"ignoring enabled 'format.use-share-safe' config because "
3595 b"it is incompatible with disabled 'format.usestore'"
3595 b"it is incompatible with disabled 'format.usestore'"
3596 b" config\n"
3596 b" config\n"
3597 )
3597 )
3598 )
3598 )
3599 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3599 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3600
3600
3601 return dropped
3601 return dropped
3602
3602
3603
3603
3604 def filterknowncreateopts(ui, createopts):
3604 def filterknowncreateopts(ui, createopts):
3605 """Filters a dict of repo creation options against options that are known.
3605 """Filters a dict of repo creation options against options that are known.
3606
3606
3607 Receives a dict of repo creation options and returns a dict of those
3607 Receives a dict of repo creation options and returns a dict of those
3608 options that we don't know how to handle.
3608 options that we don't know how to handle.
3609
3609
3610 This function is called as part of repository creation. If the
3610 This function is called as part of repository creation. If the
3611 returned dict contains any items, repository creation will not
3611 returned dict contains any items, repository creation will not
3612 be allowed, as it means there was a request to create a repository
3612 be allowed, as it means there was a request to create a repository
3613 with options not recognized by loaded code.
3613 with options not recognized by loaded code.
3614
3614
3615 Extensions can wrap this function to filter out creation options
3615 Extensions can wrap this function to filter out creation options
3616 they know how to handle.
3616 they know how to handle.
3617 """
3617 """
3618 known = {
3618 known = {
3619 b'backend',
3619 b'backend',
3620 b'lfs',
3620 b'lfs',
3621 b'narrowfiles',
3621 b'narrowfiles',
3622 b'sharedrepo',
3622 b'sharedrepo',
3623 b'sharedrelative',
3623 b'sharedrelative',
3624 b'shareditems',
3624 b'shareditems',
3625 b'shallowfilestore',
3625 b'shallowfilestore',
3626 }
3626 }
3627
3627
3628 return {k: v for k, v in createopts.items() if k not in known}
3628 return {k: v for k, v in createopts.items() if k not in known}
3629
3629
3630
3630
3631 def createrepository(ui, path, createopts=None):
3631 def createrepository(ui, path, createopts=None):
3632 """Create a new repository in a vfs.
3632 """Create a new repository in a vfs.
3633
3633
3634 ``path`` path to the new repo's working directory.
3634 ``path`` path to the new repo's working directory.
3635 ``createopts`` options for the new repository.
3635 ``createopts`` options for the new repository.
3636
3636
3637 The following keys for ``createopts`` are recognized:
3637 The following keys for ``createopts`` are recognized:
3638
3638
3639 backend
3639 backend
3640 The storage backend to use.
3640 The storage backend to use.
3641 lfs
3641 lfs
3642 Repository will be created with ``lfs`` requirement. The lfs extension
3642 Repository will be created with ``lfs`` requirement. The lfs extension
3643 will automatically be loaded when the repository is accessed.
3643 will automatically be loaded when the repository is accessed.
3644 narrowfiles
3644 narrowfiles
3645 Set up repository to support narrow file storage.
3645 Set up repository to support narrow file storage.
3646 sharedrepo
3646 sharedrepo
3647 Repository object from which storage should be shared.
3647 Repository object from which storage should be shared.
3648 sharedrelative
3648 sharedrelative
3649 Boolean indicating if the path to the shared repo should be
3649 Boolean indicating if the path to the shared repo should be
3650 stored as relative. By default, the pointer to the "parent" repo
3650 stored as relative. By default, the pointer to the "parent" repo
3651 is stored as an absolute path.
3651 is stored as an absolute path.
3652 shareditems
3652 shareditems
3653 Set of items to share to the new repository (in addition to storage).
3653 Set of items to share to the new repository (in addition to storage).
3654 shallowfilestore
3654 shallowfilestore
3655 Indicates that storage for files should be shallow (not all ancestor
3655 Indicates that storage for files should be shallow (not all ancestor
3656 revisions are known).
3656 revisions are known).
3657 """
3657 """
3658 createopts = defaultcreateopts(ui, createopts=createopts)
3658 createopts = defaultcreateopts(ui, createopts=createopts)
3659
3659
3660 unknownopts = filterknowncreateopts(ui, createopts)
3660 unknownopts = filterknowncreateopts(ui, createopts)
3661
3661
3662 if not isinstance(unknownopts, dict):
3662 if not isinstance(unknownopts, dict):
3663 raise error.ProgrammingError(
3663 raise error.ProgrammingError(
3664 b'filterknowncreateopts() did not return a dict'
3664 b'filterknowncreateopts() did not return a dict'
3665 )
3665 )
3666
3666
3667 if unknownopts:
3667 if unknownopts:
3668 raise error.Abort(
3668 raise error.Abort(
3669 _(
3669 _(
3670 b'unable to create repository because of unknown '
3670 b'unable to create repository because of unknown '
3671 b'creation option: %s'
3671 b'creation option: %s'
3672 )
3672 )
3673 % b', '.join(sorted(unknownopts)),
3673 % b', '.join(sorted(unknownopts)),
3674 hint=_(b'is a required extension not loaded?'),
3674 hint=_(b'is a required extension not loaded?'),
3675 )
3675 )
3676
3676
3677 requirements = newreporequirements(ui, createopts=createopts)
3677 requirements = newreporequirements(ui, createopts=createopts)
3678 requirements -= checkrequirementscompat(ui, requirements)
3678 requirements -= checkrequirementscompat(ui, requirements)
3679
3679
3680 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3680 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3681
3681
3682 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3682 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3683 if hgvfs.exists():
3683 if hgvfs.exists():
3684 raise error.RepoError(_(b'repository %s already exists') % path)
3684 raise error.RepoError(_(b'repository %s already exists') % path)
3685
3685
3686 if b'sharedrepo' in createopts:
3686 if b'sharedrepo' in createopts:
3687 sharedpath = createopts[b'sharedrepo'].sharedpath
3687 sharedpath = createopts[b'sharedrepo'].sharedpath
3688
3688
3689 if createopts.get(b'sharedrelative'):
3689 if createopts.get(b'sharedrelative'):
3690 try:
3690 try:
3691 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3691 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3692 sharedpath = util.pconvert(sharedpath)
3692 sharedpath = util.pconvert(sharedpath)
3693 except (IOError, ValueError) as e:
3693 except (IOError, ValueError) as e:
3694 # ValueError is raised on Windows if the drive letters differ
3694 # ValueError is raised on Windows if the drive letters differ
3695 # on each path.
3695 # on each path.
3696 raise error.Abort(
3696 raise error.Abort(
3697 _(b'cannot calculate relative path'),
3697 _(b'cannot calculate relative path'),
3698 hint=stringutil.forcebytestr(e),
3698 hint=stringutil.forcebytestr(e),
3699 )
3699 )
3700
3700
3701 if not wdirvfs.exists():
3701 if not wdirvfs.exists():
3702 wdirvfs.makedirs()
3702 wdirvfs.makedirs()
3703
3703
3704 hgvfs.makedir(notindexed=True)
3704 hgvfs.makedir(notindexed=True)
3705 if b'sharedrepo' not in createopts:
3705 if b'sharedrepo' not in createopts:
3706 hgvfs.mkdir(b'cache')
3706 hgvfs.mkdir(b'cache')
3707 hgvfs.mkdir(b'wcache')
3707 hgvfs.mkdir(b'wcache')
3708
3708
3709 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3709 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3710 if has_store and b'sharedrepo' not in createopts:
3710 if has_store and b'sharedrepo' not in createopts:
3711 hgvfs.mkdir(b'store')
3711 hgvfs.mkdir(b'store')
3712
3712
3713 # We create an invalid changelog outside the store so very old
3713 # We create an invalid changelog outside the store so very old
3714 # Mercurial versions (which didn't know about the requirements
3714 # Mercurial versions (which didn't know about the requirements
3715 # file) encounter an error on reading the changelog. This
3715 # file) encounter an error on reading the changelog. This
3716 # effectively locks out old clients and prevents them from
3716 # effectively locks out old clients and prevents them from
3717 # mucking with a repo in an unknown format.
3717 # mucking with a repo in an unknown format.
3718 #
3718 #
3719 # The revlog header has version 65535, which won't be recognized by
3719 # The revlog header has version 65535, which won't be recognized by
3720 # such old clients.
3720 # such old clients.
3721 hgvfs.append(
3721 hgvfs.append(
3722 b'00changelog.i',
3722 b'00changelog.i',
3723 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3723 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3724 b'layout',
3724 b'layout',
3725 )
3725 )
3726
3726
3727 # Filter the requirements into working copy and store ones
3727 # Filter the requirements into working copy and store ones
3728 wcreq, storereq = scmutil.filterrequirements(requirements)
3728 wcreq, storereq = scmutil.filterrequirements(requirements)
3729 # write working copy ones
3729 # write working copy ones
3730 scmutil.writerequires(hgvfs, wcreq)
3730 scmutil.writerequires(hgvfs, wcreq)
3731 # If there are store requirements and the current repository
3731 # If there are store requirements and the current repository
3732 # is not a shared one, write stored requirements
3732 # is not a shared one, write stored requirements
3733 # For new shared repository, we don't need to write the store
3733 # For new shared repository, we don't need to write the store
3734 # requirements as they are already present in store requires
3734 # requirements as they are already present in store requires
3735 if storereq and b'sharedrepo' not in createopts:
3735 if storereq and b'sharedrepo' not in createopts:
3736 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3736 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3737 scmutil.writerequires(storevfs, storereq)
3737 scmutil.writerequires(storevfs, storereq)
3738
3738
3739 # Write out file telling readers where to find the shared store.
3739 # Write out file telling readers where to find the shared store.
3740 if b'sharedrepo' in createopts:
3740 if b'sharedrepo' in createopts:
3741 hgvfs.write(b'sharedpath', sharedpath)
3741 hgvfs.write(b'sharedpath', sharedpath)
3742
3742
3743 if createopts.get(b'shareditems'):
3743 if createopts.get(b'shareditems'):
3744 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3744 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3745 hgvfs.write(b'shared', shared)
3745 hgvfs.write(b'shared', shared)
3746
3746
3747
3747
3748 def poisonrepository(repo):
3748 def poisonrepository(repo):
3749 """Poison a repository instance so it can no longer be used."""
3749 """Poison a repository instance so it can no longer be used."""
3750 # Perform any cleanup on the instance.
3750 # Perform any cleanup on the instance.
3751 repo.close()
3751 repo.close()
3752
3752
3753 # Our strategy is to replace the type of the object with one that
3753 # Our strategy is to replace the type of the object with one that
3754 # has all attribute lookups result in error.
3754 # has all attribute lookups result in error.
3755 #
3755 #
3756 # But we have to allow the close() method because some constructors
3756 # But we have to allow the close() method because some constructors
3757 # of repos call close() on repo references.
3757 # of repos call close() on repo references.
3758 class poisonedrepository(object):
3758 class poisonedrepository(object):
3759 def __getattribute__(self, item):
3759 def __getattribute__(self, item):
3760 if item == 'close':
3760 if item == 'close':
3761 return object.__getattribute__(self, item)
3761 return object.__getattribute__(self, item)
3762
3762
3763 raise error.ProgrammingError(
3763 raise error.ProgrammingError(
3764 b'repo instances should not be used after unshare'
3764 b'repo instances should not be used after unshare'
3765 )
3765 )
3766
3766
3767 def close(self):
3767 def close(self):
3768 pass
3768 pass
3769
3769
3770 # We may have a repoview, which intercepts __setattr__. So be sure
3770 # We may have a repoview, which intercepts __setattr__. So be sure
3771 # we operate at the lowest level possible.
3771 # we operate at the lowest level possible.
3772 object.__setattr__(repo, '__class__', poisonedrepository)
3772 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,922 +1,908 b''
1 # coding: utf-8
1 # coding: utf-8
2 # metadata.py -- code related to various metadata computation and access.
2 # metadata.py -- code related to various metadata computation and access.
3 #
3 #
4 # Copyright 2019 Google, Inc <martinvonz@google.com>
4 # Copyright 2019 Google, Inc <martinvonz@google.com>
5 # Copyright 2020 Pierre-Yves David <pierre-yves.david@octobus.net>
5 # Copyright 2020 Pierre-Yves David <pierre-yves.david@octobus.net>
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 from __future__ import absolute_import, print_function
9 from __future__ import absolute_import, print_function
10
10
11 import multiprocessing
11 import multiprocessing
12 import struct
12 import struct
13
13
14 from .node import nullrev
14 from .node import nullrev
15 from . import (
15 from . import (
16 error,
16 error,
17 requirements as requirementsmod,
18 util,
17 util,
19 )
18 )
20
19
21 from .revlogutils import (
20 from .revlogutils import (
22 constants as revlogconst,
23 flagutil as sidedataflag,
21 flagutil as sidedataflag,
24 sidedata as sidedatamod,
22 sidedata as sidedatamod,
25 )
23 )
26
24
27
25
28 class ChangingFiles(object):
26 class ChangingFiles(object):
29 """A class recording the changes made to files by a changeset
27 """A class recording the changes made to files by a changeset
30
28
31 Actions performed on files are gathered into 3 sets:
29 Actions performed on files are gathered into 3 sets:
32
30
33 - added: files actively added in the changeset.
31 - added: files actively added in the changeset.
34 - merged: files whose history got merged
32 - merged: files whose history got merged
35 - removed: files removed in the revision
33 - removed: files removed in the revision
36 - salvaged: files that might have been deleted by a merge but were not
34 - salvaged: files that might have been deleted by a merge but were not
37 - touched: files affected by the merge
35 - touched: files affected by the merge
38
36
39 and copies information is held by 2 mappings
37 and copies information is held by 2 mappings
40
38
41 - copied_from_p1: {"<new-name>": "<source-name-in-p1>"} mapping for copies
39 - copied_from_p1: {"<new-name>": "<source-name-in-p1>"} mapping for copies
42 - copied_from_p2: {"<new-name>": "<source-name-in-p2>"} mapping for copies
40 - copied_from_p2: {"<new-name>": "<source-name-in-p2>"} mapping for copies
43
41
44 See their inline help for details.
42 See their inline help for details.
45 """
43 """
46
44
47 def __init__(
45 def __init__(
48 self,
46 self,
49 touched=None,
47 touched=None,
50 added=None,
48 added=None,
51 removed=None,
49 removed=None,
52 merged=None,
50 merged=None,
53 salvaged=None,
51 salvaged=None,
54 p1_copies=None,
52 p1_copies=None,
55 p2_copies=None,
53 p2_copies=None,
56 ):
54 ):
57 self._added = set(() if added is None else added)
55 self._added = set(() if added is None else added)
58 self._merged = set(() if merged is None else merged)
56 self._merged = set(() if merged is None else merged)
59 self._removed = set(() if removed is None else removed)
57 self._removed = set(() if removed is None else removed)
60 self._touched = set(() if touched is None else touched)
58 self._touched = set(() if touched is None else touched)
61 self._salvaged = set(() if salvaged is None else salvaged)
59 self._salvaged = set(() if salvaged is None else salvaged)
62 self._touched.update(self._added)
60 self._touched.update(self._added)
63 self._touched.update(self._merged)
61 self._touched.update(self._merged)
64 self._touched.update(self._removed)
62 self._touched.update(self._removed)
65 self._p1_copies = dict(() if p1_copies is None else p1_copies)
63 self._p1_copies = dict(() if p1_copies is None else p1_copies)
66 self._p2_copies = dict(() if p2_copies is None else p2_copies)
64 self._p2_copies = dict(() if p2_copies is None else p2_copies)
67
65
68 def __eq__(self, other):
66 def __eq__(self, other):
69 return (
67 return (
70 self.added == other.added
68 self.added == other.added
71 and self.merged == other.merged
69 and self.merged == other.merged
72 and self.removed == other.removed
70 and self.removed == other.removed
73 and self.salvaged == other.salvaged
71 and self.salvaged == other.salvaged
74 and self.touched == other.touched
72 and self.touched == other.touched
75 and self.copied_from_p1 == other.copied_from_p1
73 and self.copied_from_p1 == other.copied_from_p1
76 and self.copied_from_p2 == other.copied_from_p2
74 and self.copied_from_p2 == other.copied_from_p2
77 )
75 )
78
76
79 @property
77 @property
80 def has_copies_info(self):
78 def has_copies_info(self):
81 return bool(
79 return bool(
82 self.removed
80 self.removed
83 or self.merged
81 or self.merged
84 or self.salvaged
82 or self.salvaged
85 or self.copied_from_p1
83 or self.copied_from_p1
86 or self.copied_from_p2
84 or self.copied_from_p2
87 )
85 )
88
86
89 @util.propertycache
87 @util.propertycache
90 def added(self):
88 def added(self):
91 """files actively added in the changeset
89 """files actively added in the changeset
92
90
93 Any file present in that revision that was absent in all the changeset's
91 Any file present in that revision that was absent in all the changeset's
94 parents.
92 parents.
95
93
96 In case of merge, this means a file absent in one of the parents but
94 In case of merge, this means a file absent in one of the parents but
97 existing in the other will *not* be contained in this set. (They were
95 existing in the other will *not* be contained in this set. (They were
98 added by an ancestor)
96 added by an ancestor)
99 """
97 """
100 return frozenset(self._added)
98 return frozenset(self._added)
101
99
102 def mark_added(self, filename):
100 def mark_added(self, filename):
103 if 'added' in vars(self):
101 if 'added' in vars(self):
104 del self.added
102 del self.added
105 self._added.add(filename)
103 self._added.add(filename)
106 self.mark_touched(filename)
104 self.mark_touched(filename)
107
105
108 def update_added(self, filenames):
106 def update_added(self, filenames):
109 for f in filenames:
107 for f in filenames:
110 self.mark_added(f)
108 self.mark_added(f)
111
109
112 @util.propertycache
110 @util.propertycache
113 def merged(self):
111 def merged(self):
114 """files actively merged during a merge
112 """files actively merged during a merge
115
113
116 Any modified files which had modification on both size that needed merging.
114 Any modified files which had modification on both size that needed merging.
117
115
118 In this case a new filenode was created and it has two parents.
116 In this case a new filenode was created and it has two parents.
119 """
117 """
120 return frozenset(self._merged)
118 return frozenset(self._merged)
121
119
122 def mark_merged(self, filename):
120 def mark_merged(self, filename):
123 if 'merged' in vars(self):
121 if 'merged' in vars(self):
124 del self.merged
122 del self.merged
125 self._merged.add(filename)
123 self._merged.add(filename)
126 self.mark_touched(filename)
124 self.mark_touched(filename)
127
125
128 def update_merged(self, filenames):
126 def update_merged(self, filenames):
129 for f in filenames:
127 for f in filenames:
130 self.mark_merged(f)
128 self.mark_merged(f)
131
129
132 @util.propertycache
130 @util.propertycache
133 def removed(self):
131 def removed(self):
134 """files actively removed by the changeset
132 """files actively removed by the changeset
135
133
136 In case of merge this will only contain the set of files removing "new"
134 In case of merge this will only contain the set of files removing "new"
137 content. For any file absent in the current changeset:
135 content. For any file absent in the current changeset:
138
136
139 a) If the file exists in both parents, it is clearly "actively" removed
137 a) If the file exists in both parents, it is clearly "actively" removed
140 by this changeset.
138 by this changeset.
141
139
142 b) If a file exists in only one parent and in none of the common
140 b) If a file exists in only one parent and in none of the common
143 ancestors, then the file was newly added in one of the merged branches
141 ancestors, then the file was newly added in one of the merged branches
144 and then got "actively" removed.
142 and then got "actively" removed.
145
143
146 c) If a file exists in only one parent and at least one of the common
144 c) If a file exists in only one parent and at least one of the common
147 ancestors using the same filenode, then the file was unchanged on one
145 ancestors using the same filenode, then the file was unchanged on one
148 side and deleted on the other side. The merge "passively" propagated
146 side and deleted on the other side. The merge "passively" propagated
149 that deletion, but didn't "actively" remove the file. In this case the
147 that deletion, but didn't "actively" remove the file. In this case the
150 file is *not* included in the `removed` set.
148 file is *not* included in the `removed` set.
151
149
152 d) If a file exists in only one parent and at least one of the common
150 d) If a file exists in only one parent and at least one of the common
153 ancestors using a different filenode, then the file was changed on one
151 ancestors using a different filenode, then the file was changed on one
154 side and removed on the other side. The merge process "actively"
152 side and removed on the other side. The merge process "actively"
155 decided to drop the new change and delete the file. Unlike in the
153 decided to drop the new change and delete the file. Unlike in the
156 previous case, (c), the file included in the `removed` set.
154 previous case, (c), the file included in the `removed` set.
157
155
158 Summary table for merge:
156 Summary table for merge:
159
157
160 case | exists in parents | exists in gca || removed
158 case | exists in parents | exists in gca || removed
161 (a) | both | * || yes
159 (a) | both | * || yes
162 (b) | one | none || yes
160 (b) | one | none || yes
163 (c) | one | same filenode || no
161 (c) | one | same filenode || no
164 (d) | one | new filenode || yes
162 (d) | one | new filenode || yes
165 """
163 """
166 return frozenset(self._removed)
164 return frozenset(self._removed)
167
165
168 def mark_removed(self, filename):
166 def mark_removed(self, filename):
169 if 'removed' in vars(self):
167 if 'removed' in vars(self):
170 del self.removed
168 del self.removed
171 self._removed.add(filename)
169 self._removed.add(filename)
172 self.mark_touched(filename)
170 self.mark_touched(filename)
173
171
174 def update_removed(self, filenames):
172 def update_removed(self, filenames):
175 for f in filenames:
173 for f in filenames:
176 self.mark_removed(f)
174 self.mark_removed(f)
177
175
178 @util.propertycache
176 @util.propertycache
179 def salvaged(self):
177 def salvaged(self):
180 """files that might have been deleted by a merge, but still exists.
178 """files that might have been deleted by a merge, but still exists.
181
179
182 During a merge, the manifest merging might select some files for
180 During a merge, the manifest merging might select some files for
183 removal, or for a removed/changed conflict. If at commit time the file
181 removal, or for a removed/changed conflict. If at commit time the file
184 still exists, its removal was "reverted" and the file is "salvaged"
182 still exists, its removal was "reverted" and the file is "salvaged"
185 """
183 """
186 return frozenset(self._salvaged)
184 return frozenset(self._salvaged)
187
185
188 def mark_salvaged(self, filename):
186 def mark_salvaged(self, filename):
189 if "salvaged" in vars(self):
187 if "salvaged" in vars(self):
190 del self.salvaged
188 del self.salvaged
191 self._salvaged.add(filename)
189 self._salvaged.add(filename)
192 self.mark_touched(filename)
190 self.mark_touched(filename)
193
191
194 def update_salvaged(self, filenames):
192 def update_salvaged(self, filenames):
195 for f in filenames:
193 for f in filenames:
196 self.mark_salvaged(f)
194 self.mark_salvaged(f)
197
195
198 @util.propertycache
196 @util.propertycache
199 def touched(self):
197 def touched(self):
200 """files either actively modified, added or removed"""
198 """files either actively modified, added or removed"""
201 return frozenset(self._touched)
199 return frozenset(self._touched)
202
200
203 def mark_touched(self, filename):
201 def mark_touched(self, filename):
204 if 'touched' in vars(self):
202 if 'touched' in vars(self):
205 del self.touched
203 del self.touched
206 self._touched.add(filename)
204 self._touched.add(filename)
207
205
208 def update_touched(self, filenames):
206 def update_touched(self, filenames):
209 for f in filenames:
207 for f in filenames:
210 self.mark_touched(f)
208 self.mark_touched(f)
211
209
212 @util.propertycache
210 @util.propertycache
213 def copied_from_p1(self):
211 def copied_from_p1(self):
214 return self._p1_copies.copy()
212 return self._p1_copies.copy()
215
213
216 def mark_copied_from_p1(self, source, dest):
214 def mark_copied_from_p1(self, source, dest):
217 if 'copied_from_p1' in vars(self):
215 if 'copied_from_p1' in vars(self):
218 del self.copied_from_p1
216 del self.copied_from_p1
219 self._p1_copies[dest] = source
217 self._p1_copies[dest] = source
220
218
221 def update_copies_from_p1(self, copies):
219 def update_copies_from_p1(self, copies):
222 for dest, source in copies.items():
220 for dest, source in copies.items():
223 self.mark_copied_from_p1(source, dest)
221 self.mark_copied_from_p1(source, dest)
224
222
225 @util.propertycache
223 @util.propertycache
226 def copied_from_p2(self):
224 def copied_from_p2(self):
227 return self._p2_copies.copy()
225 return self._p2_copies.copy()
228
226
229 def mark_copied_from_p2(self, source, dest):
227 def mark_copied_from_p2(self, source, dest):
230 if 'copied_from_p2' in vars(self):
228 if 'copied_from_p2' in vars(self):
231 del self.copied_from_p2
229 del self.copied_from_p2
232 self._p2_copies[dest] = source
230 self._p2_copies[dest] = source
233
231
234 def update_copies_from_p2(self, copies):
232 def update_copies_from_p2(self, copies):
235 for dest, source in copies.items():
233 for dest, source in copies.items():
236 self.mark_copied_from_p2(source, dest)
234 self.mark_copied_from_p2(source, dest)
237
235
238
236
239 def compute_all_files_changes(ctx):
237 def compute_all_files_changes(ctx):
240 """compute the files changed by a revision"""
238 """compute the files changed by a revision"""
241 p1 = ctx.p1()
239 p1 = ctx.p1()
242 p2 = ctx.p2()
240 p2 = ctx.p2()
243 if p1.rev() == nullrev and p2.rev() == nullrev:
241 if p1.rev() == nullrev and p2.rev() == nullrev:
244 return _process_root(ctx)
242 return _process_root(ctx)
245 elif p1.rev() != nullrev and p2.rev() == nullrev:
243 elif p1.rev() != nullrev and p2.rev() == nullrev:
246 return _process_linear(p1, ctx)
244 return _process_linear(p1, ctx)
247 elif p1.rev() == nullrev and p2.rev() != nullrev:
245 elif p1.rev() == nullrev and p2.rev() != nullrev:
248 # In the wild, one can encounter changeset where p1 is null but p2 is not
246 # In the wild, one can encounter changeset where p1 is null but p2 is not
249 return _process_linear(p1, ctx, parent=2)
247 return _process_linear(p1, ctx, parent=2)
250 elif p1.rev() == p2.rev():
248 elif p1.rev() == p2.rev():
251 # In the wild, one can encounter such "non-merge"
249 # In the wild, one can encounter such "non-merge"
252 return _process_linear(p1, ctx)
250 return _process_linear(p1, ctx)
253 else:
251 else:
254 return _process_merge(p1, p2, ctx)
252 return _process_merge(p1, p2, ctx)
255
253
256
254
257 def _process_root(ctx):
255 def _process_root(ctx):
258 """compute the appropriate changed files for a changeset with no parents"""
256 """compute the appropriate changed files for a changeset with no parents"""
259 # Simple, there was nothing before it, so everything is added.
257 # Simple, there was nothing before it, so everything is added.
260 md = ChangingFiles()
258 md = ChangingFiles()
261 manifest = ctx.manifest()
259 manifest = ctx.manifest()
262 for filename in manifest:
260 for filename in manifest:
263 md.mark_added(filename)
261 md.mark_added(filename)
264 return md
262 return md
265
263
266
264
267 def _process_linear(parent_ctx, children_ctx, parent=1):
265 def _process_linear(parent_ctx, children_ctx, parent=1):
268 """compute the appropriate changed files for a changeset with a single parent"""
266 """compute the appropriate changed files for a changeset with a single parent"""
269 md = ChangingFiles()
267 md = ChangingFiles()
270 parent_manifest = parent_ctx.manifest()
268 parent_manifest = parent_ctx.manifest()
271 children_manifest = children_ctx.manifest()
269 children_manifest = children_ctx.manifest()
272
270
273 copies_candidate = []
271 copies_candidate = []
274
272
275 for filename, d in parent_manifest.diff(children_manifest).items():
273 for filename, d in parent_manifest.diff(children_manifest).items():
276 if d[1][0] is None:
274 if d[1][0] is None:
277 # no filenode for the "new" value, file is absent
275 # no filenode for the "new" value, file is absent
278 md.mark_removed(filename)
276 md.mark_removed(filename)
279 else:
277 else:
280 copies_candidate.append(filename)
278 copies_candidate.append(filename)
281 if d[0][0] is None:
279 if d[0][0] is None:
282 # not filenode for the "old" value file was absent
280 # not filenode for the "old" value file was absent
283 md.mark_added(filename)
281 md.mark_added(filename)
284 else:
282 else:
285 # filenode for both "old" and "new"
283 # filenode for both "old" and "new"
286 md.mark_touched(filename)
284 md.mark_touched(filename)
287
285
288 if parent == 1:
286 if parent == 1:
289 copied = md.mark_copied_from_p1
287 copied = md.mark_copied_from_p1
290 elif parent == 2:
288 elif parent == 2:
291 copied = md.mark_copied_from_p2
289 copied = md.mark_copied_from_p2
292 else:
290 else:
293 assert False, "bad parent value %d" % parent
291 assert False, "bad parent value %d" % parent
294
292
295 for filename in copies_candidate:
293 for filename in copies_candidate:
296 copy_info = children_ctx[filename].renamed()
294 copy_info = children_ctx[filename].renamed()
297 if copy_info:
295 if copy_info:
298 source, srcnode = copy_info
296 source, srcnode = copy_info
299 copied(source, filename)
297 copied(source, filename)
300
298
301 return md
299 return md
302
300
303
301
304 def _process_merge(p1_ctx, p2_ctx, ctx):
302 def _process_merge(p1_ctx, p2_ctx, ctx):
305 """compute the appropriate changed files for a changeset with two parents
303 """compute the appropriate changed files for a changeset with two parents
306
304
307 This is a more advance case. The information we need to record is summarise
305 This is a more advance case. The information we need to record is summarise
308 in the following table:
306 in the following table:
309
307
310 ┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
308 ┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
311 │ diff ╲ diff │ ø │ (Some, None) │ (None, Some) │ (Some, Some) │
309 │ diff ╲ diff │ ø │ (Some, None) │ (None, Some) │ (Some, Some) │
312 │ p2 ╲ p1 │ │ │ │ │
310 │ p2 ╲ p1 │ │ │ │ │
313 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
311 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
314 │ │ │🄱 No Changes │🄳 No Changes │ │
312 │ │ │🄱 No Changes │🄳 No Changes │ │
315 │ ø │🄰 No Changes │ OR │ OR │🄵 No Changes │
313 │ ø │🄰 No Changes │ OR │ OR │🄵 No Changes │
316 │ │ │🄲 Deleted[1] │🄴 Salvaged[2]│ [3] │
314 │ │ │🄲 Deleted[1] │🄴 Salvaged[2]│ [3] │
317 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
315 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
318 │ │🄶 No Changes │ │ │ │
316 │ │🄶 No Changes │ │ │ │
319 │ (Some, None) │ OR │🄻 Deleted │ ø │ ø │
317 │ (Some, None) │ OR │🄻 Deleted │ ø │ ø │
320 │ │🄷 Deleted[1] │ │ │ │
318 │ │🄷 Deleted[1] │ │ │ │
321 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
319 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
322 │ │🄸 No Changes │ │ │ 🄽 Touched │
320 │ │🄸 No Changes │ │ │ 🄽 Touched │
323 │ (None, Some) │ OR │ ø │🄼 Added │OR 🅀 Salvaged │
321 │ (None, Some) │ OR │ ø │🄼 Added │OR 🅀 Salvaged │
324 │ │🄹 Salvaged[2]│ │ (copied?) │ (copied?) │
322 │ │🄹 Salvaged[2]│ │ (copied?) │ (copied?) │
325 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
323 ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
326 │ │ │ │ 🄾 Touched │ 🄿 Merged │
324 │ │ │ │ 🄾 Touched │ 🄿 Merged │
327 │ (Some, Some) │🄺 No Changes │ ø │OR 🅁 Salvaged │OR 🅂 Touched │
325 │ (Some, Some) │🄺 No Changes │ ø │OR 🅁 Salvaged │OR 🅂 Touched │
328 │ │ [3] │ │ (copied?) │ (copied?) │
326 │ │ [3] │ │ (copied?) │ (copied?) │
329 └──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
327 └──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
330
328
331 Special case [1]:
329 Special case [1]:
332
330
333 The situation is:
331 The situation is:
334 - parent-A: file exists,
332 - parent-A: file exists,
335 - parent-B: no file,
333 - parent-B: no file,
336 - working-copy: no file.
334 - working-copy: no file.
337
335
338 Detecting a "deletion" will depend on the presence of actual change on
336 Detecting a "deletion" will depend on the presence of actual change on
339 the "parent-A" branch:
337 the "parent-A" branch:
340
338
341 Subcase 🄱 or 🄶 : if the state of the file in "parent-A" is unchanged
339 Subcase 🄱 or 🄶 : if the state of the file in "parent-A" is unchanged
342 compared to the merge ancestors, then parent-A branch left the file
340 compared to the merge ancestors, then parent-A branch left the file
343 untouched while parent-B deleted it. We simply apply the change from
341 untouched while parent-B deleted it. We simply apply the change from
344 "parent-B" branch the file was automatically dropped.
342 "parent-B" branch the file was automatically dropped.
345 The result is:
343 The result is:
346 - file is not recorded as touched by the merge.
344 - file is not recorded as touched by the merge.
347
345
348 Subcase 🄲 or 🄷 : otherwise, the change from parent-A branch were explicitly dropped and
346 Subcase 🄲 or 🄷 : otherwise, the change from parent-A branch were explicitly dropped and
349 the file was "deleted again". From a user perspective, the message
347 the file was "deleted again". From a user perspective, the message
350 about "locally changed" while "remotely deleted" (or the other way
348 about "locally changed" while "remotely deleted" (or the other way
351 around) was issued and the user chose to deleted the file.
349 around) was issued and the user chose to deleted the file.
352 The result:
350 The result:
353 - file is recorded as touched by the merge.
351 - file is recorded as touched by the merge.
354
352
355
353
356 Special case [2]:
354 Special case [2]:
357
355
358 The situation is:
356 The situation is:
359 - parent-A: no file,
357 - parent-A: no file,
360 - parent-B: file,
358 - parent-B: file,
361 - working-copy: file (same content as parent-B).
359 - working-copy: file (same content as parent-B).
362
360
363 There are three subcases depending on the ancestors contents:
361 There are three subcases depending on the ancestors contents:
364
362
365 - A) the file is missing in all ancestors,
363 - A) the file is missing in all ancestors,
366 - B) at least one ancestor has the file with filenode ≠ from parent-B,
364 - B) at least one ancestor has the file with filenode ≠ from parent-B,
367 - C) all ancestors use the same filenode as parent-B,
365 - C) all ancestors use the same filenode as parent-B,
368
366
369 Subcase (A) is the simpler, nothing happend on parent-A side while
367 Subcase (A) is the simpler, nothing happend on parent-A side while
370 parent-B added it.
368 parent-B added it.
371
369
372 The result:
370 The result:
373 - the file is not marked as touched by the merge.
371 - the file is not marked as touched by the merge.
374
372
375 Subcase (B) is the counter part of "Special case [1]", the file was
373 Subcase (B) is the counter part of "Special case [1]", the file was
376 modified on parent-B side, while parent-A side deleted it. However this
374 modified on parent-B side, while parent-A side deleted it. However this
377 time, the conflict was solved by keeping the file (and its
375 time, the conflict was solved by keeping the file (and its
378 modification). We consider the file as "salvaged".
376 modification). We consider the file as "salvaged".
379
377
380 The result:
378 The result:
381 - the file is marked as "salvaged" by the merge.
379 - the file is marked as "salvaged" by the merge.
382
380
383 Subcase (C) is subtle variation of the case above. In this case, the
381 Subcase (C) is subtle variation of the case above. In this case, the
384 file in unchanged on the parent-B side and actively removed on the
382 file in unchanged on the parent-B side and actively removed on the
385 parent-A side. So the merge machinery correctly decide it should be
383 parent-A side. So the merge machinery correctly decide it should be
386 removed. However, the file was explicitly restored to its parent-B
384 removed. However, the file was explicitly restored to its parent-B
387 content before the merge was commited. The file is be marked
385 content before the merge was commited. The file is be marked
388 as salvaged too. From the merge result perspective, this is similar to
386 as salvaged too. From the merge result perspective, this is similar to
389 Subcase (B), however from the merge resolution perspective they differ
387 Subcase (B), however from the merge resolution perspective they differ
390 since in (C), there was some conflict not obvious solution to the
388 since in (C), there was some conflict not obvious solution to the
391 merge (That got reversed)
389 merge (That got reversed)
392
390
393 Special case [3]:
391 Special case [3]:
394
392
395 The situation is:
393 The situation is:
396 - parent-A: file,
394 - parent-A: file,
397 - parent-B: file (different filenode as parent-A),
395 - parent-B: file (different filenode as parent-A),
398 - working-copy: file (same filenode as parent-B).
396 - working-copy: file (same filenode as parent-B).
399
397
400 This case is in theory much simple, for this to happens, this mean the
398 This case is in theory much simple, for this to happens, this mean the
401 filenode in parent-A is purely replacing the one in parent-B (either a
399 filenode in parent-A is purely replacing the one in parent-B (either a
402 descendant, or a full new file history, see changeset). So the merge
400 descendant, or a full new file history, see changeset). So the merge
403 introduce no changes, and the file is not affected by the merge...
401 introduce no changes, and the file is not affected by the merge...
404
402
405 However, in the wild it is possible to find commit with the above is not
403 However, in the wild it is possible to find commit with the above is not
406 True. For example repository have some commit where the *new* node is an
404 True. For example repository have some commit where the *new* node is an
407 ancestor of the node in parent-A, or where parent-A and parent-B are two
405 ancestor of the node in parent-A, or where parent-A and parent-B are two
408 branches of the same file history, yet not merge-filenode were created
406 branches of the same file history, yet not merge-filenode were created
409 (while the "merge" should have led to a "modification").
407 (while the "merge" should have led to a "modification").
410
408
411 Detecting such cases (and not recording the file as modified) would be a
409 Detecting such cases (and not recording the file as modified) would be a
412 nice bonus. However do not any of this yet.
410 nice bonus. However do not any of this yet.
413 """
411 """
414
412
415 repo = ctx.repo()
413 repo = ctx.repo()
416 md = ChangingFiles()
414 md = ChangingFiles()
417
415
418 m = ctx.manifest()
416 m = ctx.manifest()
419 p1m = p1_ctx.manifest()
417 p1m = p1_ctx.manifest()
420 p2m = p2_ctx.manifest()
418 p2m = p2_ctx.manifest()
421 diff_p1 = p1m.diff(m)
419 diff_p1 = p1m.diff(m)
422 diff_p2 = p2m.diff(m)
420 diff_p2 = p2m.diff(m)
423
421
424 cahs = ctx.repo().changelog.commonancestorsheads(
422 cahs = ctx.repo().changelog.commonancestorsheads(
425 p1_ctx.node(), p2_ctx.node()
423 p1_ctx.node(), p2_ctx.node()
426 )
424 )
427 if not cahs:
425 if not cahs:
428 cahs = [nullrev]
426 cahs = [nullrev]
429 mas = [ctx.repo()[r].manifest() for r in cahs]
427 mas = [ctx.repo()[r].manifest() for r in cahs]
430
428
431 copy_candidates = []
429 copy_candidates = []
432
430
433 # Dealing with case 🄰 happens automatically. Since there are no entry in
431 # Dealing with case 🄰 happens automatically. Since there are no entry in
434 # d1 nor d2, we won't iterate on it ever.
432 # d1 nor d2, we won't iterate on it ever.
435
433
436 # Iteration over d1 content will deal with all cases, but the one in the
434 # Iteration over d1 content will deal with all cases, but the one in the
437 # first column of the table.
435 # first column of the table.
438 for filename, d1 in diff_p1.items():
436 for filename, d1 in diff_p1.items():
439
437
440 d2 = diff_p2.pop(filename, None)
438 d2 = diff_p2.pop(filename, None)
441
439
442 if d2 is None:
440 if d2 is None:
443 # this deal with the first line of the table.
441 # this deal with the first line of the table.
444 _process_other_unchanged(md, mas, filename, d1)
442 _process_other_unchanged(md, mas, filename, d1)
445 else:
443 else:
446
444
447 if d1[0][0] is None and d2[0][0] is None:
445 if d1[0][0] is None and d2[0][0] is None:
448 # case 🄼 — both deleted the file.
446 # case 🄼 — both deleted the file.
449 md.mark_added(filename)
447 md.mark_added(filename)
450 copy_candidates.append(filename)
448 copy_candidates.append(filename)
451 elif d1[1][0] is None and d2[1][0] is None:
449 elif d1[1][0] is None and d2[1][0] is None:
452 # case 🄻 — both deleted the file.
450 # case 🄻 — both deleted the file.
453 md.mark_removed(filename)
451 md.mark_removed(filename)
454 elif d1[1][0] is not None and d2[1][0] is not None:
452 elif d1[1][0] is not None and d2[1][0] is not None:
455 if d1[0][0] is None or d2[0][0] is None:
453 if d1[0][0] is None or d2[0][0] is None:
456 if any(_find(ma, filename) is not None for ma in mas):
454 if any(_find(ma, filename) is not None for ma in mas):
457 # case 🅀 or 🅁
455 # case 🅀 or 🅁
458 md.mark_salvaged(filename)
456 md.mark_salvaged(filename)
459 else:
457 else:
460 # case 🄽 🄾 : touched
458 # case 🄽 🄾 : touched
461 md.mark_touched(filename)
459 md.mark_touched(filename)
462 else:
460 else:
463 fctx = repo.filectx(filename, fileid=d1[1][0])
461 fctx = repo.filectx(filename, fileid=d1[1][0])
464 if fctx.p2().rev() == nullrev:
462 if fctx.p2().rev() == nullrev:
465 # case 🅂
463 # case 🅂
466 # lets assume we can trust the file history. If the
464 # lets assume we can trust the file history. If the
467 # filenode is not a merge, the file was not merged.
465 # filenode is not a merge, the file was not merged.
468 md.mark_touched(filename)
466 md.mark_touched(filename)
469 else:
467 else:
470 # case 🄿
468 # case 🄿
471 md.mark_merged(filename)
469 md.mark_merged(filename)
472 copy_candidates.append(filename)
470 copy_candidates.append(filename)
473 else:
471 else:
474 # Impossible case, the post-merge file status cannot be None on
472 # Impossible case, the post-merge file status cannot be None on
475 # one side and Something on the other side.
473 # one side and Something on the other side.
476 assert False, "unreachable"
474 assert False, "unreachable"
477
475
478 # Iteration over remaining d2 content deal with the first column of the
476 # Iteration over remaining d2 content deal with the first column of the
479 # table.
477 # table.
480 for filename, d2 in diff_p2.items():
478 for filename, d2 in diff_p2.items():
481 _process_other_unchanged(md, mas, filename, d2)
479 _process_other_unchanged(md, mas, filename, d2)
482
480
483 for filename in copy_candidates:
481 for filename in copy_candidates:
484 copy_info = ctx[filename].renamed()
482 copy_info = ctx[filename].renamed()
485 if copy_info:
483 if copy_info:
486 source, srcnode = copy_info
484 source, srcnode = copy_info
487 if source in p1_ctx and p1_ctx[source].filenode() == srcnode:
485 if source in p1_ctx and p1_ctx[source].filenode() == srcnode:
488 md.mark_copied_from_p1(source, filename)
486 md.mark_copied_from_p1(source, filename)
489 elif source in p2_ctx and p2_ctx[source].filenode() == srcnode:
487 elif source in p2_ctx and p2_ctx[source].filenode() == srcnode:
490 md.mark_copied_from_p2(source, filename)
488 md.mark_copied_from_p2(source, filename)
491 return md
489 return md
492
490
493
491
494 def _find(manifest, filename):
492 def _find(manifest, filename):
495 """return the associate filenode or None"""
493 """return the associate filenode or None"""
496 if filename not in manifest:
494 if filename not in manifest:
497 return None
495 return None
498 return manifest.find(filename)[0]
496 return manifest.find(filename)[0]
499
497
500
498
501 def _process_other_unchanged(md, mas, filename, diff):
499 def _process_other_unchanged(md, mas, filename, diff):
502 source_node = diff[0][0]
500 source_node = diff[0][0]
503 target_node = diff[1][0]
501 target_node = diff[1][0]
504
502
505 if source_node is not None and target_node is None:
503 if source_node is not None and target_node is None:
506 if any(not _find(ma, filename) == source_node for ma in mas):
504 if any(not _find(ma, filename) == source_node for ma in mas):
507 # case 🄲 of 🄷
505 # case 🄲 of 🄷
508 md.mark_removed(filename)
506 md.mark_removed(filename)
509 # else, we have case 🄱 or 🄶 : no change need to be recorded
507 # else, we have case 🄱 or 🄶 : no change need to be recorded
510 elif source_node is None and target_node is not None:
508 elif source_node is None and target_node is not None:
511 if any(_find(ma, filename) is not None for ma in mas):
509 if any(_find(ma, filename) is not None for ma in mas):
512 # case 🄴 or 🄹
510 # case 🄴 or 🄹
513 md.mark_salvaged(filename)
511 md.mark_salvaged(filename)
514 # else, we have case 🄳 or 🄸 : simple merge without intervention
512 # else, we have case 🄳 or 🄸 : simple merge without intervention
515 elif source_node is not None and target_node is not None:
513 elif source_node is not None and target_node is not None:
516 # case 🄵 or 🄺 : simple merge without intervention
514 # case 🄵 or 🄺 : simple merge without intervention
517 #
515 #
518 # In buggy case where source_node is not an ancestors of target_node.
516 # In buggy case where source_node is not an ancestors of target_node.
519 # There should have a been a new filenode created, recording this as
517 # There should have a been a new filenode created, recording this as
520 # "modified". We do not deal with them yet.
518 # "modified". We do not deal with them yet.
521 pass
519 pass
522 else:
520 else:
523 # An impossible case, the diff algorithm should not return entry if the
521 # An impossible case, the diff algorithm should not return entry if the
524 # file is missing on both side.
522 # file is missing on both side.
525 assert False, "unreachable"
523 assert False, "unreachable"
526
524
527
525
528 def _missing_from_all_ancestors(mas, filename):
526 def _missing_from_all_ancestors(mas, filename):
529 return all(_find(ma, filename) is None for ma in mas)
527 return all(_find(ma, filename) is None for ma in mas)
530
528
531
529
532 def computechangesetfilesadded(ctx):
530 def computechangesetfilesadded(ctx):
533 """return the list of files added in a changeset"""
531 """return the list of files added in a changeset"""
534 added = []
532 added = []
535 for f in ctx.files():
533 for f in ctx.files():
536 if not any(f in p for p in ctx.parents()):
534 if not any(f in p for p in ctx.parents()):
537 added.append(f)
535 added.append(f)
538 return added
536 return added
539
537
540
538
541 def get_removal_filter(ctx, x=None):
539 def get_removal_filter(ctx, x=None):
542 """return a function to detect files "wrongly" detected as `removed`
540 """return a function to detect files "wrongly" detected as `removed`
543
541
544 When a file is removed relative to p1 in a merge, this
542 When a file is removed relative to p1 in a merge, this
545 function determines whether the absence is due to a
543 function determines whether the absence is due to a
546 deletion from a parent, or whether the merge commit
544 deletion from a parent, or whether the merge commit
547 itself deletes the file. We decide this by doing a
545 itself deletes the file. We decide this by doing a
548 simplified three way merge of the manifest entry for
546 simplified three way merge of the manifest entry for
549 the file. There are two ways we decide the merge
547 the file. There are two ways we decide the merge
550 itself didn't delete a file:
548 itself didn't delete a file:
551 - neither parent (nor the merge) contain the file
549 - neither parent (nor the merge) contain the file
552 - exactly one parent contains the file, and that
550 - exactly one parent contains the file, and that
553 parent has the same filelog entry as the merge
551 parent has the same filelog entry as the merge
554 ancestor (or all of them if there two). In other
552 ancestor (or all of them if there two). In other
555 words, that parent left the file unchanged while the
553 words, that parent left the file unchanged while the
556 other one deleted it.
554 other one deleted it.
557 One way to think about this is that deleting a file is
555 One way to think about this is that deleting a file is
558 similar to emptying it, so the list of changed files
556 similar to emptying it, so the list of changed files
559 should be similar either way. The computation
557 should be similar either way. The computation
560 described above is not done directly in _filecommit
558 described above is not done directly in _filecommit
561 when creating the list of changed files, however
559 when creating the list of changed files, however
562 it does something very similar by comparing filelog
560 it does something very similar by comparing filelog
563 nodes.
561 nodes.
564 """
562 """
565
563
566 if x is not None:
564 if x is not None:
567 p1, p2, m1, m2 = x
565 p1, p2, m1, m2 = x
568 else:
566 else:
569 p1 = ctx.p1()
567 p1 = ctx.p1()
570 p2 = ctx.p2()
568 p2 = ctx.p2()
571 m1 = p1.manifest()
569 m1 = p1.manifest()
572 m2 = p2.manifest()
570 m2 = p2.manifest()
573
571
574 @util.cachefunc
572 @util.cachefunc
575 def mas():
573 def mas():
576 p1n = p1.node()
574 p1n = p1.node()
577 p2n = p2.node()
575 p2n = p2.node()
578 cahs = ctx.repo().changelog.commonancestorsheads(p1n, p2n)
576 cahs = ctx.repo().changelog.commonancestorsheads(p1n, p2n)
579 if not cahs:
577 if not cahs:
580 cahs = [nullrev]
578 cahs = [nullrev]
581 return [ctx.repo()[r].manifest() for r in cahs]
579 return [ctx.repo()[r].manifest() for r in cahs]
582
580
583 def deletionfromparent(f):
581 def deletionfromparent(f):
584 if f in m1:
582 if f in m1:
585 return f not in m2 and all(
583 return f not in m2 and all(
586 f in ma and ma.find(f) == m1.find(f) for ma in mas()
584 f in ma and ma.find(f) == m1.find(f) for ma in mas()
587 )
585 )
588 elif f in m2:
586 elif f in m2:
589 return all(f in ma and ma.find(f) == m2.find(f) for ma in mas())
587 return all(f in ma and ma.find(f) == m2.find(f) for ma in mas())
590 else:
588 else:
591 return True
589 return True
592
590
593 return deletionfromparent
591 return deletionfromparent
594
592
595
593
596 def computechangesetfilesremoved(ctx):
594 def computechangesetfilesremoved(ctx):
597 """return the list of files removed in a changeset"""
595 """return the list of files removed in a changeset"""
598 removed = []
596 removed = []
599 for f in ctx.files():
597 for f in ctx.files():
600 if f not in ctx:
598 if f not in ctx:
601 removed.append(f)
599 removed.append(f)
602 if removed:
600 if removed:
603 rf = get_removal_filter(ctx)
601 rf = get_removal_filter(ctx)
604 removed = [r for r in removed if not rf(r)]
602 removed = [r for r in removed if not rf(r)]
605 return removed
603 return removed
606
604
607
605
608 def computechangesetfilesmerged(ctx):
606 def computechangesetfilesmerged(ctx):
609 """return the list of files merged in a changeset"""
607 """return the list of files merged in a changeset"""
610 merged = []
608 merged = []
611 if len(ctx.parents()) < 2:
609 if len(ctx.parents()) < 2:
612 return merged
610 return merged
613 for f in ctx.files():
611 for f in ctx.files():
614 if f in ctx:
612 if f in ctx:
615 fctx = ctx[f]
613 fctx = ctx[f]
616 parents = fctx._filelog.parents(fctx._filenode)
614 parents = fctx._filelog.parents(fctx._filenode)
617 if parents[1] != ctx.repo().nullid:
615 if parents[1] != ctx.repo().nullid:
618 merged.append(f)
616 merged.append(f)
619 return merged
617 return merged
620
618
621
619
622 def computechangesetcopies(ctx):
620 def computechangesetcopies(ctx):
623 """return the copies data for a changeset
621 """return the copies data for a changeset
624
622
625 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
623 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
626
624
627 Each dictionnary are in the form: `{newname: oldname}`
625 Each dictionnary are in the form: `{newname: oldname}`
628 """
626 """
629 p1copies = {}
627 p1copies = {}
630 p2copies = {}
628 p2copies = {}
631 p1 = ctx.p1()
629 p1 = ctx.p1()
632 p2 = ctx.p2()
630 p2 = ctx.p2()
633 narrowmatch = ctx._repo.narrowmatch()
631 narrowmatch = ctx._repo.narrowmatch()
634 for dst in ctx.files():
632 for dst in ctx.files():
635 if not narrowmatch(dst) or dst not in ctx:
633 if not narrowmatch(dst) or dst not in ctx:
636 continue
634 continue
637 copied = ctx[dst].renamed()
635 copied = ctx[dst].renamed()
638 if not copied:
636 if not copied:
639 continue
637 continue
640 src, srcnode = copied
638 src, srcnode = copied
641 if src in p1 and p1[src].filenode() == srcnode:
639 if src in p1 and p1[src].filenode() == srcnode:
642 p1copies[dst] = src
640 p1copies[dst] = src
643 elif src in p2 and p2[src].filenode() == srcnode:
641 elif src in p2 and p2[src].filenode() == srcnode:
644 p2copies[dst] = src
642 p2copies[dst] = src
645 return p1copies, p2copies
643 return p1copies, p2copies
646
644
647
645
648 def encodecopies(files, copies):
646 def encodecopies(files, copies):
649 items = []
647 items = []
650 for i, dst in enumerate(files):
648 for i, dst in enumerate(files):
651 if dst in copies:
649 if dst in copies:
652 items.append(b'%d\0%s' % (i, copies[dst]))
650 items.append(b'%d\0%s' % (i, copies[dst]))
653 if len(items) != len(copies):
651 if len(items) != len(copies):
654 raise error.ProgrammingError(
652 raise error.ProgrammingError(
655 b'some copy targets missing from file list'
653 b'some copy targets missing from file list'
656 )
654 )
657 return b"\n".join(items)
655 return b"\n".join(items)
658
656
659
657
660 def decodecopies(files, data):
658 def decodecopies(files, data):
661 try:
659 try:
662 copies = {}
660 copies = {}
663 if not data:
661 if not data:
664 return copies
662 return copies
665 for l in data.split(b'\n'):
663 for l in data.split(b'\n'):
666 strindex, src = l.split(b'\0')
664 strindex, src = l.split(b'\0')
667 i = int(strindex)
665 i = int(strindex)
668 dst = files[i]
666 dst = files[i]
669 copies[dst] = src
667 copies[dst] = src
670 return copies
668 return copies
671 except (ValueError, IndexError):
669 except (ValueError, IndexError):
672 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
670 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
673 # used different syntax for the value.
671 # used different syntax for the value.
674 return None
672 return None
675
673
676
674
677 def encodefileindices(files, subset):
675 def encodefileindices(files, subset):
678 subset = set(subset)
676 subset = set(subset)
679 indices = []
677 indices = []
680 for i, f in enumerate(files):
678 for i, f in enumerate(files):
681 if f in subset:
679 if f in subset:
682 indices.append(b'%d' % i)
680 indices.append(b'%d' % i)
683 return b'\n'.join(indices)
681 return b'\n'.join(indices)
684
682
685
683
686 def decodefileindices(files, data):
684 def decodefileindices(files, data):
687 try:
685 try:
688 subset = []
686 subset = []
689 if not data:
687 if not data:
690 return subset
688 return subset
691 for strindex in data.split(b'\n'):
689 for strindex in data.split(b'\n'):
692 i = int(strindex)
690 i = int(strindex)
693 if i < 0 or i >= len(files):
691 if i < 0 or i >= len(files):
694 return None
692 return None
695 subset.append(files[i])
693 subset.append(files[i])
696 return subset
694 return subset
697 except (ValueError, IndexError):
695 except (ValueError, IndexError):
698 # Perhaps someone had chosen the same key name (e.g. "added") and
696 # Perhaps someone had chosen the same key name (e.g. "added") and
699 # used different syntax for the value.
697 # used different syntax for the value.
700 return None
698 return None
701
699
702
700
703 # see mercurial/helptext/internals/revlogs.txt for details about the format
701 # see mercurial/helptext/internals/revlogs.txt for details about the format
704
702
705 ACTION_MASK = int("111" "00", 2)
703 ACTION_MASK = int("111" "00", 2)
706 # note: untouched file used as copy source will as `000` for this mask.
704 # note: untouched file used as copy source will as `000` for this mask.
707 ADDED_FLAG = int("001" "00", 2)
705 ADDED_FLAG = int("001" "00", 2)
708 MERGED_FLAG = int("010" "00", 2)
706 MERGED_FLAG = int("010" "00", 2)
709 REMOVED_FLAG = int("011" "00", 2)
707 REMOVED_FLAG = int("011" "00", 2)
710 SALVAGED_FLAG = int("100" "00", 2)
708 SALVAGED_FLAG = int("100" "00", 2)
711 TOUCHED_FLAG = int("101" "00", 2)
709 TOUCHED_FLAG = int("101" "00", 2)
712
710
713 COPIED_MASK = int("11", 2)
711 COPIED_MASK = int("11", 2)
714 COPIED_FROM_P1_FLAG = int("10", 2)
712 COPIED_FROM_P1_FLAG = int("10", 2)
715 COPIED_FROM_P2_FLAG = int("11", 2)
713 COPIED_FROM_P2_FLAG = int("11", 2)
716
714
717 # structure is <flag><filename-end><copy-source>
715 # structure is <flag><filename-end><copy-source>
718 INDEX_HEADER = struct.Struct(">L")
716 INDEX_HEADER = struct.Struct(">L")
719 INDEX_ENTRY = struct.Struct(">bLL")
717 INDEX_ENTRY = struct.Struct(">bLL")
720
718
721
719
722 def encode_files_sidedata(files):
720 def encode_files_sidedata(files):
723 all_files = set(files.touched)
721 all_files = set(files.touched)
724 all_files.update(files.copied_from_p1.values())
722 all_files.update(files.copied_from_p1.values())
725 all_files.update(files.copied_from_p2.values())
723 all_files.update(files.copied_from_p2.values())
726 all_files = sorted(all_files)
724 all_files = sorted(all_files)
727 file_idx = {f: i for (i, f) in enumerate(all_files)}
725 file_idx = {f: i for (i, f) in enumerate(all_files)}
728 file_idx[None] = 0
726 file_idx[None] = 0
729
727
730 chunks = [INDEX_HEADER.pack(len(all_files))]
728 chunks = [INDEX_HEADER.pack(len(all_files))]
731
729
732 filename_length = 0
730 filename_length = 0
733 for f in all_files:
731 for f in all_files:
734 filename_size = len(f)
732 filename_size = len(f)
735 filename_length += filename_size
733 filename_length += filename_size
736 flag = 0
734 flag = 0
737 if f in files.added:
735 if f in files.added:
738 flag |= ADDED_FLAG
736 flag |= ADDED_FLAG
739 elif f in files.merged:
737 elif f in files.merged:
740 flag |= MERGED_FLAG
738 flag |= MERGED_FLAG
741 elif f in files.removed:
739 elif f in files.removed:
742 flag |= REMOVED_FLAG
740 flag |= REMOVED_FLAG
743 elif f in files.salvaged:
741 elif f in files.salvaged:
744 flag |= SALVAGED_FLAG
742 flag |= SALVAGED_FLAG
745 elif f in files.touched:
743 elif f in files.touched:
746 flag |= TOUCHED_FLAG
744 flag |= TOUCHED_FLAG
747
745
748 copy = None
746 copy = None
749 if f in files.copied_from_p1:
747 if f in files.copied_from_p1:
750 flag |= COPIED_FROM_P1_FLAG
748 flag |= COPIED_FROM_P1_FLAG
751 copy = files.copied_from_p1.get(f)
749 copy = files.copied_from_p1.get(f)
752 elif f in files.copied_from_p2:
750 elif f in files.copied_from_p2:
753 copy = files.copied_from_p2.get(f)
751 copy = files.copied_from_p2.get(f)
754 flag |= COPIED_FROM_P2_FLAG
752 flag |= COPIED_FROM_P2_FLAG
755 copy_idx = file_idx[copy]
753 copy_idx = file_idx[copy]
756 chunks.append(INDEX_ENTRY.pack(flag, filename_length, copy_idx))
754 chunks.append(INDEX_ENTRY.pack(flag, filename_length, copy_idx))
757 chunks.extend(all_files)
755 chunks.extend(all_files)
758 return {sidedatamod.SD_FILES: b''.join(chunks)}
756 return {sidedatamod.SD_FILES: b''.join(chunks)}
759
757
760
758
761 def decode_files_sidedata(sidedata):
759 def decode_files_sidedata(sidedata):
762 md = ChangingFiles()
760 md = ChangingFiles()
763 raw = sidedata.get(sidedatamod.SD_FILES)
761 raw = sidedata.get(sidedatamod.SD_FILES)
764
762
765 if raw is None:
763 if raw is None:
766 return md
764 return md
767
765
768 copies = []
766 copies = []
769 all_files = []
767 all_files = []
770
768
771 assert len(raw) >= INDEX_HEADER.size
769 assert len(raw) >= INDEX_HEADER.size
772 total_files = INDEX_HEADER.unpack_from(raw, 0)[0]
770 total_files = INDEX_HEADER.unpack_from(raw, 0)[0]
773
771
774 offset = INDEX_HEADER.size
772 offset = INDEX_HEADER.size
775 file_offset_base = offset + (INDEX_ENTRY.size * total_files)
773 file_offset_base = offset + (INDEX_ENTRY.size * total_files)
776 file_offset_last = file_offset_base
774 file_offset_last = file_offset_base
777
775
778 assert len(raw) >= file_offset_base
776 assert len(raw) >= file_offset_base
779
777
780 for idx in range(total_files):
778 for idx in range(total_files):
781 flag, file_end, copy_idx = INDEX_ENTRY.unpack_from(raw, offset)
779 flag, file_end, copy_idx = INDEX_ENTRY.unpack_from(raw, offset)
782 file_end += file_offset_base
780 file_end += file_offset_base
783 filename = raw[file_offset_last:file_end]
781 filename = raw[file_offset_last:file_end]
784 filesize = file_end - file_offset_last
782 filesize = file_end - file_offset_last
785 assert len(filename) == filesize
783 assert len(filename) == filesize
786 offset += INDEX_ENTRY.size
784 offset += INDEX_ENTRY.size
787 file_offset_last = file_end
785 file_offset_last = file_end
788 all_files.append(filename)
786 all_files.append(filename)
789 if flag & ACTION_MASK == ADDED_FLAG:
787 if flag & ACTION_MASK == ADDED_FLAG:
790 md.mark_added(filename)
788 md.mark_added(filename)
791 elif flag & ACTION_MASK == MERGED_FLAG:
789 elif flag & ACTION_MASK == MERGED_FLAG:
792 md.mark_merged(filename)
790 md.mark_merged(filename)
793 elif flag & ACTION_MASK == REMOVED_FLAG:
791 elif flag & ACTION_MASK == REMOVED_FLAG:
794 md.mark_removed(filename)
792 md.mark_removed(filename)
795 elif flag & ACTION_MASK == SALVAGED_FLAG:
793 elif flag & ACTION_MASK == SALVAGED_FLAG:
796 md.mark_salvaged(filename)
794 md.mark_salvaged(filename)
797 elif flag & ACTION_MASK == TOUCHED_FLAG:
795 elif flag & ACTION_MASK == TOUCHED_FLAG:
798 md.mark_touched(filename)
796 md.mark_touched(filename)
799
797
800 copied = None
798 copied = None
801 if flag & COPIED_MASK == COPIED_FROM_P1_FLAG:
799 if flag & COPIED_MASK == COPIED_FROM_P1_FLAG:
802 copied = md.mark_copied_from_p1
800 copied = md.mark_copied_from_p1
803 elif flag & COPIED_MASK == COPIED_FROM_P2_FLAG:
801 elif flag & COPIED_MASK == COPIED_FROM_P2_FLAG:
804 copied = md.mark_copied_from_p2
802 copied = md.mark_copied_from_p2
805
803
806 if copied is not None:
804 if copied is not None:
807 copies.append((copied, filename, copy_idx))
805 copies.append((copied, filename, copy_idx))
808
806
809 for copied, filename, copy_idx in copies:
807 for copied, filename, copy_idx in copies:
810 copied(all_files[copy_idx], filename)
808 copied(all_files[copy_idx], filename)
811
809
812 return md
810 return md
813
811
814
812
815 def _getsidedata(srcrepo, rev):
813 def _getsidedata(srcrepo, rev):
816 ctx = srcrepo[rev]
814 ctx = srcrepo[rev]
817 files = compute_all_files_changes(ctx)
815 files = compute_all_files_changes(ctx)
818 return encode_files_sidedata(files), files.has_copies_info
816 return encode_files_sidedata(files), files.has_copies_info
819
817
820
818
821 def copies_sidedata_computer(repo, revlog, rev, existing_sidedata):
819 def copies_sidedata_computer(repo, revlog, rev, existing_sidedata):
822 sidedata, has_copies_info = _getsidedata(repo, rev)
820 sidedata, has_copies_info = _getsidedata(repo, rev)
823 flags_to_add = sidedataflag.REVIDX_HASCOPIESINFO if has_copies_info else 0
821 flags_to_add = sidedataflag.REVIDX_HASCOPIESINFO if has_copies_info else 0
824 return sidedata, (flags_to_add, 0)
822 return sidedata, (flags_to_add, 0)
825
823
826
824
827 def set_sidedata_spec_for_repo(repo):
828 if requirementsmod.COPIESSDC_REQUIREMENT in repo.requirements:
829 repo.register_wanted_sidedata(sidedatamod.SD_FILES)
830 repo.register_sidedata_computer(
831 revlogconst.KIND_CHANGELOG,
832 sidedatamod.SD_FILES,
833 (sidedatamod.SD_FILES,),
834 copies_sidedata_computer,
835 sidedataflag.REVIDX_HASCOPIESINFO,
836 )
837
838
839 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
825 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
840 """The function used by worker precomputing sidedata
826 """The function used by worker precomputing sidedata
841
827
842 It read an input queue containing revision numbers
828 It read an input queue containing revision numbers
843 It write in an output queue containing (rev, <sidedata-map>)
829 It write in an output queue containing (rev, <sidedata-map>)
844
830
845 The `None` input value is used as a stop signal.
831 The `None` input value is used as a stop signal.
846
832
847 The `tokens` semaphore is user to avoid having too many unprocessed
833 The `tokens` semaphore is user to avoid having too many unprocessed
848 entries. The workers needs to acquire one token before fetching a task.
834 entries. The workers needs to acquire one token before fetching a task.
849 They will be released by the consumer of the produced data.
835 They will be released by the consumer of the produced data.
850 """
836 """
851 tokens.acquire()
837 tokens.acquire()
852 rev = revs_queue.get()
838 rev = revs_queue.get()
853 while rev is not None:
839 while rev is not None:
854 data = _getsidedata(srcrepo, rev)
840 data = _getsidedata(srcrepo, rev)
855 sidedata_queue.put((rev, data))
841 sidedata_queue.put((rev, data))
856 tokens.acquire()
842 tokens.acquire()
857 rev = revs_queue.get()
843 rev = revs_queue.get()
858 # processing of `None` is completed, release the token.
844 # processing of `None` is completed, release the token.
859 tokens.release()
845 tokens.release()
860
846
861
847
862 BUFF_PER_WORKER = 50
848 BUFF_PER_WORKER = 50
863
849
864
850
865 def _get_worker_sidedata_adder(srcrepo, destrepo):
851 def _get_worker_sidedata_adder(srcrepo, destrepo):
866 """The parallel version of the sidedata computation
852 """The parallel version of the sidedata computation
867
853
868 This code spawn a pool of worker that precompute a buffer of sidedata
854 This code spawn a pool of worker that precompute a buffer of sidedata
869 before we actually need them"""
855 before we actually need them"""
870 # avoid circular import copies -> scmutil -> worker -> copies
856 # avoid circular import copies -> scmutil -> worker -> copies
871 from . import worker
857 from . import worker
872
858
873 nbworkers = worker._numworkers(srcrepo.ui)
859 nbworkers = worker._numworkers(srcrepo.ui)
874
860
875 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
861 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
876 revsq = multiprocessing.Queue()
862 revsq = multiprocessing.Queue()
877 sidedataq = multiprocessing.Queue()
863 sidedataq = multiprocessing.Queue()
878
864
879 assert srcrepo.filtername is None
865 assert srcrepo.filtername is None
880 # queue all tasks beforehand, revision numbers are small and it make
866 # queue all tasks beforehand, revision numbers are small and it make
881 # synchronisation simpler
867 # synchronisation simpler
882 #
868 #
883 # Since the computation for each node can be quite expensive, the overhead
869 # Since the computation for each node can be quite expensive, the overhead
884 # of using a single queue is not revelant. In practice, most computation
870 # of using a single queue is not revelant. In practice, most computation
885 # are fast but some are very expensive and dominate all the other smaller
871 # are fast but some are very expensive and dominate all the other smaller
886 # cost.
872 # cost.
887 for r in srcrepo.changelog.revs():
873 for r in srcrepo.changelog.revs():
888 revsq.put(r)
874 revsq.put(r)
889 # queue the "no more tasks" markers
875 # queue the "no more tasks" markers
890 for i in range(nbworkers):
876 for i in range(nbworkers):
891 revsq.put(None)
877 revsq.put(None)
892
878
893 allworkers = []
879 allworkers = []
894 for i in range(nbworkers):
880 for i in range(nbworkers):
895 args = (srcrepo, revsq, sidedataq, tokens)
881 args = (srcrepo, revsq, sidedataq, tokens)
896 w = multiprocessing.Process(target=_sidedata_worker, args=args)
882 w = multiprocessing.Process(target=_sidedata_worker, args=args)
897 allworkers.append(w)
883 allworkers.append(w)
898 w.start()
884 w.start()
899
885
900 # dictionnary to store results for revision higher than we one we are
886 # dictionnary to store results for revision higher than we one we are
901 # looking for. For example, if we need the sidedatamap for 42, and 43 is
887 # looking for. For example, if we need the sidedatamap for 42, and 43 is
902 # received, when shelve 43 for later use.
888 # received, when shelve 43 for later use.
903 staging = {}
889 staging = {}
904
890
905 def sidedata_companion(repo, revlog, rev, old_sidedata):
891 def sidedata_companion(repo, revlog, rev, old_sidedata):
906 # Is the data previously shelved ?
892 # Is the data previously shelved ?
907 data = staging.pop(rev, None)
893 data = staging.pop(rev, None)
908 if data is None:
894 if data is None:
909 # look at the queued result until we find the one we are lookig
895 # look at the queued result until we find the one we are lookig
910 # for (shelve the other ones)
896 # for (shelve the other ones)
911 r, data = sidedataq.get()
897 r, data = sidedataq.get()
912 while r != rev:
898 while r != rev:
913 staging[r] = data
899 staging[r] = data
914 r, data = sidedataq.get()
900 r, data = sidedataq.get()
915 tokens.release()
901 tokens.release()
916 sidedata, has_copies_info = data
902 sidedata, has_copies_info = data
917 new_flag = 0
903 new_flag = 0
918 if has_copies_info:
904 if has_copies_info:
919 new_flag = sidedataflag.REVIDX_HASCOPIESINFO
905 new_flag = sidedataflag.REVIDX_HASCOPIESINFO
920 return sidedata, (new_flag, 0)
906 return sidedata, (new_flag, 0)
921
907
922 return sidedata_companion
908 return sidedata_companion
@@ -1,3129 +1,3129 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end 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 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import binascii
16 import binascii
17 import collections
17 import collections
18 import contextlib
18 import contextlib
19 import errno
19 import errno
20 import io
20 import io
21 import os
21 import os
22 import struct
22 import struct
23 import zlib
23 import zlib
24
24
25 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
26 from .node import (
26 from .node import (
27 bin,
27 bin,
28 hex,
28 hex,
29 nullrev,
29 nullrev,
30 sha1nodeconstants,
30 sha1nodeconstants,
31 short,
31 short,
32 wdirrev,
32 wdirrev,
33 )
33 )
34 from .i18n import _
34 from .i18n import _
35 from .pycompat import getattr
35 from .pycompat import getattr
36 from .revlogutils.constants import (
36 from .revlogutils.constants import (
37 ALL_KINDS,
37 ALL_KINDS,
38 FLAG_GENERALDELTA,
38 FLAG_GENERALDELTA,
39 FLAG_INLINE_DATA,
39 FLAG_INLINE_DATA,
40 INDEX_HEADER,
40 INDEX_HEADER,
41 REVLOGV0,
41 REVLOGV0,
42 REVLOGV1,
42 REVLOGV1,
43 REVLOGV1_FLAGS,
43 REVLOGV1_FLAGS,
44 REVLOGV2,
44 REVLOGV2,
45 REVLOGV2_FLAGS,
45 REVLOGV2_FLAGS,
46 REVLOG_DEFAULT_FLAGS,
46 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FORMAT,
47 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_VERSION,
48 REVLOG_DEFAULT_VERSION,
49 )
49 )
50 from .revlogutils.flagutil import (
50 from .revlogutils.flagutil import (
51 REVIDX_DEFAULT_FLAGS,
51 REVIDX_DEFAULT_FLAGS,
52 REVIDX_ELLIPSIS,
52 REVIDX_ELLIPSIS,
53 REVIDX_EXTSTORED,
53 REVIDX_EXTSTORED,
54 REVIDX_FLAGS_ORDER,
54 REVIDX_FLAGS_ORDER,
55 REVIDX_HASCOPIESINFO,
55 REVIDX_HASCOPIESINFO,
56 REVIDX_ISCENSORED,
56 REVIDX_ISCENSORED,
57 REVIDX_RAWTEXT_CHANGING_FLAGS,
57 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 )
58 )
59 from .thirdparty import attr
59 from .thirdparty import attr
60 from . import (
60 from . import (
61 ancestor,
61 ancestor,
62 dagop,
62 dagop,
63 error,
63 error,
64 mdiff,
64 mdiff,
65 policy,
65 policy,
66 pycompat,
66 pycompat,
67 templatefilters,
67 templatefilters,
68 util,
68 util,
69 )
69 )
70 from .interfaces import (
70 from .interfaces import (
71 repository,
71 repository,
72 util as interfaceutil,
72 util as interfaceutil,
73 )
73 )
74 from .revlogutils import (
74 from .revlogutils import (
75 deltas as deltautil,
75 deltas as deltautil,
76 flagutil,
76 flagutil,
77 nodemap as nodemaputil,
77 nodemap as nodemaputil,
78 revlogv0,
78 revlogv0,
79 sidedata as sidedatautil,
79 sidedata as sidedatautil,
80 )
80 )
81 from .utils import (
81 from .utils import (
82 storageutil,
82 storageutil,
83 stringutil,
83 stringutil,
84 )
84 )
85
85
86 # blanked usage of all the name to prevent pyflakes constraints
86 # blanked usage of all the name to prevent pyflakes constraints
87 # We need these name available in the module for extensions.
87 # We need these name available in the module for extensions.
88
88
89 REVLOGV0
89 REVLOGV0
90 REVLOGV1
90 REVLOGV1
91 REVLOGV2
91 REVLOGV2
92 FLAG_INLINE_DATA
92 FLAG_INLINE_DATA
93 FLAG_GENERALDELTA
93 FLAG_GENERALDELTA
94 REVLOG_DEFAULT_FLAGS
94 REVLOG_DEFAULT_FLAGS
95 REVLOG_DEFAULT_FORMAT
95 REVLOG_DEFAULT_FORMAT
96 REVLOG_DEFAULT_VERSION
96 REVLOG_DEFAULT_VERSION
97 REVLOGV1_FLAGS
97 REVLOGV1_FLAGS
98 REVLOGV2_FLAGS
98 REVLOGV2_FLAGS
99 REVIDX_ISCENSORED
99 REVIDX_ISCENSORED
100 REVIDX_ELLIPSIS
100 REVIDX_ELLIPSIS
101 REVIDX_HASCOPIESINFO
101 REVIDX_HASCOPIESINFO
102 REVIDX_EXTSTORED
102 REVIDX_EXTSTORED
103 REVIDX_DEFAULT_FLAGS
103 REVIDX_DEFAULT_FLAGS
104 REVIDX_FLAGS_ORDER
104 REVIDX_FLAGS_ORDER
105 REVIDX_RAWTEXT_CHANGING_FLAGS
105 REVIDX_RAWTEXT_CHANGING_FLAGS
106
106
107 parsers = policy.importmod('parsers')
107 parsers = policy.importmod('parsers')
108 rustancestor = policy.importrust('ancestor')
108 rustancestor = policy.importrust('ancestor')
109 rustdagop = policy.importrust('dagop')
109 rustdagop = policy.importrust('dagop')
110 rustrevlog = policy.importrust('revlog')
110 rustrevlog = policy.importrust('revlog')
111
111
112 # Aliased for performance.
112 # Aliased for performance.
113 _zlibdecompress = zlib.decompress
113 _zlibdecompress = zlib.decompress
114
114
115 # max size of revlog with inline data
115 # max size of revlog with inline data
116 _maxinline = 131072
116 _maxinline = 131072
117 _chunksize = 1048576
117 _chunksize = 1048576
118
118
119 # Flag processors for REVIDX_ELLIPSIS.
119 # Flag processors for REVIDX_ELLIPSIS.
120 def ellipsisreadprocessor(rl, text):
120 def ellipsisreadprocessor(rl, text):
121 return text, False
121 return text, False
122
122
123
123
124 def ellipsiswriteprocessor(rl, text):
124 def ellipsiswriteprocessor(rl, text):
125 return text, False
125 return text, False
126
126
127
127
128 def ellipsisrawprocessor(rl, text):
128 def ellipsisrawprocessor(rl, text):
129 return False
129 return False
130
130
131
131
132 ellipsisprocessor = (
132 ellipsisprocessor = (
133 ellipsisreadprocessor,
133 ellipsisreadprocessor,
134 ellipsiswriteprocessor,
134 ellipsiswriteprocessor,
135 ellipsisrawprocessor,
135 ellipsisrawprocessor,
136 )
136 )
137
137
138
138
139 def offset_type(offset, type):
139 def offset_type(offset, type):
140 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
140 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
141 raise ValueError(b'unknown revlog index flags')
141 raise ValueError(b'unknown revlog index flags')
142 return int(int(offset) << 16 | type)
142 return int(int(offset) << 16 | type)
143
143
144
144
145 def _verify_revision(rl, skipflags, state, node):
145 def _verify_revision(rl, skipflags, state, node):
146 """Verify the integrity of the given revlog ``node`` while providing a hook
146 """Verify the integrity of the given revlog ``node`` while providing a hook
147 point for extensions to influence the operation."""
147 point for extensions to influence the operation."""
148 if skipflags:
148 if skipflags:
149 state[b'skipread'].add(node)
149 state[b'skipread'].add(node)
150 else:
150 else:
151 # Side-effect: read content and verify hash.
151 # Side-effect: read content and verify hash.
152 rl.revision(node)
152 rl.revision(node)
153
153
154
154
155 # True if a fast implementation for persistent-nodemap is available
155 # True if a fast implementation for persistent-nodemap is available
156 #
156 #
157 # We also consider we have a "fast" implementation in "pure" python because
157 # We also consider we have a "fast" implementation in "pure" python because
158 # people using pure don't really have performance consideration (and a
158 # people using pure don't really have performance consideration (and a
159 # wheelbarrow of other slowness source)
159 # wheelbarrow of other slowness source)
160 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
160 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
161 parsers, 'BaseIndexObject'
161 parsers, 'BaseIndexObject'
162 )
162 )
163
163
164
164
165 @attr.s(slots=True, frozen=True)
165 @attr.s(slots=True, frozen=True)
166 class _revisioninfo(object):
166 class _revisioninfo(object):
167 """Information about a revision that allows building its fulltext
167 """Information about a revision that allows building its fulltext
168 node: expected hash of the revision
168 node: expected hash of the revision
169 p1, p2: parent revs of the revision
169 p1, p2: parent revs of the revision
170 btext: built text cache consisting of a one-element list
170 btext: built text cache consisting of a one-element list
171 cachedelta: (baserev, uncompressed_delta) or None
171 cachedelta: (baserev, uncompressed_delta) or None
172 flags: flags associated to the revision storage
172 flags: flags associated to the revision storage
173
173
174 One of btext[0] or cachedelta must be set.
174 One of btext[0] or cachedelta must be set.
175 """
175 """
176
176
177 node = attr.ib()
177 node = attr.ib()
178 p1 = attr.ib()
178 p1 = attr.ib()
179 p2 = attr.ib()
179 p2 = attr.ib()
180 btext = attr.ib()
180 btext = attr.ib()
181 textlen = attr.ib()
181 textlen = attr.ib()
182 cachedelta = attr.ib()
182 cachedelta = attr.ib()
183 flags = attr.ib()
183 flags = attr.ib()
184
184
185
185
186 @interfaceutil.implementer(repository.irevisiondelta)
186 @interfaceutil.implementer(repository.irevisiondelta)
187 @attr.s(slots=True)
187 @attr.s(slots=True)
188 class revlogrevisiondelta(object):
188 class revlogrevisiondelta(object):
189 node = attr.ib()
189 node = attr.ib()
190 p1node = attr.ib()
190 p1node = attr.ib()
191 p2node = attr.ib()
191 p2node = attr.ib()
192 basenode = attr.ib()
192 basenode = attr.ib()
193 flags = attr.ib()
193 flags = attr.ib()
194 baserevisionsize = attr.ib()
194 baserevisionsize = attr.ib()
195 revision = attr.ib()
195 revision = attr.ib()
196 delta = attr.ib()
196 delta = attr.ib()
197 sidedata = attr.ib()
197 sidedata = attr.ib()
198 protocol_flags = attr.ib()
198 protocol_flags = attr.ib()
199 linknode = attr.ib(default=None)
199 linknode = attr.ib(default=None)
200
200
201
201
202 @interfaceutil.implementer(repository.iverifyproblem)
202 @interfaceutil.implementer(repository.iverifyproblem)
203 @attr.s(frozen=True)
203 @attr.s(frozen=True)
204 class revlogproblem(object):
204 class revlogproblem(object):
205 warning = attr.ib(default=None)
205 warning = attr.ib(default=None)
206 error = attr.ib(default=None)
206 error = attr.ib(default=None)
207 node = attr.ib(default=None)
207 node = attr.ib(default=None)
208
208
209
209
210 def parse_index_v1(data, inline):
210 def parse_index_v1(data, inline):
211 # call the C implementation to parse the index data
211 # call the C implementation to parse the index data
212 index, cache = parsers.parse_index2(data, inline)
212 index, cache = parsers.parse_index2(data, inline)
213 return index, cache
213 return index, cache
214
214
215
215
216 def parse_index_v2(data, inline):
216 def parse_index_v2(data, inline):
217 # call the C implementation to parse the index data
217 # call the C implementation to parse the index data
218 index, cache = parsers.parse_index2(data, inline, revlogv2=True)
218 index, cache = parsers.parse_index2(data, inline, revlogv2=True)
219 return index, cache
219 return index, cache
220
220
221
221
222 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
222 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
223
223
224 def parse_index_v1_nodemap(data, inline):
224 def parse_index_v1_nodemap(data, inline):
225 index, cache = parsers.parse_index_devel_nodemap(data, inline)
225 index, cache = parsers.parse_index_devel_nodemap(data, inline)
226 return index, cache
226 return index, cache
227
227
228
228
229 else:
229 else:
230 parse_index_v1_nodemap = None
230 parse_index_v1_nodemap = None
231
231
232
232
233 def parse_index_v1_mixed(data, inline):
233 def parse_index_v1_mixed(data, inline):
234 index, cache = parse_index_v1(data, inline)
234 index, cache = parse_index_v1(data, inline)
235 return rustrevlog.MixedIndex(index), cache
235 return rustrevlog.MixedIndex(index), cache
236
236
237
237
238 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
238 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
239 # signed integer)
239 # signed integer)
240 _maxentrysize = 0x7FFFFFFF
240 _maxentrysize = 0x7FFFFFFF
241
241
242
242
243 class revlog(object):
243 class revlog(object):
244 """
244 """
245 the underlying revision storage object
245 the underlying revision storage object
246
246
247 A revlog consists of two parts, an index and the revision data.
247 A revlog consists of two parts, an index and the revision data.
248
248
249 The index is a file with a fixed record size containing
249 The index is a file with a fixed record size containing
250 information on each revision, including its nodeid (hash), the
250 information on each revision, including its nodeid (hash), the
251 nodeids of its parents, the position and offset of its data within
251 nodeids of its parents, the position and offset of its data within
252 the data file, and the revision it's based on. Finally, each entry
252 the data file, and the revision it's based on. Finally, each entry
253 contains a linkrev entry that can serve as a pointer to external
253 contains a linkrev entry that can serve as a pointer to external
254 data.
254 data.
255
255
256 The revision data itself is a linear collection of data chunks.
256 The revision data itself is a linear collection of data chunks.
257 Each chunk represents a revision and is usually represented as a
257 Each chunk represents a revision and is usually represented as a
258 delta against the previous chunk. To bound lookup time, runs of
258 delta against the previous chunk. To bound lookup time, runs of
259 deltas are limited to about 2 times the length of the original
259 deltas are limited to about 2 times the length of the original
260 version data. This makes retrieval of a version proportional to
260 version data. This makes retrieval of a version proportional to
261 its size, or O(1) relative to the number of revisions.
261 its size, or O(1) relative to the number of revisions.
262
262
263 Both pieces of the revlog are written to in an append-only
263 Both pieces of the revlog are written to in an append-only
264 fashion, which means we never need to rewrite a file to insert or
264 fashion, which means we never need to rewrite a file to insert or
265 remove data, and can use some simple techniques to avoid the need
265 remove data, and can use some simple techniques to avoid the need
266 for locking while reading.
266 for locking while reading.
267
267
268 If checkambig, indexfile is opened with checkambig=True at
268 If checkambig, indexfile is opened with checkambig=True at
269 writing, to avoid file stat ambiguity.
269 writing, to avoid file stat ambiguity.
270
270
271 If mmaplargeindex is True, and an mmapindexthreshold is set, the
271 If mmaplargeindex is True, and an mmapindexthreshold is set, the
272 index will be mmapped rather than read if it is larger than the
272 index will be mmapped rather than read if it is larger than the
273 configured threshold.
273 configured threshold.
274
274
275 If censorable is True, the revlog can have censored revisions.
275 If censorable is True, the revlog can have censored revisions.
276
276
277 If `upperboundcomp` is not None, this is the expected maximal gain from
277 If `upperboundcomp` is not None, this is the expected maximal gain from
278 compression for the data content.
278 compression for the data content.
279
279
280 `concurrencychecker` is an optional function that receives 3 arguments: a
280 `concurrencychecker` is an optional function that receives 3 arguments: a
281 file handle, a filename, and an expected position. It should check whether
281 file handle, a filename, and an expected position. It should check whether
282 the current position in the file handle is valid, and log/warn/fail (by
282 the current position in the file handle is valid, and log/warn/fail (by
283 raising).
283 raising).
284 """
284 """
285
285
286 _flagserrorclass = error.RevlogError
286 _flagserrorclass = error.RevlogError
287
287
288 def __init__(
288 def __init__(
289 self,
289 self,
290 opener,
290 opener,
291 target,
291 target,
292 indexfile=None,
292 indexfile=None,
293 datafile=None,
293 datafile=None,
294 checkambig=False,
294 checkambig=False,
295 mmaplargeindex=False,
295 mmaplargeindex=False,
296 censorable=False,
296 censorable=False,
297 upperboundcomp=None,
297 upperboundcomp=None,
298 persistentnodemap=False,
298 persistentnodemap=False,
299 concurrencychecker=None,
299 concurrencychecker=None,
300 ):
300 ):
301 """
301 """
302 create a revlog object
302 create a revlog object
303
303
304 opener is a function that abstracts the file opening operation
304 opener is a function that abstracts the file opening operation
305 and can be used to implement COW semantics or the like.
305 and can be used to implement COW semantics or the like.
306
306
307 `target`: a (KIND, ID) tuple that identify the content stored in
307 `target`: a (KIND, ID) tuple that identify the content stored in
308 this revlog. It help the rest of the code to understand what the revlog
308 this revlog. It help the rest of the code to understand what the revlog
309 is about without having to resort to heuristic and index filename
309 is about without having to resort to heuristic and index filename
310 analysis. Note: that this must be reliably be set by normal code, but
310 analysis. Note: that this must be reliably be set by normal code, but
311 that test, debug, or performance measurement code might not set this to
311 that test, debug, or performance measurement code might not set this to
312 accurate value.
312 accurate value.
313 """
313 """
314 self.upperboundcomp = upperboundcomp
314 self.upperboundcomp = upperboundcomp
315 self.indexfile = indexfile
315 self.indexfile = indexfile
316 self.datafile = datafile or (indexfile[:-2] + b".d")
316 self.datafile = datafile or (indexfile[:-2] + b".d")
317 self.nodemap_file = None
317 self.nodemap_file = None
318 if persistentnodemap:
318 if persistentnodemap:
319 self.nodemap_file = nodemaputil.get_nodemap_file(
319 self.nodemap_file = nodemaputil.get_nodemap_file(
320 opener, self.indexfile
320 opener, self.indexfile
321 )
321 )
322
322
323 self.opener = opener
323 self.opener = opener
324 assert target[0] in ALL_KINDS
324 assert target[0] in ALL_KINDS
325 assert len(target) == 2
325 assert len(target) == 2
326 self.target = target
326 self.target = target
327 # When True, indexfile is opened with checkambig=True at writing, to
327 # When True, indexfile is opened with checkambig=True at writing, to
328 # avoid file stat ambiguity.
328 # avoid file stat ambiguity.
329 self._checkambig = checkambig
329 self._checkambig = checkambig
330 self._mmaplargeindex = mmaplargeindex
330 self._mmaplargeindex = mmaplargeindex
331 self._censorable = censorable
331 self._censorable = censorable
332 # 3-tuple of (node, rev, text) for a raw revision.
332 # 3-tuple of (node, rev, text) for a raw revision.
333 self._revisioncache = None
333 self._revisioncache = None
334 # Maps rev to chain base rev.
334 # Maps rev to chain base rev.
335 self._chainbasecache = util.lrucachedict(100)
335 self._chainbasecache = util.lrucachedict(100)
336 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
336 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
337 self._chunkcache = (0, b'')
337 self._chunkcache = (0, b'')
338 # How much data to read and cache into the raw revlog data cache.
338 # How much data to read and cache into the raw revlog data cache.
339 self._chunkcachesize = 65536
339 self._chunkcachesize = 65536
340 self._maxchainlen = None
340 self._maxchainlen = None
341 self._deltabothparents = True
341 self._deltabothparents = True
342 self.index = None
342 self.index = None
343 self._nodemap_docket = None
343 self._nodemap_docket = None
344 # Mapping of partial identifiers to full nodes.
344 # Mapping of partial identifiers to full nodes.
345 self._pcache = {}
345 self._pcache = {}
346 # Mapping of revision integer to full node.
346 # Mapping of revision integer to full node.
347 self._compengine = b'zlib'
347 self._compengine = b'zlib'
348 self._compengineopts = {}
348 self._compengineopts = {}
349 self._maxdeltachainspan = -1
349 self._maxdeltachainspan = -1
350 self._withsparseread = False
350 self._withsparseread = False
351 self._sparserevlog = False
351 self._sparserevlog = False
352 self._srdensitythreshold = 0.50
352 self._srdensitythreshold = 0.50
353 self._srmingapsize = 262144
353 self._srmingapsize = 262144
354
354
355 # Make copy of flag processors so each revlog instance can support
355 # Make copy of flag processors so each revlog instance can support
356 # custom flags.
356 # custom flags.
357 self._flagprocessors = dict(flagutil.flagprocessors)
357 self._flagprocessors = dict(flagutil.flagprocessors)
358
358
359 # 2-tuple of file handles being used for active writing.
359 # 2-tuple of file handles being used for active writing.
360 self._writinghandles = None
360 self._writinghandles = None
361
361
362 self._loadindex()
362 self._loadindex()
363
363
364 self._concurrencychecker = concurrencychecker
364 self._concurrencychecker = concurrencychecker
365
365
366 def _loadindex(self):
366 def _loadindex(self):
367 mmapindexthreshold = None
367 mmapindexthreshold = None
368 opts = self.opener.options
368 opts = self.opener.options
369
369
370 if b'revlogv2' in opts:
370 if b'revlogv2' in opts:
371 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
371 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
372 elif b'revlogv1' in opts:
372 elif b'revlogv1' in opts:
373 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
373 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
374 if b'generaldelta' in opts:
374 if b'generaldelta' in opts:
375 newversionflags |= FLAG_GENERALDELTA
375 newversionflags |= FLAG_GENERALDELTA
376 elif b'revlogv0' in self.opener.options:
376 elif b'revlogv0' in self.opener.options:
377 newversionflags = REVLOGV0
377 newversionflags = REVLOGV0
378 else:
378 else:
379 newversionflags = REVLOG_DEFAULT_VERSION
379 newversionflags = REVLOG_DEFAULT_VERSION
380
380
381 if b'chunkcachesize' in opts:
381 if b'chunkcachesize' in opts:
382 self._chunkcachesize = opts[b'chunkcachesize']
382 self._chunkcachesize = opts[b'chunkcachesize']
383 if b'maxchainlen' in opts:
383 if b'maxchainlen' in opts:
384 self._maxchainlen = opts[b'maxchainlen']
384 self._maxchainlen = opts[b'maxchainlen']
385 if b'deltabothparents' in opts:
385 if b'deltabothparents' in opts:
386 self._deltabothparents = opts[b'deltabothparents']
386 self._deltabothparents = opts[b'deltabothparents']
387 self._lazydelta = bool(opts.get(b'lazydelta', True))
387 self._lazydelta = bool(opts.get(b'lazydelta', True))
388 self._lazydeltabase = False
388 self._lazydeltabase = False
389 if self._lazydelta:
389 if self._lazydelta:
390 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
390 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
391 if b'compengine' in opts:
391 if b'compengine' in opts:
392 self._compengine = opts[b'compengine']
392 self._compengine = opts[b'compengine']
393 if b'zlib.level' in opts:
393 if b'zlib.level' in opts:
394 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
394 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
395 if b'zstd.level' in opts:
395 if b'zstd.level' in opts:
396 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
396 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
397 if b'maxdeltachainspan' in opts:
397 if b'maxdeltachainspan' in opts:
398 self._maxdeltachainspan = opts[b'maxdeltachainspan']
398 self._maxdeltachainspan = opts[b'maxdeltachainspan']
399 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
399 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
400 mmapindexthreshold = opts[b'mmapindexthreshold']
400 mmapindexthreshold = opts[b'mmapindexthreshold']
401 self.hassidedata = bool(opts.get(b'side-data', False))
401 self.hassidedata = bool(opts.get(b'side-data', False))
402 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
402 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
403 withsparseread = bool(opts.get(b'with-sparse-read', False))
403 withsparseread = bool(opts.get(b'with-sparse-read', False))
404 # sparse-revlog forces sparse-read
404 # sparse-revlog forces sparse-read
405 self._withsparseread = self._sparserevlog or withsparseread
405 self._withsparseread = self._sparserevlog or withsparseread
406 if b'sparse-read-density-threshold' in opts:
406 if b'sparse-read-density-threshold' in opts:
407 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
407 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
408 if b'sparse-read-min-gap-size' in opts:
408 if b'sparse-read-min-gap-size' in opts:
409 self._srmingapsize = opts[b'sparse-read-min-gap-size']
409 self._srmingapsize = opts[b'sparse-read-min-gap-size']
410 if opts.get(b'enableellipsis'):
410 if opts.get(b'enableellipsis'):
411 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
411 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
412
412
413 # revlog v0 doesn't have flag processors
413 # revlog v0 doesn't have flag processors
414 for flag, processor in pycompat.iteritems(
414 for flag, processor in pycompat.iteritems(
415 opts.get(b'flagprocessors', {})
415 opts.get(b'flagprocessors', {})
416 ):
416 ):
417 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
417 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
418
418
419 if self._chunkcachesize <= 0:
419 if self._chunkcachesize <= 0:
420 raise error.RevlogError(
420 raise error.RevlogError(
421 _(b'revlog chunk cache size %r is not greater than 0')
421 _(b'revlog chunk cache size %r is not greater than 0')
422 % self._chunkcachesize
422 % self._chunkcachesize
423 )
423 )
424 elif self._chunkcachesize & (self._chunkcachesize - 1):
424 elif self._chunkcachesize & (self._chunkcachesize - 1):
425 raise error.RevlogError(
425 raise error.RevlogError(
426 _(b'revlog chunk cache size %r is not a power of 2')
426 _(b'revlog chunk cache size %r is not a power of 2')
427 % self._chunkcachesize
427 % self._chunkcachesize
428 )
428 )
429
429
430 indexdata = b''
430 indexdata = b''
431 self._initempty = True
431 self._initempty = True
432 try:
432 try:
433 with self._indexfp() as f:
433 with self._indexfp() as f:
434 if (
434 if (
435 mmapindexthreshold is not None
435 mmapindexthreshold is not None
436 and self.opener.fstat(f).st_size >= mmapindexthreshold
436 and self.opener.fstat(f).st_size >= mmapindexthreshold
437 ):
437 ):
438 # TODO: should .close() to release resources without
438 # TODO: should .close() to release resources without
439 # relying on Python GC
439 # relying on Python GC
440 indexdata = util.buffer(util.mmapread(f))
440 indexdata = util.buffer(util.mmapread(f))
441 else:
441 else:
442 indexdata = f.read()
442 indexdata = f.read()
443 if len(indexdata) > 0:
443 if len(indexdata) > 0:
444 versionflags = INDEX_HEADER.unpack(indexdata[:4])[0]
444 versionflags = INDEX_HEADER.unpack(indexdata[:4])[0]
445 self._initempty = False
445 self._initempty = False
446 else:
446 else:
447 versionflags = newversionflags
447 versionflags = newversionflags
448 except IOError as inst:
448 except IOError as inst:
449 if inst.errno != errno.ENOENT:
449 if inst.errno != errno.ENOENT:
450 raise
450 raise
451
451
452 versionflags = newversionflags
452 versionflags = newversionflags
453
453
454 self.version = versionflags
454 self.version = versionflags
455
455
456 flags = versionflags & ~0xFFFF
456 flags = versionflags & ~0xFFFF
457 fmt = versionflags & 0xFFFF
457 fmt = versionflags & 0xFFFF
458
458
459 if fmt == REVLOGV0:
459 if fmt == REVLOGV0:
460 if flags:
460 if flags:
461 raise error.RevlogError(
461 raise error.RevlogError(
462 _(b'unknown flags (%#04x) in version %d revlog %s')
462 _(b'unknown flags (%#04x) in version %d revlog %s')
463 % (flags >> 16, fmt, self.indexfile)
463 % (flags >> 16, fmt, self.indexfile)
464 )
464 )
465
465
466 self._inline = False
466 self._inline = False
467 self._generaldelta = False
467 self._generaldelta = False
468
468
469 elif fmt == REVLOGV1:
469 elif fmt == REVLOGV1:
470 if flags & ~REVLOGV1_FLAGS:
470 if flags & ~REVLOGV1_FLAGS:
471 raise error.RevlogError(
471 raise error.RevlogError(
472 _(b'unknown flags (%#04x) in version %d revlog %s')
472 _(b'unknown flags (%#04x) in version %d revlog %s')
473 % (flags >> 16, fmt, self.indexfile)
473 % (flags >> 16, fmt, self.indexfile)
474 )
474 )
475
475
476 self._inline = versionflags & FLAG_INLINE_DATA
476 self._inline = versionflags & FLAG_INLINE_DATA
477 self._generaldelta = versionflags & FLAG_GENERALDELTA
477 self._generaldelta = versionflags & FLAG_GENERALDELTA
478
478
479 elif fmt == REVLOGV2:
479 elif fmt == REVLOGV2:
480 if flags & ~REVLOGV2_FLAGS:
480 if flags & ~REVLOGV2_FLAGS:
481 raise error.RevlogError(
481 raise error.RevlogError(
482 _(b'unknown flags (%#04x) in version %d revlog %s')
482 _(b'unknown flags (%#04x) in version %d revlog %s')
483 % (flags >> 16, fmt, self.indexfile)
483 % (flags >> 16, fmt, self.indexfile)
484 )
484 )
485
485
486 # There is a bug in the transaction handling when going from an
486 # There is a bug in the transaction handling when going from an
487 # inline revlog to a separate index and data file. Turn it off until
487 # inline revlog to a separate index and data file. Turn it off until
488 # it's fixed, since v2 revlogs sometimes get rewritten on exchange.
488 # it's fixed, since v2 revlogs sometimes get rewritten on exchange.
489 # See issue6485
489 # See issue6485
490 self._inline = False
490 self._inline = False
491 # generaldelta implied by version 2 revlogs.
491 # generaldelta implied by version 2 revlogs.
492 self._generaldelta = True
492 self._generaldelta = True
493
493
494 else:
494 else:
495 raise error.RevlogError(
495 raise error.RevlogError(
496 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
496 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
497 )
497 )
498
498
499 self.nodeconstants = sha1nodeconstants
499 self.nodeconstants = sha1nodeconstants
500 self.nullid = self.nodeconstants.nullid
500 self.nullid = self.nodeconstants.nullid
501
501
502 # sparse-revlog can't be on without general-delta (issue6056)
502 # sparse-revlog can't be on without general-delta (issue6056)
503 if not self._generaldelta:
503 if not self._generaldelta:
504 self._sparserevlog = False
504 self._sparserevlog = False
505
505
506 self._storedeltachains = True
506 self._storedeltachains = True
507
507
508 devel_nodemap = (
508 devel_nodemap = (
509 self.nodemap_file
509 self.nodemap_file
510 and opts.get(b'devel-force-nodemap', False)
510 and opts.get(b'devel-force-nodemap', False)
511 and parse_index_v1_nodemap is not None
511 and parse_index_v1_nodemap is not None
512 )
512 )
513
513
514 use_rust_index = False
514 use_rust_index = False
515 if rustrevlog is not None:
515 if rustrevlog is not None:
516 if self.nodemap_file is not None:
516 if self.nodemap_file is not None:
517 use_rust_index = True
517 use_rust_index = True
518 else:
518 else:
519 use_rust_index = self.opener.options.get(b'rust.index')
519 use_rust_index = self.opener.options.get(b'rust.index')
520
520
521 self._parse_index = parse_index_v1
521 self._parse_index = parse_index_v1
522 if self.version == REVLOGV0:
522 if self.version == REVLOGV0:
523 self._parse_index = revlogv0.parse_index_v0
523 self._parse_index = revlogv0.parse_index_v0
524 elif fmt == REVLOGV2:
524 elif fmt == REVLOGV2:
525 self._parse_index = parse_index_v2
525 self._parse_index = parse_index_v2
526 elif devel_nodemap:
526 elif devel_nodemap:
527 self._parse_index = parse_index_v1_nodemap
527 self._parse_index = parse_index_v1_nodemap
528 elif use_rust_index:
528 elif use_rust_index:
529 self._parse_index = parse_index_v1_mixed
529 self._parse_index = parse_index_v1_mixed
530 try:
530 try:
531 d = self._parse_index(indexdata, self._inline)
531 d = self._parse_index(indexdata, self._inline)
532 index, _chunkcache = d
532 index, _chunkcache = d
533 use_nodemap = (
533 use_nodemap = (
534 not self._inline
534 not self._inline
535 and self.nodemap_file is not None
535 and self.nodemap_file is not None
536 and util.safehasattr(index, 'update_nodemap_data')
536 and util.safehasattr(index, 'update_nodemap_data')
537 )
537 )
538 if use_nodemap:
538 if use_nodemap:
539 nodemap_data = nodemaputil.persisted_data(self)
539 nodemap_data = nodemaputil.persisted_data(self)
540 if nodemap_data is not None:
540 if nodemap_data is not None:
541 docket = nodemap_data[0]
541 docket = nodemap_data[0]
542 if (
542 if (
543 len(d[0]) > docket.tip_rev
543 len(d[0]) > docket.tip_rev
544 and d[0][docket.tip_rev][7] == docket.tip_node
544 and d[0][docket.tip_rev][7] == docket.tip_node
545 ):
545 ):
546 # no changelog tampering
546 # no changelog tampering
547 self._nodemap_docket = docket
547 self._nodemap_docket = docket
548 index.update_nodemap_data(*nodemap_data)
548 index.update_nodemap_data(*nodemap_data)
549 except (ValueError, IndexError):
549 except (ValueError, IndexError):
550 raise error.RevlogError(
550 raise error.RevlogError(
551 _(b"index %s is corrupted") % self.indexfile
551 _(b"index %s is corrupted") % self.indexfile
552 )
552 )
553 self.index, self._chunkcache = d
553 self.index, self._chunkcache = d
554 if not self._chunkcache:
554 if not self._chunkcache:
555 self._chunkclear()
555 self._chunkclear()
556 # revnum -> (chain-length, sum-delta-length)
556 # revnum -> (chain-length, sum-delta-length)
557 self._chaininfocache = util.lrucachedict(500)
557 self._chaininfocache = util.lrucachedict(500)
558 # revlog header -> revlog compressor
558 # revlog header -> revlog compressor
559 self._decompressors = {}
559 self._decompressors = {}
560
560
561 @util.propertycache
561 @util.propertycache
562 def revlog_kind(self):
562 def revlog_kind(self):
563 return self.target[0]
563 return self.target[0]
564
564
565 @util.propertycache
565 @util.propertycache
566 def _compressor(self):
566 def _compressor(self):
567 engine = util.compengines[self._compengine]
567 engine = util.compengines[self._compengine]
568 return engine.revlogcompressor(self._compengineopts)
568 return engine.revlogcompressor(self._compengineopts)
569
569
570 def _indexfp(self, mode=b'r'):
570 def _indexfp(self, mode=b'r'):
571 """file object for the revlog's index file"""
571 """file object for the revlog's index file"""
572 args = {'mode': mode}
572 args = {'mode': mode}
573 if mode != b'r':
573 if mode != b'r':
574 args['checkambig'] = self._checkambig
574 args['checkambig'] = self._checkambig
575 if mode == b'w':
575 if mode == b'w':
576 args['atomictemp'] = True
576 args['atomictemp'] = True
577 return self.opener(self.indexfile, **args)
577 return self.opener(self.indexfile, **args)
578
578
579 def _datafp(self, mode=b'r'):
579 def _datafp(self, mode=b'r'):
580 """file object for the revlog's data file"""
580 """file object for the revlog's data file"""
581 return self.opener(self.datafile, mode=mode)
581 return self.opener(self.datafile, mode=mode)
582
582
583 @contextlib.contextmanager
583 @contextlib.contextmanager
584 def _datareadfp(self, existingfp=None):
584 def _datareadfp(self, existingfp=None):
585 """file object suitable to read data"""
585 """file object suitable to read data"""
586 # Use explicit file handle, if given.
586 # Use explicit file handle, if given.
587 if existingfp is not None:
587 if existingfp is not None:
588 yield existingfp
588 yield existingfp
589
589
590 # Use a file handle being actively used for writes, if available.
590 # Use a file handle being actively used for writes, if available.
591 # There is some danger to doing this because reads will seek the
591 # There is some danger to doing this because reads will seek the
592 # file. However, _writeentry() performs a SEEK_END before all writes,
592 # file. However, _writeentry() performs a SEEK_END before all writes,
593 # so we should be safe.
593 # so we should be safe.
594 elif self._writinghandles:
594 elif self._writinghandles:
595 if self._inline:
595 if self._inline:
596 yield self._writinghandles[0]
596 yield self._writinghandles[0]
597 else:
597 else:
598 yield self._writinghandles[1]
598 yield self._writinghandles[1]
599
599
600 # Otherwise open a new file handle.
600 # Otherwise open a new file handle.
601 else:
601 else:
602 if self._inline:
602 if self._inline:
603 func = self._indexfp
603 func = self._indexfp
604 else:
604 else:
605 func = self._datafp
605 func = self._datafp
606 with func() as fp:
606 with func() as fp:
607 yield fp
607 yield fp
608
608
609 def tiprev(self):
609 def tiprev(self):
610 return len(self.index) - 1
610 return len(self.index) - 1
611
611
612 def tip(self):
612 def tip(self):
613 return self.node(self.tiprev())
613 return self.node(self.tiprev())
614
614
615 def __contains__(self, rev):
615 def __contains__(self, rev):
616 return 0 <= rev < len(self)
616 return 0 <= rev < len(self)
617
617
618 def __len__(self):
618 def __len__(self):
619 return len(self.index)
619 return len(self.index)
620
620
621 def __iter__(self):
621 def __iter__(self):
622 return iter(pycompat.xrange(len(self)))
622 return iter(pycompat.xrange(len(self)))
623
623
624 def revs(self, start=0, stop=None):
624 def revs(self, start=0, stop=None):
625 """iterate over all rev in this revlog (from start to stop)"""
625 """iterate over all rev in this revlog (from start to stop)"""
626 return storageutil.iterrevs(len(self), start=start, stop=stop)
626 return storageutil.iterrevs(len(self), start=start, stop=stop)
627
627
628 @property
628 @property
629 def nodemap(self):
629 def nodemap(self):
630 msg = (
630 msg = (
631 b"revlog.nodemap is deprecated, "
631 b"revlog.nodemap is deprecated, "
632 b"use revlog.index.[has_node|rev|get_rev]"
632 b"use revlog.index.[has_node|rev|get_rev]"
633 )
633 )
634 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
634 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
635 return self.index.nodemap
635 return self.index.nodemap
636
636
637 @property
637 @property
638 def _nodecache(self):
638 def _nodecache(self):
639 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
639 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
640 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
640 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
641 return self.index.nodemap
641 return self.index.nodemap
642
642
643 def hasnode(self, node):
643 def hasnode(self, node):
644 try:
644 try:
645 self.rev(node)
645 self.rev(node)
646 return True
646 return True
647 except KeyError:
647 except KeyError:
648 return False
648 return False
649
649
650 def candelta(self, baserev, rev):
650 def candelta(self, baserev, rev):
651 """whether two revisions (baserev, rev) can be delta-ed or not"""
651 """whether two revisions (baserev, rev) can be delta-ed or not"""
652 # Disable delta if either rev requires a content-changing flag
652 # Disable delta if either rev requires a content-changing flag
653 # processor (ex. LFS). This is because such flag processor can alter
653 # processor (ex. LFS). This is because such flag processor can alter
654 # the rawtext content that the delta will be based on, and two clients
654 # the rawtext content that the delta will be based on, and two clients
655 # could have a same revlog node with different flags (i.e. different
655 # could have a same revlog node with different flags (i.e. different
656 # rawtext contents) and the delta could be incompatible.
656 # rawtext contents) and the delta could be incompatible.
657 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
657 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
658 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
658 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
659 ):
659 ):
660 return False
660 return False
661 return True
661 return True
662
662
663 def update_caches(self, transaction):
663 def update_caches(self, transaction):
664 if self.nodemap_file is not None:
664 if self.nodemap_file is not None:
665 if transaction is None:
665 if transaction is None:
666 nodemaputil.update_persistent_nodemap(self)
666 nodemaputil.update_persistent_nodemap(self)
667 else:
667 else:
668 nodemaputil.setup_persistent_nodemap(transaction, self)
668 nodemaputil.setup_persistent_nodemap(transaction, self)
669
669
670 def clearcaches(self):
670 def clearcaches(self):
671 self._revisioncache = None
671 self._revisioncache = None
672 self._chainbasecache.clear()
672 self._chainbasecache.clear()
673 self._chunkcache = (0, b'')
673 self._chunkcache = (0, b'')
674 self._pcache = {}
674 self._pcache = {}
675 self._nodemap_docket = None
675 self._nodemap_docket = None
676 self.index.clearcaches()
676 self.index.clearcaches()
677 # The python code is the one responsible for validating the docket, we
677 # The python code is the one responsible for validating the docket, we
678 # end up having to refresh it here.
678 # end up having to refresh it here.
679 use_nodemap = (
679 use_nodemap = (
680 not self._inline
680 not self._inline
681 and self.nodemap_file is not None
681 and self.nodemap_file is not None
682 and util.safehasattr(self.index, 'update_nodemap_data')
682 and util.safehasattr(self.index, 'update_nodemap_data')
683 )
683 )
684 if use_nodemap:
684 if use_nodemap:
685 nodemap_data = nodemaputil.persisted_data(self)
685 nodemap_data = nodemaputil.persisted_data(self)
686 if nodemap_data is not None:
686 if nodemap_data is not None:
687 self._nodemap_docket = nodemap_data[0]
687 self._nodemap_docket = nodemap_data[0]
688 self.index.update_nodemap_data(*nodemap_data)
688 self.index.update_nodemap_data(*nodemap_data)
689
689
690 def rev(self, node):
690 def rev(self, node):
691 try:
691 try:
692 return self.index.rev(node)
692 return self.index.rev(node)
693 except TypeError:
693 except TypeError:
694 raise
694 raise
695 except error.RevlogError:
695 except error.RevlogError:
696 # parsers.c radix tree lookup failed
696 # parsers.c radix tree lookup failed
697 if (
697 if (
698 node == self.nodeconstants.wdirid
698 node == self.nodeconstants.wdirid
699 or node in self.nodeconstants.wdirfilenodeids
699 or node in self.nodeconstants.wdirfilenodeids
700 ):
700 ):
701 raise error.WdirUnsupported
701 raise error.WdirUnsupported
702 raise error.LookupError(node, self.indexfile, _(b'no node'))
702 raise error.LookupError(node, self.indexfile, _(b'no node'))
703
703
704 # Accessors for index entries.
704 # Accessors for index entries.
705
705
706 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
706 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
707 # are flags.
707 # are flags.
708 def start(self, rev):
708 def start(self, rev):
709 return int(self.index[rev][0] >> 16)
709 return int(self.index[rev][0] >> 16)
710
710
711 def flags(self, rev):
711 def flags(self, rev):
712 return self.index[rev][0] & 0xFFFF
712 return self.index[rev][0] & 0xFFFF
713
713
714 def length(self, rev):
714 def length(self, rev):
715 return self.index[rev][1]
715 return self.index[rev][1]
716
716
717 def sidedata_length(self, rev):
717 def sidedata_length(self, rev):
718 if self.version & 0xFFFF != REVLOGV2:
718 if self.version & 0xFFFF != REVLOGV2:
719 return 0
719 return 0
720 return self.index[rev][9]
720 return self.index[rev][9]
721
721
722 def rawsize(self, rev):
722 def rawsize(self, rev):
723 """return the length of the uncompressed text for a given revision"""
723 """return the length of the uncompressed text for a given revision"""
724 l = self.index[rev][2]
724 l = self.index[rev][2]
725 if l >= 0:
725 if l >= 0:
726 return l
726 return l
727
727
728 t = self.rawdata(rev)
728 t = self.rawdata(rev)
729 return len(t)
729 return len(t)
730
730
731 def size(self, rev):
731 def size(self, rev):
732 """length of non-raw text (processed by a "read" flag processor)"""
732 """length of non-raw text (processed by a "read" flag processor)"""
733 # fast path: if no "read" flag processor could change the content,
733 # fast path: if no "read" flag processor could change the content,
734 # size is rawsize. note: ELLIPSIS is known to not change the content.
734 # size is rawsize. note: ELLIPSIS is known to not change the content.
735 flags = self.flags(rev)
735 flags = self.flags(rev)
736 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
736 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
737 return self.rawsize(rev)
737 return self.rawsize(rev)
738
738
739 return len(self.revision(rev, raw=False))
739 return len(self.revision(rev, raw=False))
740
740
741 def chainbase(self, rev):
741 def chainbase(self, rev):
742 base = self._chainbasecache.get(rev)
742 base = self._chainbasecache.get(rev)
743 if base is not None:
743 if base is not None:
744 return base
744 return base
745
745
746 index = self.index
746 index = self.index
747 iterrev = rev
747 iterrev = rev
748 base = index[iterrev][3]
748 base = index[iterrev][3]
749 while base != iterrev:
749 while base != iterrev:
750 iterrev = base
750 iterrev = base
751 base = index[iterrev][3]
751 base = index[iterrev][3]
752
752
753 self._chainbasecache[rev] = base
753 self._chainbasecache[rev] = base
754 return base
754 return base
755
755
756 def linkrev(self, rev):
756 def linkrev(self, rev):
757 return self.index[rev][4]
757 return self.index[rev][4]
758
758
759 def parentrevs(self, rev):
759 def parentrevs(self, rev):
760 try:
760 try:
761 entry = self.index[rev]
761 entry = self.index[rev]
762 except IndexError:
762 except IndexError:
763 if rev == wdirrev:
763 if rev == wdirrev:
764 raise error.WdirUnsupported
764 raise error.WdirUnsupported
765 raise
765 raise
766 if entry[5] == nullrev:
766 if entry[5] == nullrev:
767 return entry[6], entry[5]
767 return entry[6], entry[5]
768 else:
768 else:
769 return entry[5], entry[6]
769 return entry[5], entry[6]
770
770
771 # fast parentrevs(rev) where rev isn't filtered
771 # fast parentrevs(rev) where rev isn't filtered
772 _uncheckedparentrevs = parentrevs
772 _uncheckedparentrevs = parentrevs
773
773
774 def node(self, rev):
774 def node(self, rev):
775 try:
775 try:
776 return self.index[rev][7]
776 return self.index[rev][7]
777 except IndexError:
777 except IndexError:
778 if rev == wdirrev:
778 if rev == wdirrev:
779 raise error.WdirUnsupported
779 raise error.WdirUnsupported
780 raise
780 raise
781
781
782 # Derived from index values.
782 # Derived from index values.
783
783
784 def end(self, rev):
784 def end(self, rev):
785 return self.start(rev) + self.length(rev)
785 return self.start(rev) + self.length(rev)
786
786
787 def parents(self, node):
787 def parents(self, node):
788 i = self.index
788 i = self.index
789 d = i[self.rev(node)]
789 d = i[self.rev(node)]
790 # inline node() to avoid function call overhead
790 # inline node() to avoid function call overhead
791 if d[5] == self.nullid:
791 if d[5] == self.nullid:
792 return i[d[6]][7], i[d[5]][7]
792 return i[d[6]][7], i[d[5]][7]
793 else:
793 else:
794 return i[d[5]][7], i[d[6]][7]
794 return i[d[5]][7], i[d[6]][7]
795
795
796 def chainlen(self, rev):
796 def chainlen(self, rev):
797 return self._chaininfo(rev)[0]
797 return self._chaininfo(rev)[0]
798
798
799 def _chaininfo(self, rev):
799 def _chaininfo(self, rev):
800 chaininfocache = self._chaininfocache
800 chaininfocache = self._chaininfocache
801 if rev in chaininfocache:
801 if rev in chaininfocache:
802 return chaininfocache[rev]
802 return chaininfocache[rev]
803 index = self.index
803 index = self.index
804 generaldelta = self._generaldelta
804 generaldelta = self._generaldelta
805 iterrev = rev
805 iterrev = rev
806 e = index[iterrev]
806 e = index[iterrev]
807 clen = 0
807 clen = 0
808 compresseddeltalen = 0
808 compresseddeltalen = 0
809 while iterrev != e[3]:
809 while iterrev != e[3]:
810 clen += 1
810 clen += 1
811 compresseddeltalen += e[1]
811 compresseddeltalen += e[1]
812 if generaldelta:
812 if generaldelta:
813 iterrev = e[3]
813 iterrev = e[3]
814 else:
814 else:
815 iterrev -= 1
815 iterrev -= 1
816 if iterrev in chaininfocache:
816 if iterrev in chaininfocache:
817 t = chaininfocache[iterrev]
817 t = chaininfocache[iterrev]
818 clen += t[0]
818 clen += t[0]
819 compresseddeltalen += t[1]
819 compresseddeltalen += t[1]
820 break
820 break
821 e = index[iterrev]
821 e = index[iterrev]
822 else:
822 else:
823 # Add text length of base since decompressing that also takes
823 # Add text length of base since decompressing that also takes
824 # work. For cache hits the length is already included.
824 # work. For cache hits the length is already included.
825 compresseddeltalen += e[1]
825 compresseddeltalen += e[1]
826 r = (clen, compresseddeltalen)
826 r = (clen, compresseddeltalen)
827 chaininfocache[rev] = r
827 chaininfocache[rev] = r
828 return r
828 return r
829
829
830 def _deltachain(self, rev, stoprev=None):
830 def _deltachain(self, rev, stoprev=None):
831 """Obtain the delta chain for a revision.
831 """Obtain the delta chain for a revision.
832
832
833 ``stoprev`` specifies a revision to stop at. If not specified, we
833 ``stoprev`` specifies a revision to stop at. If not specified, we
834 stop at the base of the chain.
834 stop at the base of the chain.
835
835
836 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
836 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
837 revs in ascending order and ``stopped`` is a bool indicating whether
837 revs in ascending order and ``stopped`` is a bool indicating whether
838 ``stoprev`` was hit.
838 ``stoprev`` was hit.
839 """
839 """
840 # Try C implementation.
840 # Try C implementation.
841 try:
841 try:
842 return self.index.deltachain(rev, stoprev, self._generaldelta)
842 return self.index.deltachain(rev, stoprev, self._generaldelta)
843 except AttributeError:
843 except AttributeError:
844 pass
844 pass
845
845
846 chain = []
846 chain = []
847
847
848 # Alias to prevent attribute lookup in tight loop.
848 # Alias to prevent attribute lookup in tight loop.
849 index = self.index
849 index = self.index
850 generaldelta = self._generaldelta
850 generaldelta = self._generaldelta
851
851
852 iterrev = rev
852 iterrev = rev
853 e = index[iterrev]
853 e = index[iterrev]
854 while iterrev != e[3] and iterrev != stoprev:
854 while iterrev != e[3] and iterrev != stoprev:
855 chain.append(iterrev)
855 chain.append(iterrev)
856 if generaldelta:
856 if generaldelta:
857 iterrev = e[3]
857 iterrev = e[3]
858 else:
858 else:
859 iterrev -= 1
859 iterrev -= 1
860 e = index[iterrev]
860 e = index[iterrev]
861
861
862 if iterrev == stoprev:
862 if iterrev == stoprev:
863 stopped = True
863 stopped = True
864 else:
864 else:
865 chain.append(iterrev)
865 chain.append(iterrev)
866 stopped = False
866 stopped = False
867
867
868 chain.reverse()
868 chain.reverse()
869 return chain, stopped
869 return chain, stopped
870
870
871 def ancestors(self, revs, stoprev=0, inclusive=False):
871 def ancestors(self, revs, stoprev=0, inclusive=False):
872 """Generate the ancestors of 'revs' in reverse revision order.
872 """Generate the ancestors of 'revs' in reverse revision order.
873 Does not generate revs lower than stoprev.
873 Does not generate revs lower than stoprev.
874
874
875 See the documentation for ancestor.lazyancestors for more details."""
875 See the documentation for ancestor.lazyancestors for more details."""
876
876
877 # first, make sure start revisions aren't filtered
877 # first, make sure start revisions aren't filtered
878 revs = list(revs)
878 revs = list(revs)
879 checkrev = self.node
879 checkrev = self.node
880 for r in revs:
880 for r in revs:
881 checkrev(r)
881 checkrev(r)
882 # and we're sure ancestors aren't filtered as well
882 # and we're sure ancestors aren't filtered as well
883
883
884 if rustancestor is not None:
884 if rustancestor is not None:
885 lazyancestors = rustancestor.LazyAncestors
885 lazyancestors = rustancestor.LazyAncestors
886 arg = self.index
886 arg = self.index
887 else:
887 else:
888 lazyancestors = ancestor.lazyancestors
888 lazyancestors = ancestor.lazyancestors
889 arg = self._uncheckedparentrevs
889 arg = self._uncheckedparentrevs
890 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
890 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
891
891
892 def descendants(self, revs):
892 def descendants(self, revs):
893 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
893 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
894
894
895 def findcommonmissing(self, common=None, heads=None):
895 def findcommonmissing(self, common=None, heads=None):
896 """Return a tuple of the ancestors of common and the ancestors of heads
896 """Return a tuple of the ancestors of common and the ancestors of heads
897 that are not ancestors of common. In revset terminology, we return the
897 that are not ancestors of common. In revset terminology, we return the
898 tuple:
898 tuple:
899
899
900 ::common, (::heads) - (::common)
900 ::common, (::heads) - (::common)
901
901
902 The list is sorted by revision number, meaning it is
902 The list is sorted by revision number, meaning it is
903 topologically sorted.
903 topologically sorted.
904
904
905 'heads' and 'common' are both lists of node IDs. If heads is
905 'heads' and 'common' are both lists of node IDs. If heads is
906 not supplied, uses all of the revlog's heads. If common is not
906 not supplied, uses all of the revlog's heads. If common is not
907 supplied, uses nullid."""
907 supplied, uses nullid."""
908 if common is None:
908 if common is None:
909 common = [self.nullid]
909 common = [self.nullid]
910 if heads is None:
910 if heads is None:
911 heads = self.heads()
911 heads = self.heads()
912
912
913 common = [self.rev(n) for n in common]
913 common = [self.rev(n) for n in common]
914 heads = [self.rev(n) for n in heads]
914 heads = [self.rev(n) for n in heads]
915
915
916 # we want the ancestors, but inclusive
916 # we want the ancestors, but inclusive
917 class lazyset(object):
917 class lazyset(object):
918 def __init__(self, lazyvalues):
918 def __init__(self, lazyvalues):
919 self.addedvalues = set()
919 self.addedvalues = set()
920 self.lazyvalues = lazyvalues
920 self.lazyvalues = lazyvalues
921
921
922 def __contains__(self, value):
922 def __contains__(self, value):
923 return value in self.addedvalues or value in self.lazyvalues
923 return value in self.addedvalues or value in self.lazyvalues
924
924
925 def __iter__(self):
925 def __iter__(self):
926 added = self.addedvalues
926 added = self.addedvalues
927 for r in added:
927 for r in added:
928 yield r
928 yield r
929 for r in self.lazyvalues:
929 for r in self.lazyvalues:
930 if not r in added:
930 if not r in added:
931 yield r
931 yield r
932
932
933 def add(self, value):
933 def add(self, value):
934 self.addedvalues.add(value)
934 self.addedvalues.add(value)
935
935
936 def update(self, values):
936 def update(self, values):
937 self.addedvalues.update(values)
937 self.addedvalues.update(values)
938
938
939 has = lazyset(self.ancestors(common))
939 has = lazyset(self.ancestors(common))
940 has.add(nullrev)
940 has.add(nullrev)
941 has.update(common)
941 has.update(common)
942
942
943 # take all ancestors from heads that aren't in has
943 # take all ancestors from heads that aren't in has
944 missing = set()
944 missing = set()
945 visit = collections.deque(r for r in heads if r not in has)
945 visit = collections.deque(r for r in heads if r not in has)
946 while visit:
946 while visit:
947 r = visit.popleft()
947 r = visit.popleft()
948 if r in missing:
948 if r in missing:
949 continue
949 continue
950 else:
950 else:
951 missing.add(r)
951 missing.add(r)
952 for p in self.parentrevs(r):
952 for p in self.parentrevs(r):
953 if p not in has:
953 if p not in has:
954 visit.append(p)
954 visit.append(p)
955 missing = list(missing)
955 missing = list(missing)
956 missing.sort()
956 missing.sort()
957 return has, [self.node(miss) for miss in missing]
957 return has, [self.node(miss) for miss in missing]
958
958
959 def incrementalmissingrevs(self, common=None):
959 def incrementalmissingrevs(self, common=None):
960 """Return an object that can be used to incrementally compute the
960 """Return an object that can be used to incrementally compute the
961 revision numbers of the ancestors of arbitrary sets that are not
961 revision numbers of the ancestors of arbitrary sets that are not
962 ancestors of common. This is an ancestor.incrementalmissingancestors
962 ancestors of common. This is an ancestor.incrementalmissingancestors
963 object.
963 object.
964
964
965 'common' is a list of revision numbers. If common is not supplied, uses
965 'common' is a list of revision numbers. If common is not supplied, uses
966 nullrev.
966 nullrev.
967 """
967 """
968 if common is None:
968 if common is None:
969 common = [nullrev]
969 common = [nullrev]
970
970
971 if rustancestor is not None:
971 if rustancestor is not None:
972 return rustancestor.MissingAncestors(self.index, common)
972 return rustancestor.MissingAncestors(self.index, common)
973 return ancestor.incrementalmissingancestors(self.parentrevs, common)
973 return ancestor.incrementalmissingancestors(self.parentrevs, common)
974
974
975 def findmissingrevs(self, common=None, heads=None):
975 def findmissingrevs(self, common=None, heads=None):
976 """Return the revision numbers of the ancestors of heads that
976 """Return the revision numbers of the ancestors of heads that
977 are not ancestors of common.
977 are not ancestors of common.
978
978
979 More specifically, return a list of revision numbers corresponding to
979 More specifically, return a list of revision numbers corresponding to
980 nodes N such that every N satisfies the following constraints:
980 nodes N such that every N satisfies the following constraints:
981
981
982 1. N is an ancestor of some node in 'heads'
982 1. N is an ancestor of some node in 'heads'
983 2. N is not an ancestor of any node in 'common'
983 2. N is not an ancestor of any node in 'common'
984
984
985 The list is sorted by revision number, meaning it is
985 The list is sorted by revision number, meaning it is
986 topologically sorted.
986 topologically sorted.
987
987
988 'heads' and 'common' are both lists of revision numbers. If heads is
988 'heads' and 'common' are both lists of revision numbers. If heads is
989 not supplied, uses all of the revlog's heads. If common is not
989 not supplied, uses all of the revlog's heads. If common is not
990 supplied, uses nullid."""
990 supplied, uses nullid."""
991 if common is None:
991 if common is None:
992 common = [nullrev]
992 common = [nullrev]
993 if heads is None:
993 if heads is None:
994 heads = self.headrevs()
994 heads = self.headrevs()
995
995
996 inc = self.incrementalmissingrevs(common=common)
996 inc = self.incrementalmissingrevs(common=common)
997 return inc.missingancestors(heads)
997 return inc.missingancestors(heads)
998
998
999 def findmissing(self, common=None, heads=None):
999 def findmissing(self, common=None, heads=None):
1000 """Return the ancestors of heads that are not ancestors of common.
1000 """Return the ancestors of heads that are not ancestors of common.
1001
1001
1002 More specifically, return a list of nodes N such that every N
1002 More specifically, return a list of nodes N such that every N
1003 satisfies the following constraints:
1003 satisfies the following constraints:
1004
1004
1005 1. N is an ancestor of some node in 'heads'
1005 1. N is an ancestor of some node in 'heads'
1006 2. N is not an ancestor of any node in 'common'
1006 2. N is not an ancestor of any node in 'common'
1007
1007
1008 The list is sorted by revision number, meaning it is
1008 The list is sorted by revision number, meaning it is
1009 topologically sorted.
1009 topologically sorted.
1010
1010
1011 'heads' and 'common' are both lists of node IDs. If heads is
1011 'heads' and 'common' are both lists of node IDs. If heads is
1012 not supplied, uses all of the revlog's heads. If common is not
1012 not supplied, uses all of the revlog's heads. If common is not
1013 supplied, uses nullid."""
1013 supplied, uses nullid."""
1014 if common is None:
1014 if common is None:
1015 common = [self.nullid]
1015 common = [self.nullid]
1016 if heads is None:
1016 if heads is None:
1017 heads = self.heads()
1017 heads = self.heads()
1018
1018
1019 common = [self.rev(n) for n in common]
1019 common = [self.rev(n) for n in common]
1020 heads = [self.rev(n) for n in heads]
1020 heads = [self.rev(n) for n in heads]
1021
1021
1022 inc = self.incrementalmissingrevs(common=common)
1022 inc = self.incrementalmissingrevs(common=common)
1023 return [self.node(r) for r in inc.missingancestors(heads)]
1023 return [self.node(r) for r in inc.missingancestors(heads)]
1024
1024
1025 def nodesbetween(self, roots=None, heads=None):
1025 def nodesbetween(self, roots=None, heads=None):
1026 """Return a topological path from 'roots' to 'heads'.
1026 """Return a topological path from 'roots' to 'heads'.
1027
1027
1028 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1028 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1029 topologically sorted list of all nodes N that satisfy both of
1029 topologically sorted list of all nodes N that satisfy both of
1030 these constraints:
1030 these constraints:
1031
1031
1032 1. N is a descendant of some node in 'roots'
1032 1. N is a descendant of some node in 'roots'
1033 2. N is an ancestor of some node in 'heads'
1033 2. N is an ancestor of some node in 'heads'
1034
1034
1035 Every node is considered to be both a descendant and an ancestor
1035 Every node is considered to be both a descendant and an ancestor
1036 of itself, so every reachable node in 'roots' and 'heads' will be
1036 of itself, so every reachable node in 'roots' and 'heads' will be
1037 included in 'nodes'.
1037 included in 'nodes'.
1038
1038
1039 'outroots' is the list of reachable nodes in 'roots', i.e., the
1039 'outroots' is the list of reachable nodes in 'roots', i.e., the
1040 subset of 'roots' that is returned in 'nodes'. Likewise,
1040 subset of 'roots' that is returned in 'nodes'. Likewise,
1041 'outheads' is the subset of 'heads' that is also in 'nodes'.
1041 'outheads' is the subset of 'heads' that is also in 'nodes'.
1042
1042
1043 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1043 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1044 unspecified, uses nullid as the only root. If 'heads' is
1044 unspecified, uses nullid as the only root. If 'heads' is
1045 unspecified, uses list of all of the revlog's heads."""
1045 unspecified, uses list of all of the revlog's heads."""
1046 nonodes = ([], [], [])
1046 nonodes = ([], [], [])
1047 if roots is not None:
1047 if roots is not None:
1048 roots = list(roots)
1048 roots = list(roots)
1049 if not roots:
1049 if not roots:
1050 return nonodes
1050 return nonodes
1051 lowestrev = min([self.rev(n) for n in roots])
1051 lowestrev = min([self.rev(n) for n in roots])
1052 else:
1052 else:
1053 roots = [self.nullid] # Everybody's a descendant of nullid
1053 roots = [self.nullid] # Everybody's a descendant of nullid
1054 lowestrev = nullrev
1054 lowestrev = nullrev
1055 if (lowestrev == nullrev) and (heads is None):
1055 if (lowestrev == nullrev) and (heads is None):
1056 # We want _all_ the nodes!
1056 # We want _all_ the nodes!
1057 return (
1057 return (
1058 [self.node(r) for r in self],
1058 [self.node(r) for r in self],
1059 [self.nullid],
1059 [self.nullid],
1060 list(self.heads()),
1060 list(self.heads()),
1061 )
1061 )
1062 if heads is None:
1062 if heads is None:
1063 # All nodes are ancestors, so the latest ancestor is the last
1063 # All nodes are ancestors, so the latest ancestor is the last
1064 # node.
1064 # node.
1065 highestrev = len(self) - 1
1065 highestrev = len(self) - 1
1066 # Set ancestors to None to signal that every node is an ancestor.
1066 # Set ancestors to None to signal that every node is an ancestor.
1067 ancestors = None
1067 ancestors = None
1068 # Set heads to an empty dictionary for later discovery of heads
1068 # Set heads to an empty dictionary for later discovery of heads
1069 heads = {}
1069 heads = {}
1070 else:
1070 else:
1071 heads = list(heads)
1071 heads = list(heads)
1072 if not heads:
1072 if not heads:
1073 return nonodes
1073 return nonodes
1074 ancestors = set()
1074 ancestors = set()
1075 # Turn heads into a dictionary so we can remove 'fake' heads.
1075 # Turn heads into a dictionary so we can remove 'fake' heads.
1076 # Also, later we will be using it to filter out the heads we can't
1076 # Also, later we will be using it to filter out the heads we can't
1077 # find from roots.
1077 # find from roots.
1078 heads = dict.fromkeys(heads, False)
1078 heads = dict.fromkeys(heads, False)
1079 # Start at the top and keep marking parents until we're done.
1079 # Start at the top and keep marking parents until we're done.
1080 nodestotag = set(heads)
1080 nodestotag = set(heads)
1081 # Remember where the top was so we can use it as a limit later.
1081 # Remember where the top was so we can use it as a limit later.
1082 highestrev = max([self.rev(n) for n in nodestotag])
1082 highestrev = max([self.rev(n) for n in nodestotag])
1083 while nodestotag:
1083 while nodestotag:
1084 # grab a node to tag
1084 # grab a node to tag
1085 n = nodestotag.pop()
1085 n = nodestotag.pop()
1086 # Never tag nullid
1086 # Never tag nullid
1087 if n == self.nullid:
1087 if n == self.nullid:
1088 continue
1088 continue
1089 # A node's revision number represents its place in a
1089 # A node's revision number represents its place in a
1090 # topologically sorted list of nodes.
1090 # topologically sorted list of nodes.
1091 r = self.rev(n)
1091 r = self.rev(n)
1092 if r >= lowestrev:
1092 if r >= lowestrev:
1093 if n not in ancestors:
1093 if n not in ancestors:
1094 # If we are possibly a descendant of one of the roots
1094 # If we are possibly a descendant of one of the roots
1095 # and we haven't already been marked as an ancestor
1095 # and we haven't already been marked as an ancestor
1096 ancestors.add(n) # Mark as ancestor
1096 ancestors.add(n) # Mark as ancestor
1097 # Add non-nullid parents to list of nodes to tag.
1097 # Add non-nullid parents to list of nodes to tag.
1098 nodestotag.update(
1098 nodestotag.update(
1099 [p for p in self.parents(n) if p != self.nullid]
1099 [p for p in self.parents(n) if p != self.nullid]
1100 )
1100 )
1101 elif n in heads: # We've seen it before, is it a fake head?
1101 elif n in heads: # We've seen it before, is it a fake head?
1102 # So it is, real heads should not be the ancestors of
1102 # So it is, real heads should not be the ancestors of
1103 # any other heads.
1103 # any other heads.
1104 heads.pop(n)
1104 heads.pop(n)
1105 if not ancestors:
1105 if not ancestors:
1106 return nonodes
1106 return nonodes
1107 # Now that we have our set of ancestors, we want to remove any
1107 # Now that we have our set of ancestors, we want to remove any
1108 # roots that are not ancestors.
1108 # roots that are not ancestors.
1109
1109
1110 # If one of the roots was nullid, everything is included anyway.
1110 # If one of the roots was nullid, everything is included anyway.
1111 if lowestrev > nullrev:
1111 if lowestrev > nullrev:
1112 # But, since we weren't, let's recompute the lowest rev to not
1112 # But, since we weren't, let's recompute the lowest rev to not
1113 # include roots that aren't ancestors.
1113 # include roots that aren't ancestors.
1114
1114
1115 # Filter out roots that aren't ancestors of heads
1115 # Filter out roots that aren't ancestors of heads
1116 roots = [root for root in roots if root in ancestors]
1116 roots = [root for root in roots if root in ancestors]
1117 # Recompute the lowest revision
1117 # Recompute the lowest revision
1118 if roots:
1118 if roots:
1119 lowestrev = min([self.rev(root) for root in roots])
1119 lowestrev = min([self.rev(root) for root in roots])
1120 else:
1120 else:
1121 # No more roots? Return empty list
1121 # No more roots? Return empty list
1122 return nonodes
1122 return nonodes
1123 else:
1123 else:
1124 # We are descending from nullid, and don't need to care about
1124 # We are descending from nullid, and don't need to care about
1125 # any other roots.
1125 # any other roots.
1126 lowestrev = nullrev
1126 lowestrev = nullrev
1127 roots = [self.nullid]
1127 roots = [self.nullid]
1128 # Transform our roots list into a set.
1128 # Transform our roots list into a set.
1129 descendants = set(roots)
1129 descendants = set(roots)
1130 # Also, keep the original roots so we can filter out roots that aren't
1130 # Also, keep the original roots so we can filter out roots that aren't
1131 # 'real' roots (i.e. are descended from other roots).
1131 # 'real' roots (i.e. are descended from other roots).
1132 roots = descendants.copy()
1132 roots = descendants.copy()
1133 # Our topologically sorted list of output nodes.
1133 # Our topologically sorted list of output nodes.
1134 orderedout = []
1134 orderedout = []
1135 # Don't start at nullid since we don't want nullid in our output list,
1135 # Don't start at nullid since we don't want nullid in our output list,
1136 # and if nullid shows up in descendants, empty parents will look like
1136 # and if nullid shows up in descendants, empty parents will look like
1137 # they're descendants.
1137 # they're descendants.
1138 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1138 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1139 n = self.node(r)
1139 n = self.node(r)
1140 isdescendant = False
1140 isdescendant = False
1141 if lowestrev == nullrev: # Everybody is a descendant of nullid
1141 if lowestrev == nullrev: # Everybody is a descendant of nullid
1142 isdescendant = True
1142 isdescendant = True
1143 elif n in descendants:
1143 elif n in descendants:
1144 # n is already a descendant
1144 # n is already a descendant
1145 isdescendant = True
1145 isdescendant = True
1146 # This check only needs to be done here because all the roots
1146 # This check only needs to be done here because all the roots
1147 # will start being marked is descendants before the loop.
1147 # will start being marked is descendants before the loop.
1148 if n in roots:
1148 if n in roots:
1149 # If n was a root, check if it's a 'real' root.
1149 # If n was a root, check if it's a 'real' root.
1150 p = tuple(self.parents(n))
1150 p = tuple(self.parents(n))
1151 # If any of its parents are descendants, it's not a root.
1151 # If any of its parents are descendants, it's not a root.
1152 if (p[0] in descendants) or (p[1] in descendants):
1152 if (p[0] in descendants) or (p[1] in descendants):
1153 roots.remove(n)
1153 roots.remove(n)
1154 else:
1154 else:
1155 p = tuple(self.parents(n))
1155 p = tuple(self.parents(n))
1156 # A node is a descendant if either of its parents are
1156 # A node is a descendant if either of its parents are
1157 # descendants. (We seeded the dependents list with the roots
1157 # descendants. (We seeded the dependents list with the roots
1158 # up there, remember?)
1158 # up there, remember?)
1159 if (p[0] in descendants) or (p[1] in descendants):
1159 if (p[0] in descendants) or (p[1] in descendants):
1160 descendants.add(n)
1160 descendants.add(n)
1161 isdescendant = True
1161 isdescendant = True
1162 if isdescendant and ((ancestors is None) or (n in ancestors)):
1162 if isdescendant and ((ancestors is None) or (n in ancestors)):
1163 # Only include nodes that are both descendants and ancestors.
1163 # Only include nodes that are both descendants and ancestors.
1164 orderedout.append(n)
1164 orderedout.append(n)
1165 if (ancestors is not None) and (n in heads):
1165 if (ancestors is not None) and (n in heads):
1166 # We're trying to figure out which heads are reachable
1166 # We're trying to figure out which heads are reachable
1167 # from roots.
1167 # from roots.
1168 # Mark this head as having been reached
1168 # Mark this head as having been reached
1169 heads[n] = True
1169 heads[n] = True
1170 elif ancestors is None:
1170 elif ancestors is None:
1171 # Otherwise, we're trying to discover the heads.
1171 # Otherwise, we're trying to discover the heads.
1172 # Assume this is a head because if it isn't, the next step
1172 # Assume this is a head because if it isn't, the next step
1173 # will eventually remove it.
1173 # will eventually remove it.
1174 heads[n] = True
1174 heads[n] = True
1175 # But, obviously its parents aren't.
1175 # But, obviously its parents aren't.
1176 for p in self.parents(n):
1176 for p in self.parents(n):
1177 heads.pop(p, None)
1177 heads.pop(p, None)
1178 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1178 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1179 roots = list(roots)
1179 roots = list(roots)
1180 assert orderedout
1180 assert orderedout
1181 assert roots
1181 assert roots
1182 assert heads
1182 assert heads
1183 return (orderedout, roots, heads)
1183 return (orderedout, roots, heads)
1184
1184
1185 def headrevs(self, revs=None):
1185 def headrevs(self, revs=None):
1186 if revs is None:
1186 if revs is None:
1187 try:
1187 try:
1188 return self.index.headrevs()
1188 return self.index.headrevs()
1189 except AttributeError:
1189 except AttributeError:
1190 return self._headrevs()
1190 return self._headrevs()
1191 if rustdagop is not None:
1191 if rustdagop is not None:
1192 return rustdagop.headrevs(self.index, revs)
1192 return rustdagop.headrevs(self.index, revs)
1193 return dagop.headrevs(revs, self._uncheckedparentrevs)
1193 return dagop.headrevs(revs, self._uncheckedparentrevs)
1194
1194
1195 def computephases(self, roots):
1195 def computephases(self, roots):
1196 return self.index.computephasesmapsets(roots)
1196 return self.index.computephasesmapsets(roots)
1197
1197
1198 def _headrevs(self):
1198 def _headrevs(self):
1199 count = len(self)
1199 count = len(self)
1200 if not count:
1200 if not count:
1201 return [nullrev]
1201 return [nullrev]
1202 # we won't iter over filtered rev so nobody is a head at start
1202 # we won't iter over filtered rev so nobody is a head at start
1203 ishead = [0] * (count + 1)
1203 ishead = [0] * (count + 1)
1204 index = self.index
1204 index = self.index
1205 for r in self:
1205 for r in self:
1206 ishead[r] = 1 # I may be an head
1206 ishead[r] = 1 # I may be an head
1207 e = index[r]
1207 e = index[r]
1208 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1208 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1209 return [r for r, val in enumerate(ishead) if val]
1209 return [r for r, val in enumerate(ishead) if val]
1210
1210
1211 def heads(self, start=None, stop=None):
1211 def heads(self, start=None, stop=None):
1212 """return the list of all nodes that have no children
1212 """return the list of all nodes that have no children
1213
1213
1214 if start is specified, only heads that are descendants of
1214 if start is specified, only heads that are descendants of
1215 start will be returned
1215 start will be returned
1216 if stop is specified, it will consider all the revs from stop
1216 if stop is specified, it will consider all the revs from stop
1217 as if they had no children
1217 as if they had no children
1218 """
1218 """
1219 if start is None and stop is None:
1219 if start is None and stop is None:
1220 if not len(self):
1220 if not len(self):
1221 return [self.nullid]
1221 return [self.nullid]
1222 return [self.node(r) for r in self.headrevs()]
1222 return [self.node(r) for r in self.headrevs()]
1223
1223
1224 if start is None:
1224 if start is None:
1225 start = nullrev
1225 start = nullrev
1226 else:
1226 else:
1227 start = self.rev(start)
1227 start = self.rev(start)
1228
1228
1229 stoprevs = {self.rev(n) for n in stop or []}
1229 stoprevs = {self.rev(n) for n in stop or []}
1230
1230
1231 revs = dagop.headrevssubset(
1231 revs = dagop.headrevssubset(
1232 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1232 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1233 )
1233 )
1234
1234
1235 return [self.node(rev) for rev in revs]
1235 return [self.node(rev) for rev in revs]
1236
1236
1237 def children(self, node):
1237 def children(self, node):
1238 """find the children of a given node"""
1238 """find the children of a given node"""
1239 c = []
1239 c = []
1240 p = self.rev(node)
1240 p = self.rev(node)
1241 for r in self.revs(start=p + 1):
1241 for r in self.revs(start=p + 1):
1242 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1242 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1243 if prevs:
1243 if prevs:
1244 for pr in prevs:
1244 for pr in prevs:
1245 if pr == p:
1245 if pr == p:
1246 c.append(self.node(r))
1246 c.append(self.node(r))
1247 elif p == nullrev:
1247 elif p == nullrev:
1248 c.append(self.node(r))
1248 c.append(self.node(r))
1249 return c
1249 return c
1250
1250
1251 def commonancestorsheads(self, a, b):
1251 def commonancestorsheads(self, a, b):
1252 """calculate all the heads of the common ancestors of nodes a and b"""
1252 """calculate all the heads of the common ancestors of nodes a and b"""
1253 a, b = self.rev(a), self.rev(b)
1253 a, b = self.rev(a), self.rev(b)
1254 ancs = self._commonancestorsheads(a, b)
1254 ancs = self._commonancestorsheads(a, b)
1255 return pycompat.maplist(self.node, ancs)
1255 return pycompat.maplist(self.node, ancs)
1256
1256
1257 def _commonancestorsheads(self, *revs):
1257 def _commonancestorsheads(self, *revs):
1258 """calculate all the heads of the common ancestors of revs"""
1258 """calculate all the heads of the common ancestors of revs"""
1259 try:
1259 try:
1260 ancs = self.index.commonancestorsheads(*revs)
1260 ancs = self.index.commonancestorsheads(*revs)
1261 except (AttributeError, OverflowError): # C implementation failed
1261 except (AttributeError, OverflowError): # C implementation failed
1262 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1262 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1263 return ancs
1263 return ancs
1264
1264
1265 def isancestor(self, a, b):
1265 def isancestor(self, a, b):
1266 """return True if node a is an ancestor of node b
1266 """return True if node a is an ancestor of node b
1267
1267
1268 A revision is considered an ancestor of itself."""
1268 A revision is considered an ancestor of itself."""
1269 a, b = self.rev(a), self.rev(b)
1269 a, b = self.rev(a), self.rev(b)
1270 return self.isancestorrev(a, b)
1270 return self.isancestorrev(a, b)
1271
1271
1272 def isancestorrev(self, a, b):
1272 def isancestorrev(self, a, b):
1273 """return True if revision a is an ancestor of revision b
1273 """return True if revision a is an ancestor of revision b
1274
1274
1275 A revision is considered an ancestor of itself.
1275 A revision is considered an ancestor of itself.
1276
1276
1277 The implementation of this is trivial but the use of
1277 The implementation of this is trivial but the use of
1278 reachableroots is not."""
1278 reachableroots is not."""
1279 if a == nullrev:
1279 if a == nullrev:
1280 return True
1280 return True
1281 elif a == b:
1281 elif a == b:
1282 return True
1282 return True
1283 elif a > b:
1283 elif a > b:
1284 return False
1284 return False
1285 return bool(self.reachableroots(a, [b], [a], includepath=False))
1285 return bool(self.reachableroots(a, [b], [a], includepath=False))
1286
1286
1287 def reachableroots(self, minroot, heads, roots, includepath=False):
1287 def reachableroots(self, minroot, heads, roots, includepath=False):
1288 """return (heads(::(<roots> and <roots>::<heads>)))
1288 """return (heads(::(<roots> and <roots>::<heads>)))
1289
1289
1290 If includepath is True, return (<roots>::<heads>)."""
1290 If includepath is True, return (<roots>::<heads>)."""
1291 try:
1291 try:
1292 return self.index.reachableroots2(
1292 return self.index.reachableroots2(
1293 minroot, heads, roots, includepath
1293 minroot, heads, roots, includepath
1294 )
1294 )
1295 except AttributeError:
1295 except AttributeError:
1296 return dagop._reachablerootspure(
1296 return dagop._reachablerootspure(
1297 self.parentrevs, minroot, roots, heads, includepath
1297 self.parentrevs, minroot, roots, heads, includepath
1298 )
1298 )
1299
1299
1300 def ancestor(self, a, b):
1300 def ancestor(self, a, b):
1301 """calculate the "best" common ancestor of nodes a and b"""
1301 """calculate the "best" common ancestor of nodes a and b"""
1302
1302
1303 a, b = self.rev(a), self.rev(b)
1303 a, b = self.rev(a), self.rev(b)
1304 try:
1304 try:
1305 ancs = self.index.ancestors(a, b)
1305 ancs = self.index.ancestors(a, b)
1306 except (AttributeError, OverflowError):
1306 except (AttributeError, OverflowError):
1307 ancs = ancestor.ancestors(self.parentrevs, a, b)
1307 ancs = ancestor.ancestors(self.parentrevs, a, b)
1308 if ancs:
1308 if ancs:
1309 # choose a consistent winner when there's a tie
1309 # choose a consistent winner when there's a tie
1310 return min(map(self.node, ancs))
1310 return min(map(self.node, ancs))
1311 return self.nullid
1311 return self.nullid
1312
1312
1313 def _match(self, id):
1313 def _match(self, id):
1314 if isinstance(id, int):
1314 if isinstance(id, int):
1315 # rev
1315 # rev
1316 return self.node(id)
1316 return self.node(id)
1317 if len(id) == self.nodeconstants.nodelen:
1317 if len(id) == self.nodeconstants.nodelen:
1318 # possibly a binary node
1318 # possibly a binary node
1319 # odds of a binary node being all hex in ASCII are 1 in 10**25
1319 # odds of a binary node being all hex in ASCII are 1 in 10**25
1320 try:
1320 try:
1321 node = id
1321 node = id
1322 self.rev(node) # quick search the index
1322 self.rev(node) # quick search the index
1323 return node
1323 return node
1324 except error.LookupError:
1324 except error.LookupError:
1325 pass # may be partial hex id
1325 pass # may be partial hex id
1326 try:
1326 try:
1327 # str(rev)
1327 # str(rev)
1328 rev = int(id)
1328 rev = int(id)
1329 if b"%d" % rev != id:
1329 if b"%d" % rev != id:
1330 raise ValueError
1330 raise ValueError
1331 if rev < 0:
1331 if rev < 0:
1332 rev = len(self) + rev
1332 rev = len(self) + rev
1333 if rev < 0 or rev >= len(self):
1333 if rev < 0 or rev >= len(self):
1334 raise ValueError
1334 raise ValueError
1335 return self.node(rev)
1335 return self.node(rev)
1336 except (ValueError, OverflowError):
1336 except (ValueError, OverflowError):
1337 pass
1337 pass
1338 if len(id) == 2 * self.nodeconstants.nodelen:
1338 if len(id) == 2 * self.nodeconstants.nodelen:
1339 try:
1339 try:
1340 # a full hex nodeid?
1340 # a full hex nodeid?
1341 node = bin(id)
1341 node = bin(id)
1342 self.rev(node)
1342 self.rev(node)
1343 return node
1343 return node
1344 except (TypeError, error.LookupError):
1344 except (TypeError, error.LookupError):
1345 pass
1345 pass
1346
1346
1347 def _partialmatch(self, id):
1347 def _partialmatch(self, id):
1348 # we don't care wdirfilenodeids as they should be always full hash
1348 # we don't care wdirfilenodeids as they should be always full hash
1349 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1349 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1350 try:
1350 try:
1351 partial = self.index.partialmatch(id)
1351 partial = self.index.partialmatch(id)
1352 if partial and self.hasnode(partial):
1352 if partial and self.hasnode(partial):
1353 if maybewdir:
1353 if maybewdir:
1354 # single 'ff...' match in radix tree, ambiguous with wdir
1354 # single 'ff...' match in radix tree, ambiguous with wdir
1355 raise error.RevlogError
1355 raise error.RevlogError
1356 return partial
1356 return partial
1357 if maybewdir:
1357 if maybewdir:
1358 # no 'ff...' match in radix tree, wdir identified
1358 # no 'ff...' match in radix tree, wdir identified
1359 raise error.WdirUnsupported
1359 raise error.WdirUnsupported
1360 return None
1360 return None
1361 except error.RevlogError:
1361 except error.RevlogError:
1362 # parsers.c radix tree lookup gave multiple matches
1362 # parsers.c radix tree lookup gave multiple matches
1363 # fast path: for unfiltered changelog, radix tree is accurate
1363 # fast path: for unfiltered changelog, radix tree is accurate
1364 if not getattr(self, 'filteredrevs', None):
1364 if not getattr(self, 'filteredrevs', None):
1365 raise error.AmbiguousPrefixLookupError(
1365 raise error.AmbiguousPrefixLookupError(
1366 id, self.indexfile, _(b'ambiguous identifier')
1366 id, self.indexfile, _(b'ambiguous identifier')
1367 )
1367 )
1368 # fall through to slow path that filters hidden revisions
1368 # fall through to slow path that filters hidden revisions
1369 except (AttributeError, ValueError):
1369 except (AttributeError, ValueError):
1370 # we are pure python, or key was too short to search radix tree
1370 # we are pure python, or key was too short to search radix tree
1371 pass
1371 pass
1372
1372
1373 if id in self._pcache:
1373 if id in self._pcache:
1374 return self._pcache[id]
1374 return self._pcache[id]
1375
1375
1376 if len(id) <= 40:
1376 if len(id) <= 40:
1377 try:
1377 try:
1378 # hex(node)[:...]
1378 # hex(node)[:...]
1379 l = len(id) // 2 # grab an even number of digits
1379 l = len(id) // 2 # grab an even number of digits
1380 prefix = bin(id[: l * 2])
1380 prefix = bin(id[: l * 2])
1381 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1381 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1382 nl = [
1382 nl = [
1383 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1383 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1384 ]
1384 ]
1385 if self.nodeconstants.nullhex.startswith(id):
1385 if self.nodeconstants.nullhex.startswith(id):
1386 nl.append(self.nullid)
1386 nl.append(self.nullid)
1387 if len(nl) > 0:
1387 if len(nl) > 0:
1388 if len(nl) == 1 and not maybewdir:
1388 if len(nl) == 1 and not maybewdir:
1389 self._pcache[id] = nl[0]
1389 self._pcache[id] = nl[0]
1390 return nl[0]
1390 return nl[0]
1391 raise error.AmbiguousPrefixLookupError(
1391 raise error.AmbiguousPrefixLookupError(
1392 id, self.indexfile, _(b'ambiguous identifier')
1392 id, self.indexfile, _(b'ambiguous identifier')
1393 )
1393 )
1394 if maybewdir:
1394 if maybewdir:
1395 raise error.WdirUnsupported
1395 raise error.WdirUnsupported
1396 return None
1396 return None
1397 except TypeError:
1397 except TypeError:
1398 pass
1398 pass
1399
1399
1400 def lookup(self, id):
1400 def lookup(self, id):
1401 """locate a node based on:
1401 """locate a node based on:
1402 - revision number or str(revision number)
1402 - revision number or str(revision number)
1403 - nodeid or subset of hex nodeid
1403 - nodeid or subset of hex nodeid
1404 """
1404 """
1405 n = self._match(id)
1405 n = self._match(id)
1406 if n is not None:
1406 if n is not None:
1407 return n
1407 return n
1408 n = self._partialmatch(id)
1408 n = self._partialmatch(id)
1409 if n:
1409 if n:
1410 return n
1410 return n
1411
1411
1412 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1412 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1413
1413
1414 def shortest(self, node, minlength=1):
1414 def shortest(self, node, minlength=1):
1415 """Find the shortest unambiguous prefix that matches node."""
1415 """Find the shortest unambiguous prefix that matches node."""
1416
1416
1417 def isvalid(prefix):
1417 def isvalid(prefix):
1418 try:
1418 try:
1419 matchednode = self._partialmatch(prefix)
1419 matchednode = self._partialmatch(prefix)
1420 except error.AmbiguousPrefixLookupError:
1420 except error.AmbiguousPrefixLookupError:
1421 return False
1421 return False
1422 except error.WdirUnsupported:
1422 except error.WdirUnsupported:
1423 # single 'ff...' match
1423 # single 'ff...' match
1424 return True
1424 return True
1425 if matchednode is None:
1425 if matchednode is None:
1426 raise error.LookupError(node, self.indexfile, _(b'no node'))
1426 raise error.LookupError(node, self.indexfile, _(b'no node'))
1427 return True
1427 return True
1428
1428
1429 def maybewdir(prefix):
1429 def maybewdir(prefix):
1430 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1430 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1431
1431
1432 hexnode = hex(node)
1432 hexnode = hex(node)
1433
1433
1434 def disambiguate(hexnode, minlength):
1434 def disambiguate(hexnode, minlength):
1435 """Disambiguate against wdirid."""
1435 """Disambiguate against wdirid."""
1436 for length in range(minlength, len(hexnode) + 1):
1436 for length in range(minlength, len(hexnode) + 1):
1437 prefix = hexnode[:length]
1437 prefix = hexnode[:length]
1438 if not maybewdir(prefix):
1438 if not maybewdir(prefix):
1439 return prefix
1439 return prefix
1440
1440
1441 if not getattr(self, 'filteredrevs', None):
1441 if not getattr(self, 'filteredrevs', None):
1442 try:
1442 try:
1443 length = max(self.index.shortest(node), minlength)
1443 length = max(self.index.shortest(node), minlength)
1444 return disambiguate(hexnode, length)
1444 return disambiguate(hexnode, length)
1445 except error.RevlogError:
1445 except error.RevlogError:
1446 if node != self.nodeconstants.wdirid:
1446 if node != self.nodeconstants.wdirid:
1447 raise error.LookupError(node, self.indexfile, _(b'no node'))
1447 raise error.LookupError(node, self.indexfile, _(b'no node'))
1448 except AttributeError:
1448 except AttributeError:
1449 # Fall through to pure code
1449 # Fall through to pure code
1450 pass
1450 pass
1451
1451
1452 if node == self.nodeconstants.wdirid:
1452 if node == self.nodeconstants.wdirid:
1453 for length in range(minlength, len(hexnode) + 1):
1453 for length in range(minlength, len(hexnode) + 1):
1454 prefix = hexnode[:length]
1454 prefix = hexnode[:length]
1455 if isvalid(prefix):
1455 if isvalid(prefix):
1456 return prefix
1456 return prefix
1457
1457
1458 for length in range(minlength, len(hexnode) + 1):
1458 for length in range(minlength, len(hexnode) + 1):
1459 prefix = hexnode[:length]
1459 prefix = hexnode[:length]
1460 if isvalid(prefix):
1460 if isvalid(prefix):
1461 return disambiguate(hexnode, length)
1461 return disambiguate(hexnode, length)
1462
1462
1463 def cmp(self, node, text):
1463 def cmp(self, node, text):
1464 """compare text with a given file revision
1464 """compare text with a given file revision
1465
1465
1466 returns True if text is different than what is stored.
1466 returns True if text is different than what is stored.
1467 """
1467 """
1468 p1, p2 = self.parents(node)
1468 p1, p2 = self.parents(node)
1469 return storageutil.hashrevisionsha1(text, p1, p2) != node
1469 return storageutil.hashrevisionsha1(text, p1, p2) != node
1470
1470
1471 def _cachesegment(self, offset, data):
1471 def _cachesegment(self, offset, data):
1472 """Add a segment to the revlog cache.
1472 """Add a segment to the revlog cache.
1473
1473
1474 Accepts an absolute offset and the data that is at that location.
1474 Accepts an absolute offset and the data that is at that location.
1475 """
1475 """
1476 o, d = self._chunkcache
1476 o, d = self._chunkcache
1477 # try to add to existing cache
1477 # try to add to existing cache
1478 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1478 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1479 self._chunkcache = o, d + data
1479 self._chunkcache = o, d + data
1480 else:
1480 else:
1481 self._chunkcache = offset, data
1481 self._chunkcache = offset, data
1482
1482
1483 def _readsegment(self, offset, length, df=None):
1483 def _readsegment(self, offset, length, df=None):
1484 """Load a segment of raw data from the revlog.
1484 """Load a segment of raw data from the revlog.
1485
1485
1486 Accepts an absolute offset, length to read, and an optional existing
1486 Accepts an absolute offset, length to read, and an optional existing
1487 file handle to read from.
1487 file handle to read from.
1488
1488
1489 If an existing file handle is passed, it will be seeked and the
1489 If an existing file handle is passed, it will be seeked and the
1490 original seek position will NOT be restored.
1490 original seek position will NOT be restored.
1491
1491
1492 Returns a str or buffer of raw byte data.
1492 Returns a str or buffer of raw byte data.
1493
1493
1494 Raises if the requested number of bytes could not be read.
1494 Raises if the requested number of bytes could not be read.
1495 """
1495 """
1496 # Cache data both forward and backward around the requested
1496 # Cache data both forward and backward around the requested
1497 # data, in a fixed size window. This helps speed up operations
1497 # data, in a fixed size window. This helps speed up operations
1498 # involving reading the revlog backwards.
1498 # involving reading the revlog backwards.
1499 cachesize = self._chunkcachesize
1499 cachesize = self._chunkcachesize
1500 realoffset = offset & ~(cachesize - 1)
1500 realoffset = offset & ~(cachesize - 1)
1501 reallength = (
1501 reallength = (
1502 (offset + length + cachesize) & ~(cachesize - 1)
1502 (offset + length + cachesize) & ~(cachesize - 1)
1503 ) - realoffset
1503 ) - realoffset
1504 with self._datareadfp(df) as df:
1504 with self._datareadfp(df) as df:
1505 df.seek(realoffset)
1505 df.seek(realoffset)
1506 d = df.read(reallength)
1506 d = df.read(reallength)
1507
1507
1508 self._cachesegment(realoffset, d)
1508 self._cachesegment(realoffset, d)
1509 if offset != realoffset or reallength != length:
1509 if offset != realoffset or reallength != length:
1510 startoffset = offset - realoffset
1510 startoffset = offset - realoffset
1511 if len(d) - startoffset < length:
1511 if len(d) - startoffset < length:
1512 raise error.RevlogError(
1512 raise error.RevlogError(
1513 _(
1513 _(
1514 b'partial read of revlog %s; expected %d bytes from '
1514 b'partial read of revlog %s; expected %d bytes from '
1515 b'offset %d, got %d'
1515 b'offset %d, got %d'
1516 )
1516 )
1517 % (
1517 % (
1518 self.indexfile if self._inline else self.datafile,
1518 self.indexfile if self._inline else self.datafile,
1519 length,
1519 length,
1520 realoffset,
1520 realoffset,
1521 len(d) - startoffset,
1521 len(d) - startoffset,
1522 )
1522 )
1523 )
1523 )
1524
1524
1525 return util.buffer(d, startoffset, length)
1525 return util.buffer(d, startoffset, length)
1526
1526
1527 if len(d) < length:
1527 if len(d) < length:
1528 raise error.RevlogError(
1528 raise error.RevlogError(
1529 _(
1529 _(
1530 b'partial read of revlog %s; expected %d bytes from offset '
1530 b'partial read of revlog %s; expected %d bytes from offset '
1531 b'%d, got %d'
1531 b'%d, got %d'
1532 )
1532 )
1533 % (
1533 % (
1534 self.indexfile if self._inline else self.datafile,
1534 self.indexfile if self._inline else self.datafile,
1535 length,
1535 length,
1536 offset,
1536 offset,
1537 len(d),
1537 len(d),
1538 )
1538 )
1539 )
1539 )
1540
1540
1541 return d
1541 return d
1542
1542
1543 def _getsegment(self, offset, length, df=None):
1543 def _getsegment(self, offset, length, df=None):
1544 """Obtain a segment of raw data from the revlog.
1544 """Obtain a segment of raw data from the revlog.
1545
1545
1546 Accepts an absolute offset, length of bytes to obtain, and an
1546 Accepts an absolute offset, length of bytes to obtain, and an
1547 optional file handle to the already-opened revlog. If the file
1547 optional file handle to the already-opened revlog. If the file
1548 handle is used, it's original seek position will not be preserved.
1548 handle is used, it's original seek position will not be preserved.
1549
1549
1550 Requests for data may be returned from a cache.
1550 Requests for data may be returned from a cache.
1551
1551
1552 Returns a str or a buffer instance of raw byte data.
1552 Returns a str or a buffer instance of raw byte data.
1553 """
1553 """
1554 o, d = self._chunkcache
1554 o, d = self._chunkcache
1555 l = len(d)
1555 l = len(d)
1556
1556
1557 # is it in the cache?
1557 # is it in the cache?
1558 cachestart = offset - o
1558 cachestart = offset - o
1559 cacheend = cachestart + length
1559 cacheend = cachestart + length
1560 if cachestart >= 0 and cacheend <= l:
1560 if cachestart >= 0 and cacheend <= l:
1561 if cachestart == 0 and cacheend == l:
1561 if cachestart == 0 and cacheend == l:
1562 return d # avoid a copy
1562 return d # avoid a copy
1563 return util.buffer(d, cachestart, cacheend - cachestart)
1563 return util.buffer(d, cachestart, cacheend - cachestart)
1564
1564
1565 return self._readsegment(offset, length, df=df)
1565 return self._readsegment(offset, length, df=df)
1566
1566
1567 def _getsegmentforrevs(self, startrev, endrev, df=None):
1567 def _getsegmentforrevs(self, startrev, endrev, df=None):
1568 """Obtain a segment of raw data corresponding to a range of revisions.
1568 """Obtain a segment of raw data corresponding to a range of revisions.
1569
1569
1570 Accepts the start and end revisions and an optional already-open
1570 Accepts the start and end revisions and an optional already-open
1571 file handle to be used for reading. If the file handle is read, its
1571 file handle to be used for reading. If the file handle is read, its
1572 seek position will not be preserved.
1572 seek position will not be preserved.
1573
1573
1574 Requests for data may be satisfied by a cache.
1574 Requests for data may be satisfied by a cache.
1575
1575
1576 Returns a 2-tuple of (offset, data) for the requested range of
1576 Returns a 2-tuple of (offset, data) for the requested range of
1577 revisions. Offset is the integer offset from the beginning of the
1577 revisions. Offset is the integer offset from the beginning of the
1578 revlog and data is a str or buffer of the raw byte data.
1578 revlog and data is a str or buffer of the raw byte data.
1579
1579
1580 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1580 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1581 to determine where each revision's data begins and ends.
1581 to determine where each revision's data begins and ends.
1582 """
1582 """
1583 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1583 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1584 # (functions are expensive).
1584 # (functions are expensive).
1585 index = self.index
1585 index = self.index
1586 istart = index[startrev]
1586 istart = index[startrev]
1587 start = int(istart[0] >> 16)
1587 start = int(istart[0] >> 16)
1588 if startrev == endrev:
1588 if startrev == endrev:
1589 end = start + istart[1]
1589 end = start + istart[1]
1590 else:
1590 else:
1591 iend = index[endrev]
1591 iend = index[endrev]
1592 end = int(iend[0] >> 16) + iend[1]
1592 end = int(iend[0] >> 16) + iend[1]
1593
1593
1594 if self._inline:
1594 if self._inline:
1595 start += (startrev + 1) * self.index.entry_size
1595 start += (startrev + 1) * self.index.entry_size
1596 end += (endrev + 1) * self.index.entry_size
1596 end += (endrev + 1) * self.index.entry_size
1597 length = end - start
1597 length = end - start
1598
1598
1599 return start, self._getsegment(start, length, df=df)
1599 return start, self._getsegment(start, length, df=df)
1600
1600
1601 def _chunk(self, rev, df=None):
1601 def _chunk(self, rev, df=None):
1602 """Obtain a single decompressed chunk for a revision.
1602 """Obtain a single decompressed chunk for a revision.
1603
1603
1604 Accepts an integer revision and an optional already-open file handle
1604 Accepts an integer revision and an optional already-open file handle
1605 to be used for reading. If used, the seek position of the file will not
1605 to be used for reading. If used, the seek position of the file will not
1606 be preserved.
1606 be preserved.
1607
1607
1608 Returns a str holding uncompressed data for the requested revision.
1608 Returns a str holding uncompressed data for the requested revision.
1609 """
1609 """
1610 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1610 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1611
1611
1612 def _chunks(self, revs, df=None, targetsize=None):
1612 def _chunks(self, revs, df=None, targetsize=None):
1613 """Obtain decompressed chunks for the specified revisions.
1613 """Obtain decompressed chunks for the specified revisions.
1614
1614
1615 Accepts an iterable of numeric revisions that are assumed to be in
1615 Accepts an iterable of numeric revisions that are assumed to be in
1616 ascending order. Also accepts an optional already-open file handle
1616 ascending order. Also accepts an optional already-open file handle
1617 to be used for reading. If used, the seek position of the file will
1617 to be used for reading. If used, the seek position of the file will
1618 not be preserved.
1618 not be preserved.
1619
1619
1620 This function is similar to calling ``self._chunk()`` multiple times,
1620 This function is similar to calling ``self._chunk()`` multiple times,
1621 but is faster.
1621 but is faster.
1622
1622
1623 Returns a list with decompressed data for each requested revision.
1623 Returns a list with decompressed data for each requested revision.
1624 """
1624 """
1625 if not revs:
1625 if not revs:
1626 return []
1626 return []
1627 start = self.start
1627 start = self.start
1628 length = self.length
1628 length = self.length
1629 inline = self._inline
1629 inline = self._inline
1630 iosize = self.index.entry_size
1630 iosize = self.index.entry_size
1631 buffer = util.buffer
1631 buffer = util.buffer
1632
1632
1633 l = []
1633 l = []
1634 ladd = l.append
1634 ladd = l.append
1635
1635
1636 if not self._withsparseread:
1636 if not self._withsparseread:
1637 slicedchunks = (revs,)
1637 slicedchunks = (revs,)
1638 else:
1638 else:
1639 slicedchunks = deltautil.slicechunk(
1639 slicedchunks = deltautil.slicechunk(
1640 self, revs, targetsize=targetsize
1640 self, revs, targetsize=targetsize
1641 )
1641 )
1642
1642
1643 for revschunk in slicedchunks:
1643 for revschunk in slicedchunks:
1644 firstrev = revschunk[0]
1644 firstrev = revschunk[0]
1645 # Skip trailing revisions with empty diff
1645 # Skip trailing revisions with empty diff
1646 for lastrev in revschunk[::-1]:
1646 for lastrev in revschunk[::-1]:
1647 if length(lastrev) != 0:
1647 if length(lastrev) != 0:
1648 break
1648 break
1649
1649
1650 try:
1650 try:
1651 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1651 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1652 except OverflowError:
1652 except OverflowError:
1653 # issue4215 - we can't cache a run of chunks greater than
1653 # issue4215 - we can't cache a run of chunks greater than
1654 # 2G on Windows
1654 # 2G on Windows
1655 return [self._chunk(rev, df=df) for rev in revschunk]
1655 return [self._chunk(rev, df=df) for rev in revschunk]
1656
1656
1657 decomp = self.decompress
1657 decomp = self.decompress
1658 for rev in revschunk:
1658 for rev in revschunk:
1659 chunkstart = start(rev)
1659 chunkstart = start(rev)
1660 if inline:
1660 if inline:
1661 chunkstart += (rev + 1) * iosize
1661 chunkstart += (rev + 1) * iosize
1662 chunklength = length(rev)
1662 chunklength = length(rev)
1663 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1663 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1664
1664
1665 return l
1665 return l
1666
1666
1667 def _chunkclear(self):
1667 def _chunkclear(self):
1668 """Clear the raw chunk cache."""
1668 """Clear the raw chunk cache."""
1669 self._chunkcache = (0, b'')
1669 self._chunkcache = (0, b'')
1670
1670
1671 def deltaparent(self, rev):
1671 def deltaparent(self, rev):
1672 """return deltaparent of the given revision"""
1672 """return deltaparent of the given revision"""
1673 base = self.index[rev][3]
1673 base = self.index[rev][3]
1674 if base == rev:
1674 if base == rev:
1675 return nullrev
1675 return nullrev
1676 elif self._generaldelta:
1676 elif self._generaldelta:
1677 return base
1677 return base
1678 else:
1678 else:
1679 return rev - 1
1679 return rev - 1
1680
1680
1681 def issnapshot(self, rev):
1681 def issnapshot(self, rev):
1682 """tells whether rev is a snapshot"""
1682 """tells whether rev is a snapshot"""
1683 if not self._sparserevlog:
1683 if not self._sparserevlog:
1684 return self.deltaparent(rev) == nullrev
1684 return self.deltaparent(rev) == nullrev
1685 elif util.safehasattr(self.index, b'issnapshot'):
1685 elif util.safehasattr(self.index, b'issnapshot'):
1686 # directly assign the method to cache the testing and access
1686 # directly assign the method to cache the testing and access
1687 self.issnapshot = self.index.issnapshot
1687 self.issnapshot = self.index.issnapshot
1688 return self.issnapshot(rev)
1688 return self.issnapshot(rev)
1689 if rev == nullrev:
1689 if rev == nullrev:
1690 return True
1690 return True
1691 entry = self.index[rev]
1691 entry = self.index[rev]
1692 base = entry[3]
1692 base = entry[3]
1693 if base == rev:
1693 if base == rev:
1694 return True
1694 return True
1695 if base == nullrev:
1695 if base == nullrev:
1696 return True
1696 return True
1697 p1 = entry[5]
1697 p1 = entry[5]
1698 p2 = entry[6]
1698 p2 = entry[6]
1699 if base == p1 or base == p2:
1699 if base == p1 or base == p2:
1700 return False
1700 return False
1701 return self.issnapshot(base)
1701 return self.issnapshot(base)
1702
1702
1703 def snapshotdepth(self, rev):
1703 def snapshotdepth(self, rev):
1704 """number of snapshot in the chain before this one"""
1704 """number of snapshot in the chain before this one"""
1705 if not self.issnapshot(rev):
1705 if not self.issnapshot(rev):
1706 raise error.ProgrammingError(b'revision %d not a snapshot')
1706 raise error.ProgrammingError(b'revision %d not a snapshot')
1707 return len(self._deltachain(rev)[0]) - 1
1707 return len(self._deltachain(rev)[0]) - 1
1708
1708
1709 def revdiff(self, rev1, rev2):
1709 def revdiff(self, rev1, rev2):
1710 """return or calculate a delta between two revisions
1710 """return or calculate a delta between two revisions
1711
1711
1712 The delta calculated is in binary form and is intended to be written to
1712 The delta calculated is in binary form and is intended to be written to
1713 revlog data directly. So this function needs raw revision data.
1713 revlog data directly. So this function needs raw revision data.
1714 """
1714 """
1715 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1715 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1716 return bytes(self._chunk(rev2))
1716 return bytes(self._chunk(rev2))
1717
1717
1718 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1718 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1719
1719
1720 def _processflags(self, text, flags, operation, raw=False):
1720 def _processflags(self, text, flags, operation, raw=False):
1721 """deprecated entry point to access flag processors"""
1721 """deprecated entry point to access flag processors"""
1722 msg = b'_processflag(...) use the specialized variant'
1722 msg = b'_processflag(...) use the specialized variant'
1723 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1723 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1724 if raw:
1724 if raw:
1725 return text, flagutil.processflagsraw(self, text, flags)
1725 return text, flagutil.processflagsraw(self, text, flags)
1726 elif operation == b'read':
1726 elif operation == b'read':
1727 return flagutil.processflagsread(self, text, flags)
1727 return flagutil.processflagsread(self, text, flags)
1728 else: # write operation
1728 else: # write operation
1729 return flagutil.processflagswrite(self, text, flags)
1729 return flagutil.processflagswrite(self, text, flags)
1730
1730
1731 def revision(self, nodeorrev, _df=None, raw=False):
1731 def revision(self, nodeorrev, _df=None, raw=False):
1732 """return an uncompressed revision of a given node or revision
1732 """return an uncompressed revision of a given node or revision
1733 number.
1733 number.
1734
1734
1735 _df - an existing file handle to read from. (internal-only)
1735 _df - an existing file handle to read from. (internal-only)
1736 raw - an optional argument specifying if the revision data is to be
1736 raw - an optional argument specifying if the revision data is to be
1737 treated as raw data when applying flag transforms. 'raw' should be set
1737 treated as raw data when applying flag transforms. 'raw' should be set
1738 to True when generating changegroups or in debug commands.
1738 to True when generating changegroups or in debug commands.
1739 """
1739 """
1740 if raw:
1740 if raw:
1741 msg = (
1741 msg = (
1742 b'revlog.revision(..., raw=True) is deprecated, '
1742 b'revlog.revision(..., raw=True) is deprecated, '
1743 b'use revlog.rawdata(...)'
1743 b'use revlog.rawdata(...)'
1744 )
1744 )
1745 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1745 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1746 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1746 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1747
1747
1748 def sidedata(self, nodeorrev, _df=None):
1748 def sidedata(self, nodeorrev, _df=None):
1749 """a map of extra data related to the changeset but not part of the hash
1749 """a map of extra data related to the changeset but not part of the hash
1750
1750
1751 This function currently return a dictionary. However, more advanced
1751 This function currently return a dictionary. However, more advanced
1752 mapping object will likely be used in the future for a more
1752 mapping object will likely be used in the future for a more
1753 efficient/lazy code.
1753 efficient/lazy code.
1754 """
1754 """
1755 return self._revisiondata(nodeorrev, _df)[1]
1755 return self._revisiondata(nodeorrev, _df)[1]
1756
1756
1757 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1757 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1758 # deal with <nodeorrev> argument type
1758 # deal with <nodeorrev> argument type
1759 if isinstance(nodeorrev, int):
1759 if isinstance(nodeorrev, int):
1760 rev = nodeorrev
1760 rev = nodeorrev
1761 node = self.node(rev)
1761 node = self.node(rev)
1762 else:
1762 else:
1763 node = nodeorrev
1763 node = nodeorrev
1764 rev = None
1764 rev = None
1765
1765
1766 # fast path the special `nullid` rev
1766 # fast path the special `nullid` rev
1767 if node == self.nullid:
1767 if node == self.nullid:
1768 return b"", {}
1768 return b"", {}
1769
1769
1770 # ``rawtext`` is the text as stored inside the revlog. Might be the
1770 # ``rawtext`` is the text as stored inside the revlog. Might be the
1771 # revision or might need to be processed to retrieve the revision.
1771 # revision or might need to be processed to retrieve the revision.
1772 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1772 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1773
1773
1774 if self.version & 0xFFFF == REVLOGV2:
1774 if self.version & 0xFFFF == REVLOGV2:
1775 if rev is None:
1775 if rev is None:
1776 rev = self.rev(node)
1776 rev = self.rev(node)
1777 sidedata = self._sidedata(rev)
1777 sidedata = self._sidedata(rev)
1778 else:
1778 else:
1779 sidedata = {}
1779 sidedata = {}
1780
1780
1781 if raw and validated:
1781 if raw and validated:
1782 # if we don't want to process the raw text and that raw
1782 # if we don't want to process the raw text and that raw
1783 # text is cached, we can exit early.
1783 # text is cached, we can exit early.
1784 return rawtext, sidedata
1784 return rawtext, sidedata
1785 if rev is None:
1785 if rev is None:
1786 rev = self.rev(node)
1786 rev = self.rev(node)
1787 # the revlog's flag for this revision
1787 # the revlog's flag for this revision
1788 # (usually alter its state or content)
1788 # (usually alter its state or content)
1789 flags = self.flags(rev)
1789 flags = self.flags(rev)
1790
1790
1791 if validated and flags == REVIDX_DEFAULT_FLAGS:
1791 if validated and flags == REVIDX_DEFAULT_FLAGS:
1792 # no extra flags set, no flag processor runs, text = rawtext
1792 # no extra flags set, no flag processor runs, text = rawtext
1793 return rawtext, sidedata
1793 return rawtext, sidedata
1794
1794
1795 if raw:
1795 if raw:
1796 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1796 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1797 text = rawtext
1797 text = rawtext
1798 else:
1798 else:
1799 r = flagutil.processflagsread(self, rawtext, flags)
1799 r = flagutil.processflagsread(self, rawtext, flags)
1800 text, validatehash = r
1800 text, validatehash = r
1801 if validatehash:
1801 if validatehash:
1802 self.checkhash(text, node, rev=rev)
1802 self.checkhash(text, node, rev=rev)
1803 if not validated:
1803 if not validated:
1804 self._revisioncache = (node, rev, rawtext)
1804 self._revisioncache = (node, rev, rawtext)
1805
1805
1806 return text, sidedata
1806 return text, sidedata
1807
1807
1808 def _rawtext(self, node, rev, _df=None):
1808 def _rawtext(self, node, rev, _df=None):
1809 """return the possibly unvalidated rawtext for a revision
1809 """return the possibly unvalidated rawtext for a revision
1810
1810
1811 returns (rev, rawtext, validated)
1811 returns (rev, rawtext, validated)
1812 """
1812 """
1813
1813
1814 # revision in the cache (could be useful to apply delta)
1814 # revision in the cache (could be useful to apply delta)
1815 cachedrev = None
1815 cachedrev = None
1816 # An intermediate text to apply deltas to
1816 # An intermediate text to apply deltas to
1817 basetext = None
1817 basetext = None
1818
1818
1819 # Check if we have the entry in cache
1819 # Check if we have the entry in cache
1820 # The cache entry looks like (node, rev, rawtext)
1820 # The cache entry looks like (node, rev, rawtext)
1821 if self._revisioncache:
1821 if self._revisioncache:
1822 if self._revisioncache[0] == node:
1822 if self._revisioncache[0] == node:
1823 return (rev, self._revisioncache[2], True)
1823 return (rev, self._revisioncache[2], True)
1824 cachedrev = self._revisioncache[1]
1824 cachedrev = self._revisioncache[1]
1825
1825
1826 if rev is None:
1826 if rev is None:
1827 rev = self.rev(node)
1827 rev = self.rev(node)
1828
1828
1829 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1829 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1830 if stopped:
1830 if stopped:
1831 basetext = self._revisioncache[2]
1831 basetext = self._revisioncache[2]
1832
1832
1833 # drop cache to save memory, the caller is expected to
1833 # drop cache to save memory, the caller is expected to
1834 # update self._revisioncache after validating the text
1834 # update self._revisioncache after validating the text
1835 self._revisioncache = None
1835 self._revisioncache = None
1836
1836
1837 targetsize = None
1837 targetsize = None
1838 rawsize = self.index[rev][2]
1838 rawsize = self.index[rev][2]
1839 if 0 <= rawsize:
1839 if 0 <= rawsize:
1840 targetsize = 4 * rawsize
1840 targetsize = 4 * rawsize
1841
1841
1842 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1842 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1843 if basetext is None:
1843 if basetext is None:
1844 basetext = bytes(bins[0])
1844 basetext = bytes(bins[0])
1845 bins = bins[1:]
1845 bins = bins[1:]
1846
1846
1847 rawtext = mdiff.patches(basetext, bins)
1847 rawtext = mdiff.patches(basetext, bins)
1848 del basetext # let us have a chance to free memory early
1848 del basetext # let us have a chance to free memory early
1849 return (rev, rawtext, False)
1849 return (rev, rawtext, False)
1850
1850
1851 def _sidedata(self, rev):
1851 def _sidedata(self, rev):
1852 """Return the sidedata for a given revision number."""
1852 """Return the sidedata for a given revision number."""
1853 index_entry = self.index[rev]
1853 index_entry = self.index[rev]
1854 sidedata_offset = index_entry[8]
1854 sidedata_offset = index_entry[8]
1855 sidedata_size = index_entry[9]
1855 sidedata_size = index_entry[9]
1856
1856
1857 if self._inline:
1857 if self._inline:
1858 sidedata_offset += self.index.entry_size * (1 + rev)
1858 sidedata_offset += self.index.entry_size * (1 + rev)
1859 if sidedata_size == 0:
1859 if sidedata_size == 0:
1860 return {}
1860 return {}
1861
1861
1862 segment = self._getsegment(sidedata_offset, sidedata_size)
1862 segment = self._getsegment(sidedata_offset, sidedata_size)
1863 sidedata = sidedatautil.deserialize_sidedata(segment)
1863 sidedata = sidedatautil.deserialize_sidedata(segment)
1864 return sidedata
1864 return sidedata
1865
1865
1866 def rawdata(self, nodeorrev, _df=None):
1866 def rawdata(self, nodeorrev, _df=None):
1867 """return an uncompressed raw data of a given node or revision number.
1867 """return an uncompressed raw data of a given node or revision number.
1868
1868
1869 _df - an existing file handle to read from. (internal-only)
1869 _df - an existing file handle to read from. (internal-only)
1870 """
1870 """
1871 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1871 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1872
1872
1873 def hash(self, text, p1, p2):
1873 def hash(self, text, p1, p2):
1874 """Compute a node hash.
1874 """Compute a node hash.
1875
1875
1876 Available as a function so that subclasses can replace the hash
1876 Available as a function so that subclasses can replace the hash
1877 as needed.
1877 as needed.
1878 """
1878 """
1879 return storageutil.hashrevisionsha1(text, p1, p2)
1879 return storageutil.hashrevisionsha1(text, p1, p2)
1880
1880
1881 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1881 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1882 """Check node hash integrity.
1882 """Check node hash integrity.
1883
1883
1884 Available as a function so that subclasses can extend hash mismatch
1884 Available as a function so that subclasses can extend hash mismatch
1885 behaviors as needed.
1885 behaviors as needed.
1886 """
1886 """
1887 try:
1887 try:
1888 if p1 is None and p2 is None:
1888 if p1 is None and p2 is None:
1889 p1, p2 = self.parents(node)
1889 p1, p2 = self.parents(node)
1890 if node != self.hash(text, p1, p2):
1890 if node != self.hash(text, p1, p2):
1891 # Clear the revision cache on hash failure. The revision cache
1891 # Clear the revision cache on hash failure. The revision cache
1892 # only stores the raw revision and clearing the cache does have
1892 # only stores the raw revision and clearing the cache does have
1893 # the side-effect that we won't have a cache hit when the raw
1893 # the side-effect that we won't have a cache hit when the raw
1894 # revision data is accessed. But this case should be rare and
1894 # revision data is accessed. But this case should be rare and
1895 # it is extra work to teach the cache about the hash
1895 # it is extra work to teach the cache about the hash
1896 # verification state.
1896 # verification state.
1897 if self._revisioncache and self._revisioncache[0] == node:
1897 if self._revisioncache and self._revisioncache[0] == node:
1898 self._revisioncache = None
1898 self._revisioncache = None
1899
1899
1900 revornode = rev
1900 revornode = rev
1901 if revornode is None:
1901 if revornode is None:
1902 revornode = templatefilters.short(hex(node))
1902 revornode = templatefilters.short(hex(node))
1903 raise error.RevlogError(
1903 raise error.RevlogError(
1904 _(b"integrity check failed on %s:%s")
1904 _(b"integrity check failed on %s:%s")
1905 % (self.indexfile, pycompat.bytestr(revornode))
1905 % (self.indexfile, pycompat.bytestr(revornode))
1906 )
1906 )
1907 except error.RevlogError:
1907 except error.RevlogError:
1908 if self._censorable and storageutil.iscensoredtext(text):
1908 if self._censorable and storageutil.iscensoredtext(text):
1909 raise error.CensoredNodeError(self.indexfile, node, text)
1909 raise error.CensoredNodeError(self.indexfile, node, text)
1910 raise
1910 raise
1911
1911
1912 def _enforceinlinesize(self, tr, fp=None):
1912 def _enforceinlinesize(self, tr, fp=None):
1913 """Check if the revlog is too big for inline and convert if so.
1913 """Check if the revlog is too big for inline and convert if so.
1914
1914
1915 This should be called after revisions are added to the revlog. If the
1915 This should be called after revisions are added to the revlog. If the
1916 revlog has grown too large to be an inline revlog, it will convert it
1916 revlog has grown too large to be an inline revlog, it will convert it
1917 to use multiple index and data files.
1917 to use multiple index and data files.
1918 """
1918 """
1919 tiprev = len(self) - 1
1919 tiprev = len(self) - 1
1920 if (
1920 if (
1921 not self._inline
1921 not self._inline
1922 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1922 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1923 ):
1923 ):
1924 return
1924 return
1925
1925
1926 troffset = tr.findoffset(self.indexfile)
1926 troffset = tr.findoffset(self.indexfile)
1927 if troffset is None:
1927 if troffset is None:
1928 raise error.RevlogError(
1928 raise error.RevlogError(
1929 _(b"%s not found in the transaction") % self.indexfile
1929 _(b"%s not found in the transaction") % self.indexfile
1930 )
1930 )
1931 trindex = 0
1931 trindex = 0
1932 tr.add(self.datafile, 0)
1932 tr.add(self.datafile, 0)
1933
1933
1934 if fp:
1934 if fp:
1935 fp.flush()
1935 fp.flush()
1936 fp.close()
1936 fp.close()
1937 # We can't use the cached file handle after close(). So prevent
1937 # We can't use the cached file handle after close(). So prevent
1938 # its usage.
1938 # its usage.
1939 self._writinghandles = None
1939 self._writinghandles = None
1940
1940
1941 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
1941 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
1942 for r in self:
1942 for r in self:
1943 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1943 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1944 if troffset <= self.start(r):
1944 if troffset <= self.start(r):
1945 trindex = r
1945 trindex = r
1946
1946
1947 with self._indexfp(b'w') as fp:
1947 with self._indexfp(b'w') as fp:
1948 self.version &= ~FLAG_INLINE_DATA
1948 self.version &= ~FLAG_INLINE_DATA
1949 self._inline = False
1949 self._inline = False
1950 for i in self:
1950 for i in self:
1951 e = self.index.entry_binary(i)
1951 e = self.index.entry_binary(i)
1952 if i == 0:
1952 if i == 0:
1953 header = self.index.pack_header(self.version)
1953 header = self.index.pack_header(self.version)
1954 e = header + e
1954 e = header + e
1955 fp.write(e)
1955 fp.write(e)
1956
1956
1957 # the temp file replace the real index when we exit the context
1957 # the temp file replace the real index when we exit the context
1958 # manager
1958 # manager
1959
1959
1960 tr.replace(self.indexfile, trindex * self.index.entry_size)
1960 tr.replace(self.indexfile, trindex * self.index.entry_size)
1961 nodemaputil.setup_persistent_nodemap(tr, self)
1961 nodemaputil.setup_persistent_nodemap(tr, self)
1962 self._chunkclear()
1962 self._chunkclear()
1963
1963
1964 def _nodeduplicatecallback(self, transaction, node):
1964 def _nodeduplicatecallback(self, transaction, node):
1965 """called when trying to add a node already stored."""
1965 """called when trying to add a node already stored."""
1966
1966
1967 def addrevision(
1967 def addrevision(
1968 self,
1968 self,
1969 text,
1969 text,
1970 transaction,
1970 transaction,
1971 link,
1971 link,
1972 p1,
1972 p1,
1973 p2,
1973 p2,
1974 cachedelta=None,
1974 cachedelta=None,
1975 node=None,
1975 node=None,
1976 flags=REVIDX_DEFAULT_FLAGS,
1976 flags=REVIDX_DEFAULT_FLAGS,
1977 deltacomputer=None,
1977 deltacomputer=None,
1978 sidedata=None,
1978 sidedata=None,
1979 ):
1979 ):
1980 """add a revision to the log
1980 """add a revision to the log
1981
1981
1982 text - the revision data to add
1982 text - the revision data to add
1983 transaction - the transaction object used for rollback
1983 transaction - the transaction object used for rollback
1984 link - the linkrev data to add
1984 link - the linkrev data to add
1985 p1, p2 - the parent nodeids of the revision
1985 p1, p2 - the parent nodeids of the revision
1986 cachedelta - an optional precomputed delta
1986 cachedelta - an optional precomputed delta
1987 node - nodeid of revision; typically node is not specified, and it is
1987 node - nodeid of revision; typically node is not specified, and it is
1988 computed by default as hash(text, p1, p2), however subclasses might
1988 computed by default as hash(text, p1, p2), however subclasses might
1989 use different hashing method (and override checkhash() in such case)
1989 use different hashing method (and override checkhash() in such case)
1990 flags - the known flags to set on the revision
1990 flags - the known flags to set on the revision
1991 deltacomputer - an optional deltacomputer instance shared between
1991 deltacomputer - an optional deltacomputer instance shared between
1992 multiple calls
1992 multiple calls
1993 """
1993 """
1994 if link == nullrev:
1994 if link == nullrev:
1995 raise error.RevlogError(
1995 raise error.RevlogError(
1996 _(b"attempted to add linkrev -1 to %s") % self.indexfile
1996 _(b"attempted to add linkrev -1 to %s") % self.indexfile
1997 )
1997 )
1998
1998
1999 if sidedata is None:
1999 if sidedata is None:
2000 sidedata = {}
2000 sidedata = {}
2001 elif sidedata and not self.hassidedata:
2001 elif sidedata and not self.hassidedata:
2002 raise error.ProgrammingError(
2002 raise error.ProgrammingError(
2003 _(b"trying to add sidedata to a revlog who don't support them")
2003 _(b"trying to add sidedata to a revlog who don't support them")
2004 )
2004 )
2005
2005
2006 if flags:
2006 if flags:
2007 node = node or self.hash(text, p1, p2)
2007 node = node or self.hash(text, p1, p2)
2008
2008
2009 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2009 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2010
2010
2011 # If the flag processor modifies the revision data, ignore any provided
2011 # If the flag processor modifies the revision data, ignore any provided
2012 # cachedelta.
2012 # cachedelta.
2013 if rawtext != text:
2013 if rawtext != text:
2014 cachedelta = None
2014 cachedelta = None
2015
2015
2016 if len(rawtext) > _maxentrysize:
2016 if len(rawtext) > _maxentrysize:
2017 raise error.RevlogError(
2017 raise error.RevlogError(
2018 _(
2018 _(
2019 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2019 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2020 )
2020 )
2021 % (self.indexfile, len(rawtext))
2021 % (self.indexfile, len(rawtext))
2022 )
2022 )
2023
2023
2024 node = node or self.hash(rawtext, p1, p2)
2024 node = node or self.hash(rawtext, p1, p2)
2025 rev = self.index.get_rev(node)
2025 rev = self.index.get_rev(node)
2026 if rev is not None:
2026 if rev is not None:
2027 return rev
2027 return rev
2028
2028
2029 if validatehash:
2029 if validatehash:
2030 self.checkhash(rawtext, node, p1=p1, p2=p2)
2030 self.checkhash(rawtext, node, p1=p1, p2=p2)
2031
2031
2032 return self.addrawrevision(
2032 return self.addrawrevision(
2033 rawtext,
2033 rawtext,
2034 transaction,
2034 transaction,
2035 link,
2035 link,
2036 p1,
2036 p1,
2037 p2,
2037 p2,
2038 node,
2038 node,
2039 flags,
2039 flags,
2040 cachedelta=cachedelta,
2040 cachedelta=cachedelta,
2041 deltacomputer=deltacomputer,
2041 deltacomputer=deltacomputer,
2042 sidedata=sidedata,
2042 sidedata=sidedata,
2043 )
2043 )
2044
2044
2045 def addrawrevision(
2045 def addrawrevision(
2046 self,
2046 self,
2047 rawtext,
2047 rawtext,
2048 transaction,
2048 transaction,
2049 link,
2049 link,
2050 p1,
2050 p1,
2051 p2,
2051 p2,
2052 node,
2052 node,
2053 flags,
2053 flags,
2054 cachedelta=None,
2054 cachedelta=None,
2055 deltacomputer=None,
2055 deltacomputer=None,
2056 sidedata=None,
2056 sidedata=None,
2057 ):
2057 ):
2058 """add a raw revision with known flags, node and parents
2058 """add a raw revision with known flags, node and parents
2059 useful when reusing a revision not stored in this revlog (ex: received
2059 useful when reusing a revision not stored in this revlog (ex: received
2060 over wire, or read from an external bundle).
2060 over wire, or read from an external bundle).
2061 """
2061 """
2062 dfh = None
2062 dfh = None
2063 if not self._inline:
2063 if not self._inline:
2064 dfh = self._datafp(b"a+")
2064 dfh = self._datafp(b"a+")
2065 ifh = self._indexfp(b"a+")
2065 ifh = self._indexfp(b"a+")
2066 try:
2066 try:
2067 return self._addrevision(
2067 return self._addrevision(
2068 node,
2068 node,
2069 rawtext,
2069 rawtext,
2070 transaction,
2070 transaction,
2071 link,
2071 link,
2072 p1,
2072 p1,
2073 p2,
2073 p2,
2074 flags,
2074 flags,
2075 cachedelta,
2075 cachedelta,
2076 ifh,
2076 ifh,
2077 dfh,
2077 dfh,
2078 deltacomputer=deltacomputer,
2078 deltacomputer=deltacomputer,
2079 sidedata=sidedata,
2079 sidedata=sidedata,
2080 )
2080 )
2081 finally:
2081 finally:
2082 if dfh:
2082 if dfh:
2083 dfh.close()
2083 dfh.close()
2084 ifh.close()
2084 ifh.close()
2085
2085
2086 def compress(self, data):
2086 def compress(self, data):
2087 """Generate a possibly-compressed representation of data."""
2087 """Generate a possibly-compressed representation of data."""
2088 if not data:
2088 if not data:
2089 return b'', data
2089 return b'', data
2090
2090
2091 compressed = self._compressor.compress(data)
2091 compressed = self._compressor.compress(data)
2092
2092
2093 if compressed:
2093 if compressed:
2094 # The revlog compressor added the header in the returned data.
2094 # The revlog compressor added the header in the returned data.
2095 return b'', compressed
2095 return b'', compressed
2096
2096
2097 if data[0:1] == b'\0':
2097 if data[0:1] == b'\0':
2098 return b'', data
2098 return b'', data
2099 return b'u', data
2099 return b'u', data
2100
2100
2101 def decompress(self, data):
2101 def decompress(self, data):
2102 """Decompress a revlog chunk.
2102 """Decompress a revlog chunk.
2103
2103
2104 The chunk is expected to begin with a header identifying the
2104 The chunk is expected to begin with a header identifying the
2105 format type so it can be routed to an appropriate decompressor.
2105 format type so it can be routed to an appropriate decompressor.
2106 """
2106 """
2107 if not data:
2107 if not data:
2108 return data
2108 return data
2109
2109
2110 # Revlogs are read much more frequently than they are written and many
2110 # Revlogs are read much more frequently than they are written and many
2111 # chunks only take microseconds to decompress, so performance is
2111 # chunks only take microseconds to decompress, so performance is
2112 # important here.
2112 # important here.
2113 #
2113 #
2114 # We can make a few assumptions about revlogs:
2114 # We can make a few assumptions about revlogs:
2115 #
2115 #
2116 # 1) the majority of chunks will be compressed (as opposed to inline
2116 # 1) the majority of chunks will be compressed (as opposed to inline
2117 # raw data).
2117 # raw data).
2118 # 2) decompressing *any* data will likely by at least 10x slower than
2118 # 2) decompressing *any* data will likely by at least 10x slower than
2119 # returning raw inline data.
2119 # returning raw inline data.
2120 # 3) we want to prioritize common and officially supported compression
2120 # 3) we want to prioritize common and officially supported compression
2121 # engines
2121 # engines
2122 #
2122 #
2123 # It follows that we want to optimize for "decompress compressed data
2123 # It follows that we want to optimize for "decompress compressed data
2124 # when encoded with common and officially supported compression engines"
2124 # when encoded with common and officially supported compression engines"
2125 # case over "raw data" and "data encoded by less common or non-official
2125 # case over "raw data" and "data encoded by less common or non-official
2126 # compression engines." That is why we have the inline lookup first
2126 # compression engines." That is why we have the inline lookup first
2127 # followed by the compengines lookup.
2127 # followed by the compengines lookup.
2128 #
2128 #
2129 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2129 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2130 # compressed chunks. And this matters for changelog and manifest reads.
2130 # compressed chunks. And this matters for changelog and manifest reads.
2131 t = data[0:1]
2131 t = data[0:1]
2132
2132
2133 if t == b'x':
2133 if t == b'x':
2134 try:
2134 try:
2135 return _zlibdecompress(data)
2135 return _zlibdecompress(data)
2136 except zlib.error as e:
2136 except zlib.error as e:
2137 raise error.RevlogError(
2137 raise error.RevlogError(
2138 _(b'revlog decompress error: %s')
2138 _(b'revlog decompress error: %s')
2139 % stringutil.forcebytestr(e)
2139 % stringutil.forcebytestr(e)
2140 )
2140 )
2141 # '\0' is more common than 'u' so it goes first.
2141 # '\0' is more common than 'u' so it goes first.
2142 elif t == b'\0':
2142 elif t == b'\0':
2143 return data
2143 return data
2144 elif t == b'u':
2144 elif t == b'u':
2145 return util.buffer(data, 1)
2145 return util.buffer(data, 1)
2146
2146
2147 try:
2147 try:
2148 compressor = self._decompressors[t]
2148 compressor = self._decompressors[t]
2149 except KeyError:
2149 except KeyError:
2150 try:
2150 try:
2151 engine = util.compengines.forrevlogheader(t)
2151 engine = util.compengines.forrevlogheader(t)
2152 compressor = engine.revlogcompressor(self._compengineopts)
2152 compressor = engine.revlogcompressor(self._compengineopts)
2153 self._decompressors[t] = compressor
2153 self._decompressors[t] = compressor
2154 except KeyError:
2154 except KeyError:
2155 raise error.RevlogError(
2155 raise error.RevlogError(
2156 _(b'unknown compression type %s') % binascii.hexlify(t)
2156 _(b'unknown compression type %s') % binascii.hexlify(t)
2157 )
2157 )
2158
2158
2159 return compressor.decompress(data)
2159 return compressor.decompress(data)
2160
2160
2161 def _addrevision(
2161 def _addrevision(
2162 self,
2162 self,
2163 node,
2163 node,
2164 rawtext,
2164 rawtext,
2165 transaction,
2165 transaction,
2166 link,
2166 link,
2167 p1,
2167 p1,
2168 p2,
2168 p2,
2169 flags,
2169 flags,
2170 cachedelta,
2170 cachedelta,
2171 ifh,
2171 ifh,
2172 dfh,
2172 dfh,
2173 alwayscache=False,
2173 alwayscache=False,
2174 deltacomputer=None,
2174 deltacomputer=None,
2175 sidedata=None,
2175 sidedata=None,
2176 ):
2176 ):
2177 """internal function to add revisions to the log
2177 """internal function to add revisions to the log
2178
2178
2179 see addrevision for argument descriptions.
2179 see addrevision for argument descriptions.
2180
2180
2181 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2181 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2182
2182
2183 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2183 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2184 be used.
2184 be used.
2185
2185
2186 invariants:
2186 invariants:
2187 - rawtext is optional (can be None); if not set, cachedelta must be set.
2187 - rawtext is optional (can be None); if not set, cachedelta must be set.
2188 if both are set, they must correspond to each other.
2188 if both are set, they must correspond to each other.
2189 """
2189 """
2190 if node == self.nullid:
2190 if node == self.nullid:
2191 raise error.RevlogError(
2191 raise error.RevlogError(
2192 _(b"%s: attempt to add null revision") % self.indexfile
2192 _(b"%s: attempt to add null revision") % self.indexfile
2193 )
2193 )
2194 if (
2194 if (
2195 node == self.nodeconstants.wdirid
2195 node == self.nodeconstants.wdirid
2196 or node in self.nodeconstants.wdirfilenodeids
2196 or node in self.nodeconstants.wdirfilenodeids
2197 ):
2197 ):
2198 raise error.RevlogError(
2198 raise error.RevlogError(
2199 _(b"%s: attempt to add wdir revision") % self.indexfile
2199 _(b"%s: attempt to add wdir revision") % self.indexfile
2200 )
2200 )
2201
2201
2202 if self._inline:
2202 if self._inline:
2203 fh = ifh
2203 fh = ifh
2204 else:
2204 else:
2205 fh = dfh
2205 fh = dfh
2206
2206
2207 btext = [rawtext]
2207 btext = [rawtext]
2208
2208
2209 curr = len(self)
2209 curr = len(self)
2210 prev = curr - 1
2210 prev = curr - 1
2211
2211
2212 offset = self._get_data_offset(prev)
2212 offset = self._get_data_offset(prev)
2213
2213
2214 if self._concurrencychecker:
2214 if self._concurrencychecker:
2215 if self._inline:
2215 if self._inline:
2216 # offset is "as if" it were in the .d file, so we need to add on
2216 # offset is "as if" it were in the .d file, so we need to add on
2217 # the size of the entry metadata.
2217 # the size of the entry metadata.
2218 self._concurrencychecker(
2218 self._concurrencychecker(
2219 ifh, self.indexfile, offset + curr * self.index.entry_size
2219 ifh, self.indexfile, offset + curr * self.index.entry_size
2220 )
2220 )
2221 else:
2221 else:
2222 # Entries in the .i are a consistent size.
2222 # Entries in the .i are a consistent size.
2223 self._concurrencychecker(
2223 self._concurrencychecker(
2224 ifh, self.indexfile, curr * self.index.entry_size
2224 ifh, self.indexfile, curr * self.index.entry_size
2225 )
2225 )
2226 self._concurrencychecker(dfh, self.datafile, offset)
2226 self._concurrencychecker(dfh, self.datafile, offset)
2227
2227
2228 p1r, p2r = self.rev(p1), self.rev(p2)
2228 p1r, p2r = self.rev(p1), self.rev(p2)
2229
2229
2230 # full versions are inserted when the needed deltas
2230 # full versions are inserted when the needed deltas
2231 # become comparable to the uncompressed text
2231 # become comparable to the uncompressed text
2232 if rawtext is None:
2232 if rawtext is None:
2233 # need rawtext size, before changed by flag processors, which is
2233 # need rawtext size, before changed by flag processors, which is
2234 # the non-raw size. use revlog explicitly to avoid filelog's extra
2234 # the non-raw size. use revlog explicitly to avoid filelog's extra
2235 # logic that might remove metadata size.
2235 # logic that might remove metadata size.
2236 textlen = mdiff.patchedsize(
2236 textlen = mdiff.patchedsize(
2237 revlog.size(self, cachedelta[0]), cachedelta[1]
2237 revlog.size(self, cachedelta[0]), cachedelta[1]
2238 )
2238 )
2239 else:
2239 else:
2240 textlen = len(rawtext)
2240 textlen = len(rawtext)
2241
2241
2242 if deltacomputer is None:
2242 if deltacomputer is None:
2243 deltacomputer = deltautil.deltacomputer(self)
2243 deltacomputer = deltautil.deltacomputer(self)
2244
2244
2245 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2245 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2246
2246
2247 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2247 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2248
2248
2249 if sidedata and self.version & 0xFFFF == REVLOGV2:
2249 if sidedata and self.version & 0xFFFF == REVLOGV2:
2250 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2250 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2251 sidedata_offset = offset + deltainfo.deltalen
2251 sidedata_offset = offset + deltainfo.deltalen
2252 else:
2252 else:
2253 serialized_sidedata = b""
2253 serialized_sidedata = b""
2254 # Don't store the offset if the sidedata is empty, that way
2254 # Don't store the offset if the sidedata is empty, that way
2255 # we can easily detect empty sidedata and they will be no different
2255 # we can easily detect empty sidedata and they will be no different
2256 # than ones we manually add.
2256 # than ones we manually add.
2257 sidedata_offset = 0
2257 sidedata_offset = 0
2258
2258
2259 e = (
2259 e = (
2260 offset_type(offset, flags),
2260 offset_type(offset, flags),
2261 deltainfo.deltalen,
2261 deltainfo.deltalen,
2262 textlen,
2262 textlen,
2263 deltainfo.base,
2263 deltainfo.base,
2264 link,
2264 link,
2265 p1r,
2265 p1r,
2266 p2r,
2266 p2r,
2267 node,
2267 node,
2268 sidedata_offset,
2268 sidedata_offset,
2269 len(serialized_sidedata),
2269 len(serialized_sidedata),
2270 )
2270 )
2271
2271
2272 if self.version & 0xFFFF != REVLOGV2:
2272 if self.version & 0xFFFF != REVLOGV2:
2273 e = e[:8]
2273 e = e[:8]
2274
2274
2275 self.index.append(e)
2275 self.index.append(e)
2276 entry = self.index.entry_binary(curr)
2276 entry = self.index.entry_binary(curr)
2277 if curr == 0:
2277 if curr == 0:
2278 header = self.index.pack_header(self.version)
2278 header = self.index.pack_header(self.version)
2279 entry = header + entry
2279 entry = header + entry
2280 self._writeentry(
2280 self._writeentry(
2281 transaction,
2281 transaction,
2282 ifh,
2282 ifh,
2283 dfh,
2283 dfh,
2284 entry,
2284 entry,
2285 deltainfo.data,
2285 deltainfo.data,
2286 link,
2286 link,
2287 offset,
2287 offset,
2288 serialized_sidedata,
2288 serialized_sidedata,
2289 )
2289 )
2290
2290
2291 rawtext = btext[0]
2291 rawtext = btext[0]
2292
2292
2293 if alwayscache and rawtext is None:
2293 if alwayscache and rawtext is None:
2294 rawtext = deltacomputer.buildtext(revinfo, fh)
2294 rawtext = deltacomputer.buildtext(revinfo, fh)
2295
2295
2296 if type(rawtext) == bytes: # only accept immutable objects
2296 if type(rawtext) == bytes: # only accept immutable objects
2297 self._revisioncache = (node, curr, rawtext)
2297 self._revisioncache = (node, curr, rawtext)
2298 self._chainbasecache[curr] = deltainfo.chainbase
2298 self._chainbasecache[curr] = deltainfo.chainbase
2299 return curr
2299 return curr
2300
2300
2301 def _get_data_offset(self, prev):
2301 def _get_data_offset(self, prev):
2302 """Returns the current offset in the (in-transaction) data file.
2302 """Returns the current offset in the (in-transaction) data file.
2303 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2303 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2304 file to store that information: since sidedata can be rewritten to the
2304 file to store that information: since sidedata can be rewritten to the
2305 end of the data file within a transaction, you can have cases where, for
2305 end of the data file within a transaction, you can have cases where, for
2306 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2306 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2307 to `n - 1`'s sidedata being written after `n`'s data.
2307 to `n - 1`'s sidedata being written after `n`'s data.
2308
2308
2309 TODO cache this in a docket file before getting out of experimental."""
2309 TODO cache this in a docket file before getting out of experimental."""
2310 if self.version & 0xFFFF != REVLOGV2:
2310 if self.version & 0xFFFF != REVLOGV2:
2311 return self.end(prev)
2311 return self.end(prev)
2312
2312
2313 offset = 0
2313 offset = 0
2314 for rev, entry in enumerate(self.index):
2314 for rev, entry in enumerate(self.index):
2315 sidedata_end = entry[8] + entry[9]
2315 sidedata_end = entry[8] + entry[9]
2316 # Sidedata for a previous rev has potentially been written after
2316 # Sidedata for a previous rev has potentially been written after
2317 # this rev's end, so take the max.
2317 # this rev's end, so take the max.
2318 offset = max(self.end(rev), offset, sidedata_end)
2318 offset = max(self.end(rev), offset, sidedata_end)
2319 return offset
2319 return offset
2320
2320
2321 def _writeentry(
2321 def _writeentry(
2322 self, transaction, ifh, dfh, entry, data, link, offset, sidedata
2322 self, transaction, ifh, dfh, entry, data, link, offset, sidedata
2323 ):
2323 ):
2324 # Files opened in a+ mode have inconsistent behavior on various
2324 # Files opened in a+ mode have inconsistent behavior on various
2325 # platforms. Windows requires that a file positioning call be made
2325 # platforms. Windows requires that a file positioning call be made
2326 # when the file handle transitions between reads and writes. See
2326 # when the file handle transitions between reads and writes. See
2327 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2327 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2328 # platforms, Python or the platform itself can be buggy. Some versions
2328 # platforms, Python or the platform itself can be buggy. Some versions
2329 # of Solaris have been observed to not append at the end of the file
2329 # of Solaris have been observed to not append at the end of the file
2330 # if the file was seeked to before the end. See issue4943 for more.
2330 # if the file was seeked to before the end. See issue4943 for more.
2331 #
2331 #
2332 # We work around this issue by inserting a seek() before writing.
2332 # We work around this issue by inserting a seek() before writing.
2333 # Note: This is likely not necessary on Python 3. However, because
2333 # Note: This is likely not necessary on Python 3. However, because
2334 # the file handle is reused for reads and may be seeked there, we need
2334 # the file handle is reused for reads and may be seeked there, we need
2335 # to be careful before changing this.
2335 # to be careful before changing this.
2336 ifh.seek(0, os.SEEK_END)
2336 ifh.seek(0, os.SEEK_END)
2337 if dfh:
2337 if dfh:
2338 dfh.seek(0, os.SEEK_END)
2338 dfh.seek(0, os.SEEK_END)
2339
2339
2340 curr = len(self) - 1
2340 curr = len(self) - 1
2341 if not self._inline:
2341 if not self._inline:
2342 transaction.add(self.datafile, offset)
2342 transaction.add(self.datafile, offset)
2343 transaction.add(self.indexfile, curr * len(entry))
2343 transaction.add(self.indexfile, curr * len(entry))
2344 if data[0]:
2344 if data[0]:
2345 dfh.write(data[0])
2345 dfh.write(data[0])
2346 dfh.write(data[1])
2346 dfh.write(data[1])
2347 if sidedata:
2347 if sidedata:
2348 dfh.write(sidedata)
2348 dfh.write(sidedata)
2349 ifh.write(entry)
2349 ifh.write(entry)
2350 else:
2350 else:
2351 offset += curr * self.index.entry_size
2351 offset += curr * self.index.entry_size
2352 transaction.add(self.indexfile, offset)
2352 transaction.add(self.indexfile, offset)
2353 ifh.write(entry)
2353 ifh.write(entry)
2354 ifh.write(data[0])
2354 ifh.write(data[0])
2355 ifh.write(data[1])
2355 ifh.write(data[1])
2356 if sidedata:
2356 if sidedata:
2357 ifh.write(sidedata)
2357 ifh.write(sidedata)
2358 self._enforceinlinesize(transaction, ifh)
2358 self._enforceinlinesize(transaction, ifh)
2359 nodemaputil.setup_persistent_nodemap(transaction, self)
2359 nodemaputil.setup_persistent_nodemap(transaction, self)
2360
2360
2361 def addgroup(
2361 def addgroup(
2362 self,
2362 self,
2363 deltas,
2363 deltas,
2364 linkmapper,
2364 linkmapper,
2365 transaction,
2365 transaction,
2366 alwayscache=False,
2366 alwayscache=False,
2367 addrevisioncb=None,
2367 addrevisioncb=None,
2368 duplicaterevisioncb=None,
2368 duplicaterevisioncb=None,
2369 ):
2369 ):
2370 """
2370 """
2371 add a delta group
2371 add a delta group
2372
2372
2373 given a set of deltas, add them to the revision log. the
2373 given a set of deltas, add them to the revision log. the
2374 first delta is against its parent, which should be in our
2374 first delta is against its parent, which should be in our
2375 log, the rest are against the previous delta.
2375 log, the rest are against the previous delta.
2376
2376
2377 If ``addrevisioncb`` is defined, it will be called with arguments of
2377 If ``addrevisioncb`` is defined, it will be called with arguments of
2378 this revlog and the node that was added.
2378 this revlog and the node that was added.
2379 """
2379 """
2380
2380
2381 if self._writinghandles:
2381 if self._writinghandles:
2382 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2382 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2383
2383
2384 r = len(self)
2384 r = len(self)
2385 end = 0
2385 end = 0
2386 if r:
2386 if r:
2387 end = self.end(r - 1)
2387 end = self.end(r - 1)
2388 ifh = self._indexfp(b"a+")
2388 ifh = self._indexfp(b"a+")
2389 isize = r * self.index.entry_size
2389 isize = r * self.index.entry_size
2390 if self._inline:
2390 if self._inline:
2391 transaction.add(self.indexfile, end + isize)
2391 transaction.add(self.indexfile, end + isize)
2392 dfh = None
2392 dfh = None
2393 else:
2393 else:
2394 transaction.add(self.indexfile, isize)
2394 transaction.add(self.indexfile, isize)
2395 transaction.add(self.datafile, end)
2395 transaction.add(self.datafile, end)
2396 dfh = self._datafp(b"a+")
2396 dfh = self._datafp(b"a+")
2397
2397
2398 def flush():
2398 def flush():
2399 if dfh:
2399 if dfh:
2400 dfh.flush()
2400 dfh.flush()
2401 ifh.flush()
2401 ifh.flush()
2402
2402
2403 self._writinghandles = (ifh, dfh)
2403 self._writinghandles = (ifh, dfh)
2404 empty = True
2404 empty = True
2405
2405
2406 try:
2406 try:
2407 deltacomputer = deltautil.deltacomputer(self)
2407 deltacomputer = deltautil.deltacomputer(self)
2408 # loop through our set of deltas
2408 # loop through our set of deltas
2409 for data in deltas:
2409 for data in deltas:
2410 node, p1, p2, linknode, deltabase, delta, flags, sidedata = data
2410 node, p1, p2, linknode, deltabase, delta, flags, sidedata = data
2411 link = linkmapper(linknode)
2411 link = linkmapper(linknode)
2412 flags = flags or REVIDX_DEFAULT_FLAGS
2412 flags = flags or REVIDX_DEFAULT_FLAGS
2413
2413
2414 rev = self.index.get_rev(node)
2414 rev = self.index.get_rev(node)
2415 if rev is not None:
2415 if rev is not None:
2416 # this can happen if two branches make the same change
2416 # this can happen if two branches make the same change
2417 self._nodeduplicatecallback(transaction, rev)
2417 self._nodeduplicatecallback(transaction, rev)
2418 if duplicaterevisioncb:
2418 if duplicaterevisioncb:
2419 duplicaterevisioncb(self, rev)
2419 duplicaterevisioncb(self, rev)
2420 empty = False
2420 empty = False
2421 continue
2421 continue
2422
2422
2423 for p in (p1, p2):
2423 for p in (p1, p2):
2424 if not self.index.has_node(p):
2424 if not self.index.has_node(p):
2425 raise error.LookupError(
2425 raise error.LookupError(
2426 p, self.indexfile, _(b'unknown parent')
2426 p, self.indexfile, _(b'unknown parent')
2427 )
2427 )
2428
2428
2429 if not self.index.has_node(deltabase):
2429 if not self.index.has_node(deltabase):
2430 raise error.LookupError(
2430 raise error.LookupError(
2431 deltabase, self.indexfile, _(b'unknown delta base')
2431 deltabase, self.indexfile, _(b'unknown delta base')
2432 )
2432 )
2433
2433
2434 baserev = self.rev(deltabase)
2434 baserev = self.rev(deltabase)
2435
2435
2436 if baserev != nullrev and self.iscensored(baserev):
2436 if baserev != nullrev and self.iscensored(baserev):
2437 # if base is censored, delta must be full replacement in a
2437 # if base is censored, delta must be full replacement in a
2438 # single patch operation
2438 # single patch operation
2439 hlen = struct.calcsize(b">lll")
2439 hlen = struct.calcsize(b">lll")
2440 oldlen = self.rawsize(baserev)
2440 oldlen = self.rawsize(baserev)
2441 newlen = len(delta) - hlen
2441 newlen = len(delta) - hlen
2442 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2442 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2443 raise error.CensoredBaseError(
2443 raise error.CensoredBaseError(
2444 self.indexfile, self.node(baserev)
2444 self.indexfile, self.node(baserev)
2445 )
2445 )
2446
2446
2447 if not flags and self._peek_iscensored(baserev, delta, flush):
2447 if not flags and self._peek_iscensored(baserev, delta, flush):
2448 flags |= REVIDX_ISCENSORED
2448 flags |= REVIDX_ISCENSORED
2449
2449
2450 # We assume consumers of addrevisioncb will want to retrieve
2450 # We assume consumers of addrevisioncb will want to retrieve
2451 # the added revision, which will require a call to
2451 # the added revision, which will require a call to
2452 # revision(). revision() will fast path if there is a cache
2452 # revision(). revision() will fast path if there is a cache
2453 # hit. So, we tell _addrevision() to always cache in this case.
2453 # hit. So, we tell _addrevision() to always cache in this case.
2454 # We're only using addgroup() in the context of changegroup
2454 # We're only using addgroup() in the context of changegroup
2455 # generation so the revision data can always be handled as raw
2455 # generation so the revision data can always be handled as raw
2456 # by the flagprocessor.
2456 # by the flagprocessor.
2457 rev = self._addrevision(
2457 rev = self._addrevision(
2458 node,
2458 node,
2459 None,
2459 None,
2460 transaction,
2460 transaction,
2461 link,
2461 link,
2462 p1,
2462 p1,
2463 p2,
2463 p2,
2464 flags,
2464 flags,
2465 (baserev, delta),
2465 (baserev, delta),
2466 ifh,
2466 ifh,
2467 dfh,
2467 dfh,
2468 alwayscache=alwayscache,
2468 alwayscache=alwayscache,
2469 deltacomputer=deltacomputer,
2469 deltacomputer=deltacomputer,
2470 sidedata=sidedata,
2470 sidedata=sidedata,
2471 )
2471 )
2472
2472
2473 if addrevisioncb:
2473 if addrevisioncb:
2474 addrevisioncb(self, rev)
2474 addrevisioncb(self, rev)
2475 empty = False
2475 empty = False
2476
2476
2477 if not dfh and not self._inline:
2477 if not dfh and not self._inline:
2478 # addrevision switched from inline to conventional
2478 # addrevision switched from inline to conventional
2479 # reopen the index
2479 # reopen the index
2480 ifh.close()
2480 ifh.close()
2481 dfh = self._datafp(b"a+")
2481 dfh = self._datafp(b"a+")
2482 ifh = self._indexfp(b"a+")
2482 ifh = self._indexfp(b"a+")
2483 self._writinghandles = (ifh, dfh)
2483 self._writinghandles = (ifh, dfh)
2484 finally:
2484 finally:
2485 self._writinghandles = None
2485 self._writinghandles = None
2486
2486
2487 if dfh:
2487 if dfh:
2488 dfh.close()
2488 dfh.close()
2489 ifh.close()
2489 ifh.close()
2490 return not empty
2490 return not empty
2491
2491
2492 def iscensored(self, rev):
2492 def iscensored(self, rev):
2493 """Check if a file revision is censored."""
2493 """Check if a file revision is censored."""
2494 if not self._censorable:
2494 if not self._censorable:
2495 return False
2495 return False
2496
2496
2497 return self.flags(rev) & REVIDX_ISCENSORED
2497 return self.flags(rev) & REVIDX_ISCENSORED
2498
2498
2499 def _peek_iscensored(self, baserev, delta, flush):
2499 def _peek_iscensored(self, baserev, delta, flush):
2500 """Quickly check if a delta produces a censored revision."""
2500 """Quickly check if a delta produces a censored revision."""
2501 if not self._censorable:
2501 if not self._censorable:
2502 return False
2502 return False
2503
2503
2504 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2504 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2505
2505
2506 def getstrippoint(self, minlink):
2506 def getstrippoint(self, minlink):
2507 """find the minimum rev that must be stripped to strip the linkrev
2507 """find the minimum rev that must be stripped to strip the linkrev
2508
2508
2509 Returns a tuple containing the minimum rev and a set of all revs that
2509 Returns a tuple containing the minimum rev and a set of all revs that
2510 have linkrevs that will be broken by this strip.
2510 have linkrevs that will be broken by this strip.
2511 """
2511 """
2512 return storageutil.resolvestripinfo(
2512 return storageutil.resolvestripinfo(
2513 minlink,
2513 minlink,
2514 len(self) - 1,
2514 len(self) - 1,
2515 self.headrevs(),
2515 self.headrevs(),
2516 self.linkrev,
2516 self.linkrev,
2517 self.parentrevs,
2517 self.parentrevs,
2518 )
2518 )
2519
2519
2520 def strip(self, minlink, transaction):
2520 def strip(self, minlink, transaction):
2521 """truncate the revlog on the first revision with a linkrev >= minlink
2521 """truncate the revlog on the first revision with a linkrev >= minlink
2522
2522
2523 This function is called when we're stripping revision minlink and
2523 This function is called when we're stripping revision minlink and
2524 its descendants from the repository.
2524 its descendants from the repository.
2525
2525
2526 We have to remove all revisions with linkrev >= minlink, because
2526 We have to remove all revisions with linkrev >= minlink, because
2527 the equivalent changelog revisions will be renumbered after the
2527 the equivalent changelog revisions will be renumbered after the
2528 strip.
2528 strip.
2529
2529
2530 So we truncate the revlog on the first of these revisions, and
2530 So we truncate the revlog on the first of these revisions, and
2531 trust that the caller has saved the revisions that shouldn't be
2531 trust that the caller has saved the revisions that shouldn't be
2532 removed and that it'll re-add them after this truncation.
2532 removed and that it'll re-add them after this truncation.
2533 """
2533 """
2534 if len(self) == 0:
2534 if len(self) == 0:
2535 return
2535 return
2536
2536
2537 rev, _ = self.getstrippoint(minlink)
2537 rev, _ = self.getstrippoint(minlink)
2538 if rev == len(self):
2538 if rev == len(self):
2539 return
2539 return
2540
2540
2541 # first truncate the files on disk
2541 # first truncate the files on disk
2542 end = self.start(rev)
2542 end = self.start(rev)
2543 if not self._inline:
2543 if not self._inline:
2544 transaction.add(self.datafile, end)
2544 transaction.add(self.datafile, end)
2545 end = rev * self.index.entry_size
2545 end = rev * self.index.entry_size
2546 else:
2546 else:
2547 end += rev * self.index.entry_size
2547 end += rev * self.index.entry_size
2548
2548
2549 transaction.add(self.indexfile, end)
2549 transaction.add(self.indexfile, end)
2550
2550
2551 # then reset internal state in memory to forget those revisions
2551 # then reset internal state in memory to forget those revisions
2552 self._revisioncache = None
2552 self._revisioncache = None
2553 self._chaininfocache = util.lrucachedict(500)
2553 self._chaininfocache = util.lrucachedict(500)
2554 self._chunkclear()
2554 self._chunkclear()
2555
2555
2556 del self.index[rev:-1]
2556 del self.index[rev:-1]
2557
2557
2558 def checksize(self):
2558 def checksize(self):
2559 """Check size of index and data files
2559 """Check size of index and data files
2560
2560
2561 return a (dd, di) tuple.
2561 return a (dd, di) tuple.
2562 - dd: extra bytes for the "data" file
2562 - dd: extra bytes for the "data" file
2563 - di: extra bytes for the "index" file
2563 - di: extra bytes for the "index" file
2564
2564
2565 A healthy revlog will return (0, 0).
2565 A healthy revlog will return (0, 0).
2566 """
2566 """
2567 expected = 0
2567 expected = 0
2568 if len(self):
2568 if len(self):
2569 expected = max(0, self.end(len(self) - 1))
2569 expected = max(0, self.end(len(self) - 1))
2570
2570
2571 try:
2571 try:
2572 with self._datafp() as f:
2572 with self._datafp() as f:
2573 f.seek(0, io.SEEK_END)
2573 f.seek(0, io.SEEK_END)
2574 actual = f.tell()
2574 actual = f.tell()
2575 dd = actual - expected
2575 dd = actual - expected
2576 except IOError as inst:
2576 except IOError as inst:
2577 if inst.errno != errno.ENOENT:
2577 if inst.errno != errno.ENOENT:
2578 raise
2578 raise
2579 dd = 0
2579 dd = 0
2580
2580
2581 try:
2581 try:
2582 f = self.opener(self.indexfile)
2582 f = self.opener(self.indexfile)
2583 f.seek(0, io.SEEK_END)
2583 f.seek(0, io.SEEK_END)
2584 actual = f.tell()
2584 actual = f.tell()
2585 f.close()
2585 f.close()
2586 s = self.index.entry_size
2586 s = self.index.entry_size
2587 i = max(0, actual // s)
2587 i = max(0, actual // s)
2588 di = actual - (i * s)
2588 di = actual - (i * s)
2589 if self._inline:
2589 if self._inline:
2590 databytes = 0
2590 databytes = 0
2591 for r in self:
2591 for r in self:
2592 databytes += max(0, self.length(r))
2592 databytes += max(0, self.length(r))
2593 dd = 0
2593 dd = 0
2594 di = actual - len(self) * s - databytes
2594 di = actual - len(self) * s - databytes
2595 except IOError as inst:
2595 except IOError as inst:
2596 if inst.errno != errno.ENOENT:
2596 if inst.errno != errno.ENOENT:
2597 raise
2597 raise
2598 di = 0
2598 di = 0
2599
2599
2600 return (dd, di)
2600 return (dd, di)
2601
2601
2602 def files(self):
2602 def files(self):
2603 res = [self.indexfile]
2603 res = [self.indexfile]
2604 if not self._inline:
2604 if not self._inline:
2605 res.append(self.datafile)
2605 res.append(self.datafile)
2606 return res
2606 return res
2607
2607
2608 def emitrevisions(
2608 def emitrevisions(
2609 self,
2609 self,
2610 nodes,
2610 nodes,
2611 nodesorder=None,
2611 nodesorder=None,
2612 revisiondata=False,
2612 revisiondata=False,
2613 assumehaveparentrevisions=False,
2613 assumehaveparentrevisions=False,
2614 deltamode=repository.CG_DELTAMODE_STD,
2614 deltamode=repository.CG_DELTAMODE_STD,
2615 sidedata_helpers=None,
2615 sidedata_helpers=None,
2616 ):
2616 ):
2617 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2617 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2618 raise error.ProgrammingError(
2618 raise error.ProgrammingError(
2619 b'unhandled value for nodesorder: %s' % nodesorder
2619 b'unhandled value for nodesorder: %s' % nodesorder
2620 )
2620 )
2621
2621
2622 if nodesorder is None and not self._generaldelta:
2622 if nodesorder is None and not self._generaldelta:
2623 nodesorder = b'storage'
2623 nodesorder = b'storage'
2624
2624
2625 if (
2625 if (
2626 not self._storedeltachains
2626 not self._storedeltachains
2627 and deltamode != repository.CG_DELTAMODE_PREV
2627 and deltamode != repository.CG_DELTAMODE_PREV
2628 ):
2628 ):
2629 deltamode = repository.CG_DELTAMODE_FULL
2629 deltamode = repository.CG_DELTAMODE_FULL
2630
2630
2631 return storageutil.emitrevisions(
2631 return storageutil.emitrevisions(
2632 self,
2632 self,
2633 nodes,
2633 nodes,
2634 nodesorder,
2634 nodesorder,
2635 revlogrevisiondelta,
2635 revlogrevisiondelta,
2636 deltaparentfn=self.deltaparent,
2636 deltaparentfn=self.deltaparent,
2637 candeltafn=self.candelta,
2637 candeltafn=self.candelta,
2638 rawsizefn=self.rawsize,
2638 rawsizefn=self.rawsize,
2639 revdifffn=self.revdiff,
2639 revdifffn=self.revdiff,
2640 flagsfn=self.flags,
2640 flagsfn=self.flags,
2641 deltamode=deltamode,
2641 deltamode=deltamode,
2642 revisiondata=revisiondata,
2642 revisiondata=revisiondata,
2643 assumehaveparentrevisions=assumehaveparentrevisions,
2643 assumehaveparentrevisions=assumehaveparentrevisions,
2644 sidedata_helpers=sidedata_helpers,
2644 sidedata_helpers=sidedata_helpers,
2645 )
2645 )
2646
2646
2647 DELTAREUSEALWAYS = b'always'
2647 DELTAREUSEALWAYS = b'always'
2648 DELTAREUSESAMEREVS = b'samerevs'
2648 DELTAREUSESAMEREVS = b'samerevs'
2649 DELTAREUSENEVER = b'never'
2649 DELTAREUSENEVER = b'never'
2650
2650
2651 DELTAREUSEFULLADD = b'fulladd'
2651 DELTAREUSEFULLADD = b'fulladd'
2652
2652
2653 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2653 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2654
2654
2655 def clone(
2655 def clone(
2656 self,
2656 self,
2657 tr,
2657 tr,
2658 destrevlog,
2658 destrevlog,
2659 addrevisioncb=None,
2659 addrevisioncb=None,
2660 deltareuse=DELTAREUSESAMEREVS,
2660 deltareuse=DELTAREUSESAMEREVS,
2661 forcedeltabothparents=None,
2661 forcedeltabothparents=None,
2662 sidedata_helpers=None,
2662 sidedata_helpers=None,
2663 ):
2663 ):
2664 """Copy this revlog to another, possibly with format changes.
2664 """Copy this revlog to another, possibly with format changes.
2665
2665
2666 The destination revlog will contain the same revisions and nodes.
2666 The destination revlog will contain the same revisions and nodes.
2667 However, it may not be bit-for-bit identical due to e.g. delta encoding
2667 However, it may not be bit-for-bit identical due to e.g. delta encoding
2668 differences.
2668 differences.
2669
2669
2670 The ``deltareuse`` argument control how deltas from the existing revlog
2670 The ``deltareuse`` argument control how deltas from the existing revlog
2671 are preserved in the destination revlog. The argument can have the
2671 are preserved in the destination revlog. The argument can have the
2672 following values:
2672 following values:
2673
2673
2674 DELTAREUSEALWAYS
2674 DELTAREUSEALWAYS
2675 Deltas will always be reused (if possible), even if the destination
2675 Deltas will always be reused (if possible), even if the destination
2676 revlog would not select the same revisions for the delta. This is the
2676 revlog would not select the same revisions for the delta. This is the
2677 fastest mode of operation.
2677 fastest mode of operation.
2678 DELTAREUSESAMEREVS
2678 DELTAREUSESAMEREVS
2679 Deltas will be reused if the destination revlog would pick the same
2679 Deltas will be reused if the destination revlog would pick the same
2680 revisions for the delta. This mode strikes a balance between speed
2680 revisions for the delta. This mode strikes a balance between speed
2681 and optimization.
2681 and optimization.
2682 DELTAREUSENEVER
2682 DELTAREUSENEVER
2683 Deltas will never be reused. This is the slowest mode of execution.
2683 Deltas will never be reused. This is the slowest mode of execution.
2684 This mode can be used to recompute deltas (e.g. if the diff/delta
2684 This mode can be used to recompute deltas (e.g. if the diff/delta
2685 algorithm changes).
2685 algorithm changes).
2686 DELTAREUSEFULLADD
2686 DELTAREUSEFULLADD
2687 Revision will be re-added as if their were new content. This is
2687 Revision will be re-added as if their were new content. This is
2688 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2688 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2689 eg: large file detection and handling.
2689 eg: large file detection and handling.
2690
2690
2691 Delta computation can be slow, so the choice of delta reuse policy can
2691 Delta computation can be slow, so the choice of delta reuse policy can
2692 significantly affect run time.
2692 significantly affect run time.
2693
2693
2694 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2694 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2695 two extremes. Deltas will be reused if they are appropriate. But if the
2695 two extremes. Deltas will be reused if they are appropriate. But if the
2696 delta could choose a better revision, it will do so. This means if you
2696 delta could choose a better revision, it will do so. This means if you
2697 are converting a non-generaldelta revlog to a generaldelta revlog,
2697 are converting a non-generaldelta revlog to a generaldelta revlog,
2698 deltas will be recomputed if the delta's parent isn't a parent of the
2698 deltas will be recomputed if the delta's parent isn't a parent of the
2699 revision.
2699 revision.
2700
2700
2701 In addition to the delta policy, the ``forcedeltabothparents``
2701 In addition to the delta policy, the ``forcedeltabothparents``
2702 argument controls whether to force compute deltas against both parents
2702 argument controls whether to force compute deltas against both parents
2703 for merges. By default, the current default is used.
2703 for merges. By default, the current default is used.
2704
2704
2705 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
2705 See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
2706 """
2706 """
2707 if deltareuse not in self.DELTAREUSEALL:
2707 if deltareuse not in self.DELTAREUSEALL:
2708 raise ValueError(
2708 raise ValueError(
2709 _(b'value for deltareuse invalid: %s') % deltareuse
2709 _(b'value for deltareuse invalid: %s') % deltareuse
2710 )
2710 )
2711
2711
2712 if len(destrevlog):
2712 if len(destrevlog):
2713 raise ValueError(_(b'destination revlog is not empty'))
2713 raise ValueError(_(b'destination revlog is not empty'))
2714
2714
2715 if getattr(self, 'filteredrevs', None):
2715 if getattr(self, 'filteredrevs', None):
2716 raise ValueError(_(b'source revlog has filtered revisions'))
2716 raise ValueError(_(b'source revlog has filtered revisions'))
2717 if getattr(destrevlog, 'filteredrevs', None):
2717 if getattr(destrevlog, 'filteredrevs', None):
2718 raise ValueError(_(b'destination revlog has filtered revisions'))
2718 raise ValueError(_(b'destination revlog has filtered revisions'))
2719
2719
2720 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2720 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2721 # if possible.
2721 # if possible.
2722 oldlazydelta = destrevlog._lazydelta
2722 oldlazydelta = destrevlog._lazydelta
2723 oldlazydeltabase = destrevlog._lazydeltabase
2723 oldlazydeltabase = destrevlog._lazydeltabase
2724 oldamd = destrevlog._deltabothparents
2724 oldamd = destrevlog._deltabothparents
2725
2725
2726 try:
2726 try:
2727 if deltareuse == self.DELTAREUSEALWAYS:
2727 if deltareuse == self.DELTAREUSEALWAYS:
2728 destrevlog._lazydeltabase = True
2728 destrevlog._lazydeltabase = True
2729 destrevlog._lazydelta = True
2729 destrevlog._lazydelta = True
2730 elif deltareuse == self.DELTAREUSESAMEREVS:
2730 elif deltareuse == self.DELTAREUSESAMEREVS:
2731 destrevlog._lazydeltabase = False
2731 destrevlog._lazydeltabase = False
2732 destrevlog._lazydelta = True
2732 destrevlog._lazydelta = True
2733 elif deltareuse == self.DELTAREUSENEVER:
2733 elif deltareuse == self.DELTAREUSENEVER:
2734 destrevlog._lazydeltabase = False
2734 destrevlog._lazydeltabase = False
2735 destrevlog._lazydelta = False
2735 destrevlog._lazydelta = False
2736
2736
2737 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2737 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2738
2738
2739 self._clone(
2739 self._clone(
2740 tr,
2740 tr,
2741 destrevlog,
2741 destrevlog,
2742 addrevisioncb,
2742 addrevisioncb,
2743 deltareuse,
2743 deltareuse,
2744 forcedeltabothparents,
2744 forcedeltabothparents,
2745 sidedata_helpers,
2745 sidedata_helpers,
2746 )
2746 )
2747
2747
2748 finally:
2748 finally:
2749 destrevlog._lazydelta = oldlazydelta
2749 destrevlog._lazydelta = oldlazydelta
2750 destrevlog._lazydeltabase = oldlazydeltabase
2750 destrevlog._lazydeltabase = oldlazydeltabase
2751 destrevlog._deltabothparents = oldamd
2751 destrevlog._deltabothparents = oldamd
2752
2752
2753 def _clone(
2753 def _clone(
2754 self,
2754 self,
2755 tr,
2755 tr,
2756 destrevlog,
2756 destrevlog,
2757 addrevisioncb,
2757 addrevisioncb,
2758 deltareuse,
2758 deltareuse,
2759 forcedeltabothparents,
2759 forcedeltabothparents,
2760 sidedata_helpers,
2760 sidedata_helpers,
2761 ):
2761 ):
2762 """perform the core duty of `revlog.clone` after parameter processing"""
2762 """perform the core duty of `revlog.clone` after parameter processing"""
2763 deltacomputer = deltautil.deltacomputer(destrevlog)
2763 deltacomputer = deltautil.deltacomputer(destrevlog)
2764 index = self.index
2764 index = self.index
2765 for rev in self:
2765 for rev in self:
2766 entry = index[rev]
2766 entry = index[rev]
2767
2767
2768 # Some classes override linkrev to take filtered revs into
2768 # Some classes override linkrev to take filtered revs into
2769 # account. Use raw entry from index.
2769 # account. Use raw entry from index.
2770 flags = entry[0] & 0xFFFF
2770 flags = entry[0] & 0xFFFF
2771 linkrev = entry[4]
2771 linkrev = entry[4]
2772 p1 = index[entry[5]][7]
2772 p1 = index[entry[5]][7]
2773 p2 = index[entry[6]][7]
2773 p2 = index[entry[6]][7]
2774 node = entry[7]
2774 node = entry[7]
2775
2775
2776 # (Possibly) reuse the delta from the revlog if allowed and
2776 # (Possibly) reuse the delta from the revlog if allowed and
2777 # the revlog chunk is a delta.
2777 # the revlog chunk is a delta.
2778 cachedelta = None
2778 cachedelta = None
2779 rawtext = None
2779 rawtext = None
2780 if deltareuse == self.DELTAREUSEFULLADD:
2780 if deltareuse == self.DELTAREUSEFULLADD:
2781 text, sidedata = self._revisiondata(rev)
2781 text, sidedata = self._revisiondata(rev)
2782
2782
2783 if sidedata_helpers is not None:
2783 if sidedata_helpers is not None:
2784 (sidedata, new_flags) = storageutil.run_sidedata_helpers(
2784 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
2785 self, sidedata_helpers, sidedata, rev
2785 self, sidedata_helpers, sidedata, rev
2786 )
2786 )
2787 flags = flags | new_flags[0] & ~new_flags[1]
2787 flags = flags | new_flags[0] & ~new_flags[1]
2788
2788
2789 destrevlog.addrevision(
2789 destrevlog.addrevision(
2790 text,
2790 text,
2791 tr,
2791 tr,
2792 linkrev,
2792 linkrev,
2793 p1,
2793 p1,
2794 p2,
2794 p2,
2795 cachedelta=cachedelta,
2795 cachedelta=cachedelta,
2796 node=node,
2796 node=node,
2797 flags=flags,
2797 flags=flags,
2798 deltacomputer=deltacomputer,
2798 deltacomputer=deltacomputer,
2799 sidedata=sidedata,
2799 sidedata=sidedata,
2800 )
2800 )
2801 else:
2801 else:
2802 if destrevlog._lazydelta:
2802 if destrevlog._lazydelta:
2803 dp = self.deltaparent(rev)
2803 dp = self.deltaparent(rev)
2804 if dp != nullrev:
2804 if dp != nullrev:
2805 cachedelta = (dp, bytes(self._chunk(rev)))
2805 cachedelta = (dp, bytes(self._chunk(rev)))
2806
2806
2807 sidedata = None
2807 sidedata = None
2808 if not cachedelta:
2808 if not cachedelta:
2809 rawtext, sidedata = self._revisiondata(rev)
2809 rawtext, sidedata = self._revisiondata(rev)
2810 if sidedata is None:
2810 if sidedata is None:
2811 sidedata = self.sidedata(rev)
2811 sidedata = self.sidedata(rev)
2812
2812
2813 if sidedata_helpers is not None:
2813 if sidedata_helpers is not None:
2814 (sidedata, new_flags) = storageutil.run_sidedata_helpers(
2814 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
2815 self, sidedata_helpers, sidedata, rev
2815 self, sidedata_helpers, sidedata, rev
2816 )
2816 )
2817 flags = flags | new_flags[0] & ~new_flags[1]
2817 flags = flags | new_flags[0] & ~new_flags[1]
2818
2818
2819 ifh = destrevlog.opener(
2819 ifh = destrevlog.opener(
2820 destrevlog.indexfile, b'a+', checkambig=False
2820 destrevlog.indexfile, b'a+', checkambig=False
2821 )
2821 )
2822 dfh = None
2822 dfh = None
2823 if not destrevlog._inline:
2823 if not destrevlog._inline:
2824 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2824 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2825 try:
2825 try:
2826 destrevlog._addrevision(
2826 destrevlog._addrevision(
2827 node,
2827 node,
2828 rawtext,
2828 rawtext,
2829 tr,
2829 tr,
2830 linkrev,
2830 linkrev,
2831 p1,
2831 p1,
2832 p2,
2832 p2,
2833 flags,
2833 flags,
2834 cachedelta,
2834 cachedelta,
2835 ifh,
2835 ifh,
2836 dfh,
2836 dfh,
2837 deltacomputer=deltacomputer,
2837 deltacomputer=deltacomputer,
2838 sidedata=sidedata,
2838 sidedata=sidedata,
2839 )
2839 )
2840 finally:
2840 finally:
2841 if dfh:
2841 if dfh:
2842 dfh.close()
2842 dfh.close()
2843 ifh.close()
2843 ifh.close()
2844
2844
2845 if addrevisioncb:
2845 if addrevisioncb:
2846 addrevisioncb(self, rev, node)
2846 addrevisioncb(self, rev, node)
2847
2847
2848 def censorrevision(self, tr, censornode, tombstone=b''):
2848 def censorrevision(self, tr, censornode, tombstone=b''):
2849 if (self.version & 0xFFFF) == REVLOGV0:
2849 if (self.version & 0xFFFF) == REVLOGV0:
2850 raise error.RevlogError(
2850 raise error.RevlogError(
2851 _(b'cannot censor with version %d revlogs') % self.version
2851 _(b'cannot censor with version %d revlogs') % self.version
2852 )
2852 )
2853
2853
2854 censorrev = self.rev(censornode)
2854 censorrev = self.rev(censornode)
2855 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2855 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2856
2856
2857 if len(tombstone) > self.rawsize(censorrev):
2857 if len(tombstone) > self.rawsize(censorrev):
2858 raise error.Abort(
2858 raise error.Abort(
2859 _(b'censor tombstone must be no longer than censored data')
2859 _(b'censor tombstone must be no longer than censored data')
2860 )
2860 )
2861
2861
2862 # Rewriting the revlog in place is hard. Our strategy for censoring is
2862 # Rewriting the revlog in place is hard. Our strategy for censoring is
2863 # to create a new revlog, copy all revisions to it, then replace the
2863 # to create a new revlog, copy all revisions to it, then replace the
2864 # revlogs on transaction close.
2864 # revlogs on transaction close.
2865
2865
2866 newindexfile = self.indexfile + b'.tmpcensored'
2866 newindexfile = self.indexfile + b'.tmpcensored'
2867 newdatafile = self.datafile + b'.tmpcensored'
2867 newdatafile = self.datafile + b'.tmpcensored'
2868
2868
2869 # This is a bit dangerous. We could easily have a mismatch of state.
2869 # This is a bit dangerous. We could easily have a mismatch of state.
2870 newrl = revlog(
2870 newrl = revlog(
2871 self.opener,
2871 self.opener,
2872 target=self.target,
2872 target=self.target,
2873 indexfile=newindexfile,
2873 indexfile=newindexfile,
2874 datafile=newdatafile,
2874 datafile=newdatafile,
2875 censorable=True,
2875 censorable=True,
2876 )
2876 )
2877 newrl.version = self.version
2877 newrl.version = self.version
2878 newrl._generaldelta = self._generaldelta
2878 newrl._generaldelta = self._generaldelta
2879 newrl._parse_index = self._parse_index
2879 newrl._parse_index = self._parse_index
2880
2880
2881 for rev in self.revs():
2881 for rev in self.revs():
2882 node = self.node(rev)
2882 node = self.node(rev)
2883 p1, p2 = self.parents(node)
2883 p1, p2 = self.parents(node)
2884
2884
2885 if rev == censorrev:
2885 if rev == censorrev:
2886 newrl.addrawrevision(
2886 newrl.addrawrevision(
2887 tombstone,
2887 tombstone,
2888 tr,
2888 tr,
2889 self.linkrev(censorrev),
2889 self.linkrev(censorrev),
2890 p1,
2890 p1,
2891 p2,
2891 p2,
2892 censornode,
2892 censornode,
2893 REVIDX_ISCENSORED,
2893 REVIDX_ISCENSORED,
2894 )
2894 )
2895
2895
2896 if newrl.deltaparent(rev) != nullrev:
2896 if newrl.deltaparent(rev) != nullrev:
2897 raise error.Abort(
2897 raise error.Abort(
2898 _(
2898 _(
2899 b'censored revision stored as delta; '
2899 b'censored revision stored as delta; '
2900 b'cannot censor'
2900 b'cannot censor'
2901 ),
2901 ),
2902 hint=_(
2902 hint=_(
2903 b'censoring of revlogs is not '
2903 b'censoring of revlogs is not '
2904 b'fully implemented; please report '
2904 b'fully implemented; please report '
2905 b'this bug'
2905 b'this bug'
2906 ),
2906 ),
2907 )
2907 )
2908 continue
2908 continue
2909
2909
2910 if self.iscensored(rev):
2910 if self.iscensored(rev):
2911 if self.deltaparent(rev) != nullrev:
2911 if self.deltaparent(rev) != nullrev:
2912 raise error.Abort(
2912 raise error.Abort(
2913 _(
2913 _(
2914 b'cannot censor due to censored '
2914 b'cannot censor due to censored '
2915 b'revision having delta stored'
2915 b'revision having delta stored'
2916 )
2916 )
2917 )
2917 )
2918 rawtext = self._chunk(rev)
2918 rawtext = self._chunk(rev)
2919 else:
2919 else:
2920 rawtext = self.rawdata(rev)
2920 rawtext = self.rawdata(rev)
2921
2921
2922 newrl.addrawrevision(
2922 newrl.addrawrevision(
2923 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2923 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2924 )
2924 )
2925
2925
2926 tr.addbackup(self.indexfile, location=b'store')
2926 tr.addbackup(self.indexfile, location=b'store')
2927 if not self._inline:
2927 if not self._inline:
2928 tr.addbackup(self.datafile, location=b'store')
2928 tr.addbackup(self.datafile, location=b'store')
2929
2929
2930 self.opener.rename(newrl.indexfile, self.indexfile)
2930 self.opener.rename(newrl.indexfile, self.indexfile)
2931 if not self._inline:
2931 if not self._inline:
2932 self.opener.rename(newrl.datafile, self.datafile)
2932 self.opener.rename(newrl.datafile, self.datafile)
2933
2933
2934 self.clearcaches()
2934 self.clearcaches()
2935 self._loadindex()
2935 self._loadindex()
2936
2936
2937 def verifyintegrity(self, state):
2937 def verifyintegrity(self, state):
2938 """Verifies the integrity of the revlog.
2938 """Verifies the integrity of the revlog.
2939
2939
2940 Yields ``revlogproblem`` instances describing problems that are
2940 Yields ``revlogproblem`` instances describing problems that are
2941 found.
2941 found.
2942 """
2942 """
2943 dd, di = self.checksize()
2943 dd, di = self.checksize()
2944 if dd:
2944 if dd:
2945 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2945 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2946 if di:
2946 if di:
2947 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2947 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2948
2948
2949 version = self.version & 0xFFFF
2949 version = self.version & 0xFFFF
2950
2950
2951 # The verifier tells us what version revlog we should be.
2951 # The verifier tells us what version revlog we should be.
2952 if version != state[b'expectedversion']:
2952 if version != state[b'expectedversion']:
2953 yield revlogproblem(
2953 yield revlogproblem(
2954 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2954 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2955 % (self.indexfile, version, state[b'expectedversion'])
2955 % (self.indexfile, version, state[b'expectedversion'])
2956 )
2956 )
2957
2957
2958 state[b'skipread'] = set()
2958 state[b'skipread'] = set()
2959 state[b'safe_renamed'] = set()
2959 state[b'safe_renamed'] = set()
2960
2960
2961 for rev in self:
2961 for rev in self:
2962 node = self.node(rev)
2962 node = self.node(rev)
2963
2963
2964 # Verify contents. 4 cases to care about:
2964 # Verify contents. 4 cases to care about:
2965 #
2965 #
2966 # common: the most common case
2966 # common: the most common case
2967 # rename: with a rename
2967 # rename: with a rename
2968 # meta: file content starts with b'\1\n', the metadata
2968 # meta: file content starts with b'\1\n', the metadata
2969 # header defined in filelog.py, but without a rename
2969 # header defined in filelog.py, but without a rename
2970 # ext: content stored externally
2970 # ext: content stored externally
2971 #
2971 #
2972 # More formally, their differences are shown below:
2972 # More formally, their differences are shown below:
2973 #
2973 #
2974 # | common | rename | meta | ext
2974 # | common | rename | meta | ext
2975 # -------------------------------------------------------
2975 # -------------------------------------------------------
2976 # flags() | 0 | 0 | 0 | not 0
2976 # flags() | 0 | 0 | 0 | not 0
2977 # renamed() | False | True | False | ?
2977 # renamed() | False | True | False | ?
2978 # rawtext[0:2]=='\1\n'| False | True | True | ?
2978 # rawtext[0:2]=='\1\n'| False | True | True | ?
2979 #
2979 #
2980 # "rawtext" means the raw text stored in revlog data, which
2980 # "rawtext" means the raw text stored in revlog data, which
2981 # could be retrieved by "rawdata(rev)". "text"
2981 # could be retrieved by "rawdata(rev)". "text"
2982 # mentioned below is "revision(rev)".
2982 # mentioned below is "revision(rev)".
2983 #
2983 #
2984 # There are 3 different lengths stored physically:
2984 # There are 3 different lengths stored physically:
2985 # 1. L1: rawsize, stored in revlog index
2985 # 1. L1: rawsize, stored in revlog index
2986 # 2. L2: len(rawtext), stored in revlog data
2986 # 2. L2: len(rawtext), stored in revlog data
2987 # 3. L3: len(text), stored in revlog data if flags==0, or
2987 # 3. L3: len(text), stored in revlog data if flags==0, or
2988 # possibly somewhere else if flags!=0
2988 # possibly somewhere else if flags!=0
2989 #
2989 #
2990 # L1 should be equal to L2. L3 could be different from them.
2990 # L1 should be equal to L2. L3 could be different from them.
2991 # "text" may or may not affect commit hash depending on flag
2991 # "text" may or may not affect commit hash depending on flag
2992 # processors (see flagutil.addflagprocessor).
2992 # processors (see flagutil.addflagprocessor).
2993 #
2993 #
2994 # | common | rename | meta | ext
2994 # | common | rename | meta | ext
2995 # -------------------------------------------------
2995 # -------------------------------------------------
2996 # rawsize() | L1 | L1 | L1 | L1
2996 # rawsize() | L1 | L1 | L1 | L1
2997 # size() | L1 | L2-LM | L1(*) | L1 (?)
2997 # size() | L1 | L2-LM | L1(*) | L1 (?)
2998 # len(rawtext) | L2 | L2 | L2 | L2
2998 # len(rawtext) | L2 | L2 | L2 | L2
2999 # len(text) | L2 | L2 | L2 | L3
2999 # len(text) | L2 | L2 | L2 | L3
3000 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3000 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3001 #
3001 #
3002 # LM: length of metadata, depending on rawtext
3002 # LM: length of metadata, depending on rawtext
3003 # (*): not ideal, see comment in filelog.size
3003 # (*): not ideal, see comment in filelog.size
3004 # (?): could be "- len(meta)" if the resolved content has
3004 # (?): could be "- len(meta)" if the resolved content has
3005 # rename metadata
3005 # rename metadata
3006 #
3006 #
3007 # Checks needed to be done:
3007 # Checks needed to be done:
3008 # 1. length check: L1 == L2, in all cases.
3008 # 1. length check: L1 == L2, in all cases.
3009 # 2. hash check: depending on flag processor, we may need to
3009 # 2. hash check: depending on flag processor, we may need to
3010 # use either "text" (external), or "rawtext" (in revlog).
3010 # use either "text" (external), or "rawtext" (in revlog).
3011
3011
3012 try:
3012 try:
3013 skipflags = state.get(b'skipflags', 0)
3013 skipflags = state.get(b'skipflags', 0)
3014 if skipflags:
3014 if skipflags:
3015 skipflags &= self.flags(rev)
3015 skipflags &= self.flags(rev)
3016
3016
3017 _verify_revision(self, skipflags, state, node)
3017 _verify_revision(self, skipflags, state, node)
3018
3018
3019 l1 = self.rawsize(rev)
3019 l1 = self.rawsize(rev)
3020 l2 = len(self.rawdata(node))
3020 l2 = len(self.rawdata(node))
3021
3021
3022 if l1 != l2:
3022 if l1 != l2:
3023 yield revlogproblem(
3023 yield revlogproblem(
3024 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3024 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3025 node=node,
3025 node=node,
3026 )
3026 )
3027
3027
3028 except error.CensoredNodeError:
3028 except error.CensoredNodeError:
3029 if state[b'erroroncensored']:
3029 if state[b'erroroncensored']:
3030 yield revlogproblem(
3030 yield revlogproblem(
3031 error=_(b'censored file data'), node=node
3031 error=_(b'censored file data'), node=node
3032 )
3032 )
3033 state[b'skipread'].add(node)
3033 state[b'skipread'].add(node)
3034 except Exception as e:
3034 except Exception as e:
3035 yield revlogproblem(
3035 yield revlogproblem(
3036 error=_(b'unpacking %s: %s')
3036 error=_(b'unpacking %s: %s')
3037 % (short(node), stringutil.forcebytestr(e)),
3037 % (short(node), stringutil.forcebytestr(e)),
3038 node=node,
3038 node=node,
3039 )
3039 )
3040 state[b'skipread'].add(node)
3040 state[b'skipread'].add(node)
3041
3041
3042 def storageinfo(
3042 def storageinfo(
3043 self,
3043 self,
3044 exclusivefiles=False,
3044 exclusivefiles=False,
3045 sharedfiles=False,
3045 sharedfiles=False,
3046 revisionscount=False,
3046 revisionscount=False,
3047 trackedsize=False,
3047 trackedsize=False,
3048 storedsize=False,
3048 storedsize=False,
3049 ):
3049 ):
3050 d = {}
3050 d = {}
3051
3051
3052 if exclusivefiles:
3052 if exclusivefiles:
3053 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3053 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3054 if not self._inline:
3054 if not self._inline:
3055 d[b'exclusivefiles'].append((self.opener, self.datafile))
3055 d[b'exclusivefiles'].append((self.opener, self.datafile))
3056
3056
3057 if sharedfiles:
3057 if sharedfiles:
3058 d[b'sharedfiles'] = []
3058 d[b'sharedfiles'] = []
3059
3059
3060 if revisionscount:
3060 if revisionscount:
3061 d[b'revisionscount'] = len(self)
3061 d[b'revisionscount'] = len(self)
3062
3062
3063 if trackedsize:
3063 if trackedsize:
3064 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3064 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3065
3065
3066 if storedsize:
3066 if storedsize:
3067 d[b'storedsize'] = sum(
3067 d[b'storedsize'] = sum(
3068 self.opener.stat(path).st_size for path in self.files()
3068 self.opener.stat(path).st_size for path in self.files()
3069 )
3069 )
3070
3070
3071 return d
3071 return d
3072
3072
3073 def rewrite_sidedata(self, helpers, startrev, endrev):
3073 def rewrite_sidedata(self, helpers, startrev, endrev):
3074 if self.version & 0xFFFF != REVLOGV2:
3074 if self.version & 0xFFFF != REVLOGV2:
3075 return
3075 return
3076 # inline are not yet supported because they suffer from an issue when
3076 # inline are not yet supported because they suffer from an issue when
3077 # rewriting them (since it's not an append-only operation).
3077 # rewriting them (since it's not an append-only operation).
3078 # See issue6485.
3078 # See issue6485.
3079 assert not self._inline
3079 assert not self._inline
3080 if not helpers[1] and not helpers[2]:
3080 if not helpers[1] and not helpers[2]:
3081 # Nothing to generate or remove
3081 # Nothing to generate or remove
3082 return
3082 return
3083
3083
3084 new_entries = []
3084 new_entries = []
3085 # append the new sidedata
3085 # append the new sidedata
3086 with self._datafp(b'a+') as fp:
3086 with self._datafp(b'a+') as fp:
3087 # Maybe this bug still exists, see revlog._writeentry
3087 # Maybe this bug still exists, see revlog._writeentry
3088 fp.seek(0, os.SEEK_END)
3088 fp.seek(0, os.SEEK_END)
3089 current_offset = fp.tell()
3089 current_offset = fp.tell()
3090 for rev in range(startrev, endrev + 1):
3090 for rev in range(startrev, endrev + 1):
3091 entry = self.index[rev]
3091 entry = self.index[rev]
3092 new_sidedata, flags = storageutil.run_sidedata_helpers(
3092 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3093 store=self,
3093 store=self,
3094 sidedata_helpers=helpers,
3094 sidedata_helpers=helpers,
3095 sidedata={},
3095 sidedata={},
3096 rev=rev,
3096 rev=rev,
3097 )
3097 )
3098
3098
3099 serialized_sidedata = sidedatautil.serialize_sidedata(
3099 serialized_sidedata = sidedatautil.serialize_sidedata(
3100 new_sidedata
3100 new_sidedata
3101 )
3101 )
3102 if entry[8] != 0 or entry[9] != 0:
3102 if entry[8] != 0 or entry[9] != 0:
3103 # rewriting entries that already have sidedata is not
3103 # rewriting entries that already have sidedata is not
3104 # supported yet, because it introduces garbage data in the
3104 # supported yet, because it introduces garbage data in the
3105 # revlog.
3105 # revlog.
3106 msg = b"Rewriting existing sidedata is not supported yet"
3106 msg = b"Rewriting existing sidedata is not supported yet"
3107 raise error.Abort(msg)
3107 raise error.Abort(msg)
3108
3108
3109 # Apply (potential) flags to add and to remove after running
3109 # Apply (potential) flags to add and to remove after running
3110 # the sidedata helpers
3110 # the sidedata helpers
3111 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3111 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3112 entry = (new_offset_flags,) + entry[1:8]
3112 entry = (new_offset_flags,) + entry[1:8]
3113 entry += (current_offset, len(serialized_sidedata))
3113 entry += (current_offset, len(serialized_sidedata))
3114
3114
3115 fp.write(serialized_sidedata)
3115 fp.write(serialized_sidedata)
3116 new_entries.append(entry)
3116 new_entries.append(entry)
3117 current_offset += len(serialized_sidedata)
3117 current_offset += len(serialized_sidedata)
3118
3118
3119 # rewrite the new index entries
3119 # rewrite the new index entries
3120 with self._indexfp(b'w+') as fp:
3120 with self._indexfp(b'w+') as fp:
3121 fp.seek(startrev * self.index.entry_size)
3121 fp.seek(startrev * self.index.entry_size)
3122 for i, e in enumerate(new_entries):
3122 for i, e in enumerate(new_entries):
3123 rev = startrev + i
3123 rev = startrev + i
3124 self.index.replace_sidedata_info(rev, e[8], e[9], e[0])
3124 self.index.replace_sidedata_info(rev, e[8], e[9], e[0])
3125 packed = self.index.entry_binary(rev)
3125 packed = self.index.entry_binary(rev)
3126 if rev == 0:
3126 if rev == 0:
3127 header = self.index.pack_header(self.version)
3127 header = self.index.pack_header(self.version)
3128 packed = header + packed
3128 packed = header + packed
3129 fp.write(packed)
3129 fp.write(packed)
@@ -1,93 +1,155 b''
1 # sidedata.py - Logic around store extra data alongside revlog revisions
1 # sidedata.py - Logic around store extra data alongside revlog revisions
2 #
2 #
3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net)
3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net)
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 """core code for "sidedata" support
7 """core code for "sidedata" support
8
8
9 The "sidedata" are stored alongside the revision without actually being part of
9 The "sidedata" are stored alongside the revision without actually being part of
10 its content and not affecting its hash. It's main use cases is to cache
10 its content and not affecting its hash. It's main use cases is to cache
11 important information related to a changesets.
11 important information related to a changesets.
12
12
13 The current implementation is experimental and subject to changes. Do not rely
13 The current implementation is experimental and subject to changes. Do not rely
14 on it in production.
14 on it in production.
15
15
16 Sidedata are stored in the revlog itself, thanks to a new version of the
16 Sidedata are stored in the revlog itself, thanks to a new version of the
17 revlog. The following format is currently used::
17 revlog. The following format is currently used::
18
18
19 initial header:
19 initial header:
20 <number of sidedata; 2 bytes>
20 <number of sidedata; 2 bytes>
21 sidedata (repeated N times):
21 sidedata (repeated N times):
22 <sidedata-key; 2 bytes>
22 <sidedata-key; 2 bytes>
23 <sidedata-entry-length: 4 bytes>
23 <sidedata-entry-length: 4 bytes>
24 <sidedata-content-sha1-digest: 20 bytes>
24 <sidedata-content-sha1-digest: 20 bytes>
25 <sidedata-content; X bytes>
25 <sidedata-content; X bytes>
26 normal raw text:
26 normal raw text:
27 <all bytes remaining in the rawtext>
27 <all bytes remaining in the rawtext>
28
28
29 This is a simple and effective format. It should be enough to experiment with
29 This is a simple and effective format. It should be enough to experiment with
30 the concept.
30 the concept.
31 """
31 """
32
32
33 from __future__ import absolute_import
33 from __future__ import absolute_import
34
34
35 import collections
35 import struct
36 import struct
36
37
37 from .. import error
38 from .. import error, requirements as requirementsmod
39 from ..revlogutils import constants, flagutil
38 from ..utils import hashutil
40 from ..utils import hashutil
39
41
40 ## sidedata type constant
42 ## sidedata type constant
41 # reserve a block for testing purposes.
43 # reserve a block for testing purposes.
42 SD_TEST1 = 1
44 SD_TEST1 = 1
43 SD_TEST2 = 2
45 SD_TEST2 = 2
44 SD_TEST3 = 3
46 SD_TEST3 = 3
45 SD_TEST4 = 4
47 SD_TEST4 = 4
46 SD_TEST5 = 5
48 SD_TEST5 = 5
47 SD_TEST6 = 6
49 SD_TEST6 = 6
48 SD_TEST7 = 7
50 SD_TEST7 = 7
49
51
50 # key to store copies related information
52 # key to store copies related information
51 SD_P1COPIES = 8
53 SD_P1COPIES = 8
52 SD_P2COPIES = 9
54 SD_P2COPIES = 9
53 SD_FILESADDED = 10
55 SD_FILESADDED = 10
54 SD_FILESREMOVED = 11
56 SD_FILESREMOVED = 11
55 SD_FILES = 12
57 SD_FILES = 12
56
58
57 # internal format constant
59 # internal format constant
58 SIDEDATA_HEADER = struct.Struct('>H')
60 SIDEDATA_HEADER = struct.Struct('>H')
59 SIDEDATA_ENTRY = struct.Struct('>HL20s')
61 SIDEDATA_ENTRY = struct.Struct('>HL20s')
60
62
61
63
62 def serialize_sidedata(sidedata):
64 def serialize_sidedata(sidedata):
63 sidedata = list(sidedata.items())
65 sidedata = list(sidedata.items())
64 sidedata.sort()
66 sidedata.sort()
65 buf = [SIDEDATA_HEADER.pack(len(sidedata))]
67 buf = [SIDEDATA_HEADER.pack(len(sidedata))]
66 for key, value in sidedata:
68 for key, value in sidedata:
67 digest = hashutil.sha1(value).digest()
69 digest = hashutil.sha1(value).digest()
68 buf.append(SIDEDATA_ENTRY.pack(key, len(value), digest))
70 buf.append(SIDEDATA_ENTRY.pack(key, len(value), digest))
69 for key, value in sidedata:
71 for key, value in sidedata:
70 buf.append(value)
72 buf.append(value)
71 buf = b''.join(buf)
73 buf = b''.join(buf)
72 return buf
74 return buf
73
75
74
76
75 def deserialize_sidedata(blob):
77 def deserialize_sidedata(blob):
76 sidedata = {}
78 sidedata = {}
77 offset = 0
79 offset = 0
78 (nbentry,) = SIDEDATA_HEADER.unpack(blob[: SIDEDATA_HEADER.size])
80 (nbentry,) = SIDEDATA_HEADER.unpack(blob[: SIDEDATA_HEADER.size])
79 offset += SIDEDATA_HEADER.size
81 offset += SIDEDATA_HEADER.size
80 dataoffset = SIDEDATA_HEADER.size + (SIDEDATA_ENTRY.size * nbentry)
82 dataoffset = SIDEDATA_HEADER.size + (SIDEDATA_ENTRY.size * nbentry)
81 for i in range(nbentry):
83 for i in range(nbentry):
82 nextoffset = offset + SIDEDATA_ENTRY.size
84 nextoffset = offset + SIDEDATA_ENTRY.size
83 key, size, storeddigest = SIDEDATA_ENTRY.unpack(blob[offset:nextoffset])
85 key, size, storeddigest = SIDEDATA_ENTRY.unpack(blob[offset:nextoffset])
84 offset = nextoffset
86 offset = nextoffset
85 # read the data associated with that entry
87 # read the data associated with that entry
86 nextdataoffset = dataoffset + size
88 nextdataoffset = dataoffset + size
87 entrytext = bytes(blob[dataoffset:nextdataoffset])
89 entrytext = bytes(blob[dataoffset:nextdataoffset])
88 readdigest = hashutil.sha1(entrytext).digest()
90 readdigest = hashutil.sha1(entrytext).digest()
89 if storeddigest != readdigest:
91 if storeddigest != readdigest:
90 raise error.SidedataHashError(key, storeddigest, readdigest)
92 raise error.SidedataHashError(key, storeddigest, readdigest)
91 sidedata[key] = entrytext
93 sidedata[key] = entrytext
92 dataoffset = nextdataoffset
94 dataoffset = nextdataoffset
93 return sidedata
95 return sidedata
96
97
98 def get_sidedata_helpers(repo, remote_sd_categories, pull=False):
99 # Computers for computing sidedata on-the-fly
100 sd_computers = collections.defaultdict(list)
101 # Computers for categories to remove from sidedata
102 sd_removers = collections.defaultdict(list)
103 to_generate = remote_sd_categories - repo._wanted_sidedata
104 to_remove = repo._wanted_sidedata - remote_sd_categories
105 if pull:
106 to_generate, to_remove = to_remove, to_generate
107
108 for revlog_kind, computers in repo._sidedata_computers.items():
109 for category, computer in computers.items():
110 if category in to_generate:
111 sd_computers[revlog_kind].append(computer)
112 if category in to_remove:
113 sd_removers[revlog_kind].append(computer)
114
115 sidedata_helpers = (repo, sd_computers, sd_removers)
116 return sidedata_helpers
117
118
119 def run_sidedata_helpers(store, sidedata_helpers, sidedata, rev):
120 """Returns the sidedata for the given revision after running through
121 the given helpers.
122 - `store`: the revlog this applies to (changelog, manifest, or filelog
123 instance)
124 - `sidedata_helpers`: see `storageutil.emitrevisions`
125 - `sidedata`: previous sidedata at the given rev, if any
126 - `rev`: affected rev of `store`
127 """
128 repo, sd_computers, sd_removers = sidedata_helpers
129 kind = store.revlog_kind
130 flags_to_add = 0
131 flags_to_remove = 0
132 for _keys, sd_computer, _flags in sd_computers.get(kind, []):
133 sidedata, flags = sd_computer(repo, store, rev, sidedata)
134 flags_to_add |= flags[0]
135 flags_to_remove |= flags[1]
136 for keys, _computer, flags in sd_removers.get(kind, []):
137 for key in keys:
138 sidedata.pop(key, None)
139 flags_to_remove |= flags
140 return sidedata, (flags_to_add, flags_to_remove)
141
142
143 def set_sidedata_spec_for_repo(repo):
144 # prevent cycle metadata -> revlogutils.sidedata -> metadata
145 from .. import metadata
146
147 if requirementsmod.COPIESSDC_REQUIREMENT in repo.requirements:
148 repo.register_wanted_sidedata(SD_FILES)
149 repo.register_sidedata_computer(
150 constants.KIND_CHANGELOG,
151 SD_FILES,
152 (SD_FILES,),
153 metadata.copies_sidedata_computer,
154 flagutil.REVIDX_HASCOPIESINFO,
155 )
@@ -1,594 +1,593 b''
1 # upgrade.py - functions for in place upgrade of Mercurial repository
1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 #
2 #
3 # Copyright (c) 2016-present, Gregory Szorc
3 # Copyright (c) 2016-present, Gregory Szorc
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 stat
10 import stat
11
11
12 from ..i18n import _
12 from ..i18n import _
13 from ..pycompat import getattr
13 from ..pycompat import getattr
14 from .. import (
14 from .. import (
15 changegroup,
16 changelog,
15 changelog,
17 error,
16 error,
18 filelog,
17 filelog,
19 manifest,
18 manifest,
20 metadata,
19 metadata,
21 pycompat,
20 pycompat,
22 requirements,
21 requirements,
23 scmutil,
22 scmutil,
24 store,
23 store,
25 util,
24 util,
26 vfs as vfsmod,
25 vfs as vfsmod,
27 )
26 )
28 from ..revlogutils import (
27 from ..revlogutils import (
29 constants as revlogconst,
28 constants as revlogconst,
30 flagutil,
29 flagutil,
31 nodemap,
30 nodemap,
32 sidedata as sidedatamod,
31 sidedata as sidedatamod,
33 )
32 )
34
33
35
34
36 def get_sidedata_helpers(srcrepo, dstrepo):
35 def get_sidedata_helpers(srcrepo, dstrepo):
37 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
36 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
38 sequential = pycompat.iswindows or not use_w
37 sequential = pycompat.iswindows or not use_w
39 if not sequential:
38 if not sequential:
40 srcrepo.register_sidedata_computer(
39 srcrepo.register_sidedata_computer(
41 revlogconst.KIND_CHANGELOG,
40 revlogconst.KIND_CHANGELOG,
42 sidedatamod.SD_FILES,
41 sidedatamod.SD_FILES,
43 (sidedatamod.SD_FILES,),
42 (sidedatamod.SD_FILES,),
44 metadata._get_worker_sidedata_adder(srcrepo, dstrepo),
43 metadata._get_worker_sidedata_adder(srcrepo, dstrepo),
45 flagutil.REVIDX_HASCOPIESINFO,
44 flagutil.REVIDX_HASCOPIESINFO,
46 replace=True,
45 replace=True,
47 )
46 )
48 return changegroup.get_sidedata_helpers(srcrepo, dstrepo._wanted_sidedata)
47 return sidedatamod.get_sidedata_helpers(srcrepo, dstrepo._wanted_sidedata)
49
48
50
49
51 def _revlogfrompath(repo, rl_type, path):
50 def _revlogfrompath(repo, rl_type, path):
52 """Obtain a revlog from a repo path.
51 """Obtain a revlog from a repo path.
53
52
54 An instance of the appropriate class is returned.
53 An instance of the appropriate class is returned.
55 """
54 """
56 if rl_type & store.FILEFLAGS_CHANGELOG:
55 if rl_type & store.FILEFLAGS_CHANGELOG:
57 return changelog.changelog(repo.svfs)
56 return changelog.changelog(repo.svfs)
58 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
57 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
59 mandir = b''
58 mandir = b''
60 if b'/' in path:
59 if b'/' in path:
61 mandir = path.rsplit(b'/', 1)[0]
60 mandir = path.rsplit(b'/', 1)[0]
62 return manifest.manifestrevlog(
61 return manifest.manifestrevlog(
63 repo.nodeconstants, repo.svfs, tree=mandir
62 repo.nodeconstants, repo.svfs, tree=mandir
64 )
63 )
65 else:
64 else:
66 # drop the extension and the `data/` prefix
65 # drop the extension and the `data/` prefix
67 path = path.rsplit(b'.', 1)[0].split(b'/', 1)[1]
66 path = path.rsplit(b'.', 1)[0].split(b'/', 1)[1]
68 return filelog.filelog(repo.svfs, path)
67 return filelog.filelog(repo.svfs, path)
69
68
70
69
71 def _copyrevlog(tr, destrepo, oldrl, rl_type, unencodedname):
70 def _copyrevlog(tr, destrepo, oldrl, rl_type, unencodedname):
72 """copy all relevant files for `oldrl` into `destrepo` store
71 """copy all relevant files for `oldrl` into `destrepo` store
73
72
74 Files are copied "as is" without any transformation. The copy is performed
73 Files are copied "as is" without any transformation. The copy is performed
75 without extra checks. Callers are responsible for making sure the copied
74 without extra checks. Callers are responsible for making sure the copied
76 content is compatible with format of the destination repository.
75 content is compatible with format of the destination repository.
77 """
76 """
78 oldrl = getattr(oldrl, '_revlog', oldrl)
77 oldrl = getattr(oldrl, '_revlog', oldrl)
79 newrl = _revlogfrompath(destrepo, rl_type, unencodedname)
78 newrl = _revlogfrompath(destrepo, rl_type, unencodedname)
80 newrl = getattr(newrl, '_revlog', newrl)
79 newrl = getattr(newrl, '_revlog', newrl)
81
80
82 oldvfs = oldrl.opener
81 oldvfs = oldrl.opener
83 newvfs = newrl.opener
82 newvfs = newrl.opener
84 oldindex = oldvfs.join(oldrl.indexfile)
83 oldindex = oldvfs.join(oldrl.indexfile)
85 newindex = newvfs.join(newrl.indexfile)
84 newindex = newvfs.join(newrl.indexfile)
86 olddata = oldvfs.join(oldrl.datafile)
85 olddata = oldvfs.join(oldrl.datafile)
87 newdata = newvfs.join(newrl.datafile)
86 newdata = newvfs.join(newrl.datafile)
88
87
89 with newvfs(newrl.indexfile, b'w'):
88 with newvfs(newrl.indexfile, b'w'):
90 pass # create all the directories
89 pass # create all the directories
91
90
92 util.copyfile(oldindex, newindex)
91 util.copyfile(oldindex, newindex)
93 copydata = oldrl.opener.exists(oldrl.datafile)
92 copydata = oldrl.opener.exists(oldrl.datafile)
94 if copydata:
93 if copydata:
95 util.copyfile(olddata, newdata)
94 util.copyfile(olddata, newdata)
96
95
97 if rl_type & store.FILEFLAGS_FILELOG:
96 if rl_type & store.FILEFLAGS_FILELOG:
98 destrepo.svfs.fncache.add(unencodedname)
97 destrepo.svfs.fncache.add(unencodedname)
99 if copydata:
98 if copydata:
100 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
99 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
101
100
102
101
103 UPGRADE_CHANGELOG = b"changelog"
102 UPGRADE_CHANGELOG = b"changelog"
104 UPGRADE_MANIFEST = b"manifest"
103 UPGRADE_MANIFEST = b"manifest"
105 UPGRADE_FILELOGS = b"all-filelogs"
104 UPGRADE_FILELOGS = b"all-filelogs"
106
105
107 UPGRADE_ALL_REVLOGS = frozenset(
106 UPGRADE_ALL_REVLOGS = frozenset(
108 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOGS]
107 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOGS]
109 )
108 )
110
109
111
110
112 def matchrevlog(revlogfilter, rl_type):
111 def matchrevlog(revlogfilter, rl_type):
113 """check if a revlog is selected for cloning.
112 """check if a revlog is selected for cloning.
114
113
115 In other words, are there any updates which need to be done on revlog
114 In other words, are there any updates which need to be done on revlog
116 or it can be blindly copied.
115 or it can be blindly copied.
117
116
118 The store entry is checked against the passed filter"""
117 The store entry is checked against the passed filter"""
119 if rl_type & store.FILEFLAGS_CHANGELOG:
118 if rl_type & store.FILEFLAGS_CHANGELOG:
120 return UPGRADE_CHANGELOG in revlogfilter
119 return UPGRADE_CHANGELOG in revlogfilter
121 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
120 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
122 return UPGRADE_MANIFEST in revlogfilter
121 return UPGRADE_MANIFEST in revlogfilter
123 assert rl_type & store.FILEFLAGS_FILELOG
122 assert rl_type & store.FILEFLAGS_FILELOG
124 return UPGRADE_FILELOGS in revlogfilter
123 return UPGRADE_FILELOGS in revlogfilter
125
124
126
125
127 def _perform_clone(
126 def _perform_clone(
128 ui,
127 ui,
129 dstrepo,
128 dstrepo,
130 tr,
129 tr,
131 old_revlog,
130 old_revlog,
132 rl_type,
131 rl_type,
133 unencoded,
132 unencoded,
134 upgrade_op,
133 upgrade_op,
135 sidedata_helpers,
134 sidedata_helpers,
136 oncopiedrevision,
135 oncopiedrevision,
137 ):
136 ):
138 """ returns the new revlog object created"""
137 """ returns the new revlog object created"""
139 newrl = None
138 newrl = None
140 if matchrevlog(upgrade_op.revlogs_to_process, rl_type):
139 if matchrevlog(upgrade_op.revlogs_to_process, rl_type):
141 ui.note(
140 ui.note(
142 _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded)
141 _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded)
143 )
142 )
144 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
143 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
145 old_revlog.clone(
144 old_revlog.clone(
146 tr,
145 tr,
147 newrl,
146 newrl,
148 addrevisioncb=oncopiedrevision,
147 addrevisioncb=oncopiedrevision,
149 deltareuse=upgrade_op.delta_reuse_mode,
148 deltareuse=upgrade_op.delta_reuse_mode,
150 forcedeltabothparents=upgrade_op.force_re_delta_both_parents,
149 forcedeltabothparents=upgrade_op.force_re_delta_both_parents,
151 sidedata_helpers=sidedata_helpers,
150 sidedata_helpers=sidedata_helpers,
152 )
151 )
153 else:
152 else:
154 msg = _(b'blindly copying %s containing %i revisions\n')
153 msg = _(b'blindly copying %s containing %i revisions\n')
155 ui.note(msg % (unencoded, len(old_revlog)))
154 ui.note(msg % (unencoded, len(old_revlog)))
156 _copyrevlog(tr, dstrepo, old_revlog, rl_type, unencoded)
155 _copyrevlog(tr, dstrepo, old_revlog, rl_type, unencoded)
157
156
158 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
157 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
159 return newrl
158 return newrl
160
159
161
160
162 def _clonerevlogs(
161 def _clonerevlogs(
163 ui,
162 ui,
164 srcrepo,
163 srcrepo,
165 dstrepo,
164 dstrepo,
166 tr,
165 tr,
167 upgrade_op,
166 upgrade_op,
168 ):
167 ):
169 """Copy revlogs between 2 repos."""
168 """Copy revlogs between 2 repos."""
170 revcount = 0
169 revcount = 0
171 srcsize = 0
170 srcsize = 0
172 srcrawsize = 0
171 srcrawsize = 0
173 dstsize = 0
172 dstsize = 0
174 fcount = 0
173 fcount = 0
175 frevcount = 0
174 frevcount = 0
176 fsrcsize = 0
175 fsrcsize = 0
177 frawsize = 0
176 frawsize = 0
178 fdstsize = 0
177 fdstsize = 0
179 mcount = 0
178 mcount = 0
180 mrevcount = 0
179 mrevcount = 0
181 msrcsize = 0
180 msrcsize = 0
182 mrawsize = 0
181 mrawsize = 0
183 mdstsize = 0
182 mdstsize = 0
184 crevcount = 0
183 crevcount = 0
185 csrcsize = 0
184 csrcsize = 0
186 crawsize = 0
185 crawsize = 0
187 cdstsize = 0
186 cdstsize = 0
188
187
189 alldatafiles = list(srcrepo.store.walk())
188 alldatafiles = list(srcrepo.store.walk())
190 # mapping of data files which needs to be cloned
189 # mapping of data files which needs to be cloned
191 # key is unencoded filename
190 # key is unencoded filename
192 # value is revlog_object_from_srcrepo
191 # value is revlog_object_from_srcrepo
193 manifests = {}
192 manifests = {}
194 changelogs = {}
193 changelogs = {}
195 filelogs = {}
194 filelogs = {}
196
195
197 # Perform a pass to collect metadata. This validates we can open all
196 # Perform a pass to collect metadata. This validates we can open all
198 # source files and allows a unified progress bar to be displayed.
197 # source files and allows a unified progress bar to be displayed.
199 for rl_type, unencoded, encoded, size in alldatafiles:
198 for rl_type, unencoded, encoded, size in alldatafiles:
200 if not rl_type & store.FILEFLAGS_REVLOG_MAIN:
199 if not rl_type & store.FILEFLAGS_REVLOG_MAIN:
201 continue
200 continue
202
201
203 rl = _revlogfrompath(srcrepo, rl_type, unencoded)
202 rl = _revlogfrompath(srcrepo, rl_type, unencoded)
204
203
205 info = rl.storageinfo(
204 info = rl.storageinfo(
206 exclusivefiles=True,
205 exclusivefiles=True,
207 revisionscount=True,
206 revisionscount=True,
208 trackedsize=True,
207 trackedsize=True,
209 storedsize=True,
208 storedsize=True,
210 )
209 )
211
210
212 revcount += info[b'revisionscount'] or 0
211 revcount += info[b'revisionscount'] or 0
213 datasize = info[b'storedsize'] or 0
212 datasize = info[b'storedsize'] or 0
214 rawsize = info[b'trackedsize'] or 0
213 rawsize = info[b'trackedsize'] or 0
215
214
216 srcsize += datasize
215 srcsize += datasize
217 srcrawsize += rawsize
216 srcrawsize += rawsize
218
217
219 # This is for the separate progress bars.
218 # This is for the separate progress bars.
220 if rl_type & store.FILEFLAGS_CHANGELOG:
219 if rl_type & store.FILEFLAGS_CHANGELOG:
221 changelogs[unencoded] = (rl_type, rl)
220 changelogs[unencoded] = (rl_type, rl)
222 crevcount += len(rl)
221 crevcount += len(rl)
223 csrcsize += datasize
222 csrcsize += datasize
224 crawsize += rawsize
223 crawsize += rawsize
225 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
224 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
226 manifests[unencoded] = (rl_type, rl)
225 manifests[unencoded] = (rl_type, rl)
227 mcount += 1
226 mcount += 1
228 mrevcount += len(rl)
227 mrevcount += len(rl)
229 msrcsize += datasize
228 msrcsize += datasize
230 mrawsize += rawsize
229 mrawsize += rawsize
231 elif rl_type & store.FILEFLAGS_FILELOG:
230 elif rl_type & store.FILEFLAGS_FILELOG:
232 filelogs[unencoded] = (rl_type, rl)
231 filelogs[unencoded] = (rl_type, rl)
233 fcount += 1
232 fcount += 1
234 frevcount += len(rl)
233 frevcount += len(rl)
235 fsrcsize += datasize
234 fsrcsize += datasize
236 frawsize += rawsize
235 frawsize += rawsize
237 else:
236 else:
238 error.ProgrammingError(b'unknown revlog type')
237 error.ProgrammingError(b'unknown revlog type')
239
238
240 if not revcount:
239 if not revcount:
241 return
240 return
242
241
243 ui.status(
242 ui.status(
244 _(
243 _(
245 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
244 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
246 b'%d in changelog)\n'
245 b'%d in changelog)\n'
247 )
246 )
248 % (revcount, frevcount, mrevcount, crevcount)
247 % (revcount, frevcount, mrevcount, crevcount)
249 )
248 )
250 ui.status(
249 ui.status(
251 _(b'migrating %s in store; %s tracked data\n')
250 _(b'migrating %s in store; %s tracked data\n')
252 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
251 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
253 )
252 )
254
253
255 # Used to keep track of progress.
254 # Used to keep track of progress.
256 progress = None
255 progress = None
257
256
258 def oncopiedrevision(rl, rev, node):
257 def oncopiedrevision(rl, rev, node):
259 progress.increment()
258 progress.increment()
260
259
261 sidedata_helpers = get_sidedata_helpers(srcrepo, dstrepo)
260 sidedata_helpers = get_sidedata_helpers(srcrepo, dstrepo)
262
261
263 # Migrating filelogs
262 # Migrating filelogs
264 ui.status(
263 ui.status(
265 _(
264 _(
266 b'migrating %d filelogs containing %d revisions '
265 b'migrating %d filelogs containing %d revisions '
267 b'(%s in store; %s tracked data)\n'
266 b'(%s in store; %s tracked data)\n'
268 )
267 )
269 % (
268 % (
270 fcount,
269 fcount,
271 frevcount,
270 frevcount,
272 util.bytecount(fsrcsize),
271 util.bytecount(fsrcsize),
273 util.bytecount(frawsize),
272 util.bytecount(frawsize),
274 )
273 )
275 )
274 )
276 progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
275 progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
277 for unencoded, (rl_type, oldrl) in sorted(filelogs.items()):
276 for unencoded, (rl_type, oldrl) in sorted(filelogs.items()):
278 newrl = _perform_clone(
277 newrl = _perform_clone(
279 ui,
278 ui,
280 dstrepo,
279 dstrepo,
281 tr,
280 tr,
282 oldrl,
281 oldrl,
283 rl_type,
282 rl_type,
284 unencoded,
283 unencoded,
285 upgrade_op,
284 upgrade_op,
286 sidedata_helpers,
285 sidedata_helpers,
287 oncopiedrevision,
286 oncopiedrevision,
288 )
287 )
289 info = newrl.storageinfo(storedsize=True)
288 info = newrl.storageinfo(storedsize=True)
290 fdstsize += info[b'storedsize'] or 0
289 fdstsize += info[b'storedsize'] or 0
291 ui.status(
290 ui.status(
292 _(
291 _(
293 b'finished migrating %d filelog revisions across %d '
292 b'finished migrating %d filelog revisions across %d '
294 b'filelogs; change in size: %s\n'
293 b'filelogs; change in size: %s\n'
295 )
294 )
296 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
295 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
297 )
296 )
298
297
299 # Migrating manifests
298 # Migrating manifests
300 ui.status(
299 ui.status(
301 _(
300 _(
302 b'migrating %d manifests containing %d revisions '
301 b'migrating %d manifests containing %d revisions '
303 b'(%s in store; %s tracked data)\n'
302 b'(%s in store; %s tracked data)\n'
304 )
303 )
305 % (
304 % (
306 mcount,
305 mcount,
307 mrevcount,
306 mrevcount,
308 util.bytecount(msrcsize),
307 util.bytecount(msrcsize),
309 util.bytecount(mrawsize),
308 util.bytecount(mrawsize),
310 )
309 )
311 )
310 )
312 if progress:
311 if progress:
313 progress.complete()
312 progress.complete()
314 progress = srcrepo.ui.makeprogress(
313 progress = srcrepo.ui.makeprogress(
315 _(b'manifest revisions'), total=mrevcount
314 _(b'manifest revisions'), total=mrevcount
316 )
315 )
317 for unencoded, (rl_type, oldrl) in sorted(manifests.items()):
316 for unencoded, (rl_type, oldrl) in sorted(manifests.items()):
318 newrl = _perform_clone(
317 newrl = _perform_clone(
319 ui,
318 ui,
320 dstrepo,
319 dstrepo,
321 tr,
320 tr,
322 oldrl,
321 oldrl,
323 rl_type,
322 rl_type,
324 unencoded,
323 unencoded,
325 upgrade_op,
324 upgrade_op,
326 sidedata_helpers,
325 sidedata_helpers,
327 oncopiedrevision,
326 oncopiedrevision,
328 )
327 )
329 info = newrl.storageinfo(storedsize=True)
328 info = newrl.storageinfo(storedsize=True)
330 mdstsize += info[b'storedsize'] or 0
329 mdstsize += info[b'storedsize'] or 0
331 ui.status(
330 ui.status(
332 _(
331 _(
333 b'finished migrating %d manifest revisions across %d '
332 b'finished migrating %d manifest revisions across %d '
334 b'manifests; change in size: %s\n'
333 b'manifests; change in size: %s\n'
335 )
334 )
336 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
335 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
337 )
336 )
338
337
339 # Migrating changelog
338 # Migrating changelog
340 ui.status(
339 ui.status(
341 _(
340 _(
342 b'migrating changelog containing %d revisions '
341 b'migrating changelog containing %d revisions '
343 b'(%s in store; %s tracked data)\n'
342 b'(%s in store; %s tracked data)\n'
344 )
343 )
345 % (
344 % (
346 crevcount,
345 crevcount,
347 util.bytecount(csrcsize),
346 util.bytecount(csrcsize),
348 util.bytecount(crawsize),
347 util.bytecount(crawsize),
349 )
348 )
350 )
349 )
351 if progress:
350 if progress:
352 progress.complete()
351 progress.complete()
353 progress = srcrepo.ui.makeprogress(
352 progress = srcrepo.ui.makeprogress(
354 _(b'changelog revisions'), total=crevcount
353 _(b'changelog revisions'), total=crevcount
355 )
354 )
356 for unencoded, (rl_type, oldrl) in sorted(changelogs.items()):
355 for unencoded, (rl_type, oldrl) in sorted(changelogs.items()):
357 newrl = _perform_clone(
356 newrl = _perform_clone(
358 ui,
357 ui,
359 dstrepo,
358 dstrepo,
360 tr,
359 tr,
361 oldrl,
360 oldrl,
362 rl_type,
361 rl_type,
363 unencoded,
362 unencoded,
364 upgrade_op,
363 upgrade_op,
365 sidedata_helpers,
364 sidedata_helpers,
366 oncopiedrevision,
365 oncopiedrevision,
367 )
366 )
368 info = newrl.storageinfo(storedsize=True)
367 info = newrl.storageinfo(storedsize=True)
369 cdstsize += info[b'storedsize'] or 0
368 cdstsize += info[b'storedsize'] or 0
370 progress.complete()
369 progress.complete()
371 ui.status(
370 ui.status(
372 _(
371 _(
373 b'finished migrating %d changelog revisions; change in size: '
372 b'finished migrating %d changelog revisions; change in size: '
374 b'%s\n'
373 b'%s\n'
375 )
374 )
376 % (crevcount, util.bytecount(cdstsize - csrcsize))
375 % (crevcount, util.bytecount(cdstsize - csrcsize))
377 )
376 )
378
377
379 dstsize = fdstsize + mdstsize + cdstsize
378 dstsize = fdstsize + mdstsize + cdstsize
380 ui.status(
379 ui.status(
381 _(
380 _(
382 b'finished migrating %d total revisions; total change in store '
381 b'finished migrating %d total revisions; total change in store '
383 b'size: %s\n'
382 b'size: %s\n'
384 )
383 )
385 % (revcount, util.bytecount(dstsize - srcsize))
384 % (revcount, util.bytecount(dstsize - srcsize))
386 )
385 )
387
386
388
387
389 def _files_to_copy_post_revlog_clone(srcrepo):
388 def _files_to_copy_post_revlog_clone(srcrepo):
390 """yields files which should be copied to destination after revlogs
389 """yields files which should be copied to destination after revlogs
391 are cloned"""
390 are cloned"""
392 for path, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
391 for path, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
393 # don't copy revlogs as they are already cloned
392 # don't copy revlogs as they are already cloned
394 if store.revlog_type(path) is not None:
393 if store.revlog_type(path) is not None:
395 continue
394 continue
396 # Skip transaction related files.
395 # Skip transaction related files.
397 if path.startswith(b'undo'):
396 if path.startswith(b'undo'):
398 continue
397 continue
399 # Only copy regular files.
398 # Only copy regular files.
400 if kind != stat.S_IFREG:
399 if kind != stat.S_IFREG:
401 continue
400 continue
402 # Skip other skipped files.
401 # Skip other skipped files.
403 if path in (b'lock', b'fncache'):
402 if path in (b'lock', b'fncache'):
404 continue
403 continue
405 # TODO: should we skip cache too?
404 # TODO: should we skip cache too?
406
405
407 yield path
406 yield path
408
407
409
408
410 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
409 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
411 """Replace the stores after current repository is upgraded
410 """Replace the stores after current repository is upgraded
412
411
413 Creates a backup of current repository store at backup path
412 Creates a backup of current repository store at backup path
414 Replaces upgraded store files in current repo from upgraded one
413 Replaces upgraded store files in current repo from upgraded one
415
414
416 Arguments:
415 Arguments:
417 currentrepo: repo object of current repository
416 currentrepo: repo object of current repository
418 upgradedrepo: repo object of the upgraded data
417 upgradedrepo: repo object of the upgraded data
419 backupvfs: vfs object for the backup path
418 backupvfs: vfs object for the backup path
420 upgrade_op: upgrade operation object
419 upgrade_op: upgrade operation object
421 to be used to decide what all is upgraded
420 to be used to decide what all is upgraded
422 """
421 """
423 # TODO: don't blindly rename everything in store
422 # TODO: don't blindly rename everything in store
424 # There can be upgrades where store is not touched at all
423 # There can be upgrades where store is not touched at all
425 if upgrade_op.backup_store:
424 if upgrade_op.backup_store:
426 util.rename(currentrepo.spath, backupvfs.join(b'store'))
425 util.rename(currentrepo.spath, backupvfs.join(b'store'))
427 else:
426 else:
428 currentrepo.vfs.rmtree(b'store', forcibly=True)
427 currentrepo.vfs.rmtree(b'store', forcibly=True)
429 util.rename(upgradedrepo.spath, currentrepo.spath)
428 util.rename(upgradedrepo.spath, currentrepo.spath)
430
429
431
430
432 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
431 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
433 """Hook point for extensions to perform additional actions during upgrade.
432 """Hook point for extensions to perform additional actions during upgrade.
434
433
435 This function is called after revlogs and store files have been copied but
434 This function is called after revlogs and store files have been copied but
436 before the new store is swapped into the original location.
435 before the new store is swapped into the original location.
437 """
436 """
438
437
439
438
440 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
439 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
441 """Do the low-level work of upgrading a repository.
440 """Do the low-level work of upgrading a repository.
442
441
443 The upgrade is effectively performed as a copy between a source
442 The upgrade is effectively performed as a copy between a source
444 repository and a temporary destination repository.
443 repository and a temporary destination repository.
445
444
446 The source repository is unmodified for as long as possible so the
445 The source repository is unmodified for as long as possible so the
447 upgrade can abort at any time without causing loss of service for
446 upgrade can abort at any time without causing loss of service for
448 readers and without corrupting the source repository.
447 readers and without corrupting the source repository.
449 """
448 """
450 assert srcrepo.currentwlock()
449 assert srcrepo.currentwlock()
451 assert dstrepo.currentwlock()
450 assert dstrepo.currentwlock()
452 backuppath = None
451 backuppath = None
453 backupvfs = None
452 backupvfs = None
454
453
455 ui.status(
454 ui.status(
456 _(
455 _(
457 b'(it is safe to interrupt this process any time before '
456 b'(it is safe to interrupt this process any time before '
458 b'data migration completes)\n'
457 b'data migration completes)\n'
459 )
458 )
460 )
459 )
461
460
462 if upgrade_op.requirements_only:
461 if upgrade_op.requirements_only:
463 ui.status(_(b'upgrading repository requirements\n'))
462 ui.status(_(b'upgrading repository requirements\n'))
464 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
463 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
465 # if there is only one action and that is persistent nodemap upgrade
464 # if there is only one action and that is persistent nodemap upgrade
466 # directly write the nodemap file and update requirements instead of going
465 # directly write the nodemap file and update requirements instead of going
467 # through the whole cloning process
466 # through the whole cloning process
468 elif (
467 elif (
469 len(upgrade_op.upgrade_actions) == 1
468 len(upgrade_op.upgrade_actions) == 1
470 and b'persistent-nodemap' in upgrade_op._upgrade_actions_names
469 and b'persistent-nodemap' in upgrade_op._upgrade_actions_names
471 and not upgrade_op.removed_actions
470 and not upgrade_op.removed_actions
472 ):
471 ):
473 ui.status(
472 ui.status(
474 _(b'upgrading repository to use persistent nodemap feature\n')
473 _(b'upgrading repository to use persistent nodemap feature\n')
475 )
474 )
476 with srcrepo.transaction(b'upgrade') as tr:
475 with srcrepo.transaction(b'upgrade') as tr:
477 unfi = srcrepo.unfiltered()
476 unfi = srcrepo.unfiltered()
478 cl = unfi.changelog
477 cl = unfi.changelog
479 nodemap.persist_nodemap(tr, cl, force=True)
478 nodemap.persist_nodemap(tr, cl, force=True)
480 # we want to directly operate on the underlying revlog to force
479 # we want to directly operate on the underlying revlog to force
481 # create a nodemap file. This is fine since this is upgrade code
480 # create a nodemap file. This is fine since this is upgrade code
482 # and it heavily relies on repository being revlog based
481 # and it heavily relies on repository being revlog based
483 # hence accessing private attributes can be justified
482 # hence accessing private attributes can be justified
484 nodemap.persist_nodemap(
483 nodemap.persist_nodemap(
485 tr, unfi.manifestlog._rootstore._revlog, force=True
484 tr, unfi.manifestlog._rootstore._revlog, force=True
486 )
485 )
487 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
486 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
488 elif (
487 elif (
489 len(upgrade_op.removed_actions) == 1
488 len(upgrade_op.removed_actions) == 1
490 and [
489 and [
491 x
490 x
492 for x in upgrade_op.removed_actions
491 for x in upgrade_op.removed_actions
493 if x.name == b'persistent-nodemap'
492 if x.name == b'persistent-nodemap'
494 ]
493 ]
495 and not upgrade_op.upgrade_actions
494 and not upgrade_op.upgrade_actions
496 ):
495 ):
497 ui.status(
496 ui.status(
498 _(b'downgrading repository to not use persistent nodemap feature\n')
497 _(b'downgrading repository to not use persistent nodemap feature\n')
499 )
498 )
500 with srcrepo.transaction(b'upgrade') as tr:
499 with srcrepo.transaction(b'upgrade') as tr:
501 unfi = srcrepo.unfiltered()
500 unfi = srcrepo.unfiltered()
502 cl = unfi.changelog
501 cl = unfi.changelog
503 nodemap.delete_nodemap(tr, srcrepo, cl)
502 nodemap.delete_nodemap(tr, srcrepo, cl)
504 # check comment 20 lines above for accessing private attributes
503 # check comment 20 lines above for accessing private attributes
505 nodemap.delete_nodemap(
504 nodemap.delete_nodemap(
506 tr, srcrepo, unfi.manifestlog._rootstore._revlog
505 tr, srcrepo, unfi.manifestlog._rootstore._revlog
507 )
506 )
508 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
507 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
509 else:
508 else:
510 with dstrepo.transaction(b'upgrade') as tr:
509 with dstrepo.transaction(b'upgrade') as tr:
511 _clonerevlogs(
510 _clonerevlogs(
512 ui,
511 ui,
513 srcrepo,
512 srcrepo,
514 dstrepo,
513 dstrepo,
515 tr,
514 tr,
516 upgrade_op,
515 upgrade_op,
517 )
516 )
518
517
519 # Now copy other files in the store directory.
518 # Now copy other files in the store directory.
520 for p in _files_to_copy_post_revlog_clone(srcrepo):
519 for p in _files_to_copy_post_revlog_clone(srcrepo):
521 srcrepo.ui.status(_(b'copying %s\n') % p)
520 srcrepo.ui.status(_(b'copying %s\n') % p)
522 src = srcrepo.store.rawvfs.join(p)
521 src = srcrepo.store.rawvfs.join(p)
523 dst = dstrepo.store.rawvfs.join(p)
522 dst = dstrepo.store.rawvfs.join(p)
524 util.copyfile(src, dst, copystat=True)
523 util.copyfile(src, dst, copystat=True)
525
524
526 finishdatamigration(ui, srcrepo, dstrepo, requirements)
525 finishdatamigration(ui, srcrepo, dstrepo, requirements)
527
526
528 ui.status(_(b'data fully upgraded in a temporary repository\n'))
527 ui.status(_(b'data fully upgraded in a temporary repository\n'))
529
528
530 if upgrade_op.backup_store:
529 if upgrade_op.backup_store:
531 backuppath = pycompat.mkdtemp(
530 backuppath = pycompat.mkdtemp(
532 prefix=b'upgradebackup.', dir=srcrepo.path
531 prefix=b'upgradebackup.', dir=srcrepo.path
533 )
532 )
534 backupvfs = vfsmod.vfs(backuppath)
533 backupvfs = vfsmod.vfs(backuppath)
535
534
536 # Make a backup of requires file first, as it is the first to be modified.
535 # Make a backup of requires file first, as it is the first to be modified.
537 util.copyfile(
536 util.copyfile(
538 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
537 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
539 )
538 )
540
539
541 # We install an arbitrary requirement that clients must not support
540 # We install an arbitrary requirement that clients must not support
542 # as a mechanism to lock out new clients during the data swap. This is
541 # as a mechanism to lock out new clients during the data swap. This is
543 # better than allowing a client to continue while the repository is in
542 # better than allowing a client to continue while the repository is in
544 # an inconsistent state.
543 # an inconsistent state.
545 ui.status(
544 ui.status(
546 _(
545 _(
547 b'marking source repository as being upgraded; clients will be '
546 b'marking source repository as being upgraded; clients will be '
548 b'unable to read from repository\n'
547 b'unable to read from repository\n'
549 )
548 )
550 )
549 )
551 scmutil.writereporequirements(
550 scmutil.writereporequirements(
552 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
551 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
553 )
552 )
554
553
555 ui.status(_(b'starting in-place swap of repository data\n'))
554 ui.status(_(b'starting in-place swap of repository data\n'))
556 if upgrade_op.backup_store:
555 if upgrade_op.backup_store:
557 ui.status(
556 ui.status(
558 _(b'replaced files will be backed up at %s\n') % backuppath
557 _(b'replaced files will be backed up at %s\n') % backuppath
559 )
558 )
560
559
561 # Now swap in the new store directory. Doing it as a rename should make
560 # Now swap in the new store directory. Doing it as a rename should make
562 # the operation nearly instantaneous and atomic (at least in well-behaved
561 # the operation nearly instantaneous and atomic (at least in well-behaved
563 # environments).
562 # environments).
564 ui.status(_(b'replacing store...\n'))
563 ui.status(_(b'replacing store...\n'))
565 tstart = util.timer()
564 tstart = util.timer()
566 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
565 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
567 elapsed = util.timer() - tstart
566 elapsed = util.timer() - tstart
568 ui.status(
567 ui.status(
569 _(
568 _(
570 b'store replacement complete; repository was inconsistent for '
569 b'store replacement complete; repository was inconsistent for '
571 b'%0.1fs\n'
570 b'%0.1fs\n'
572 )
571 )
573 % elapsed
572 % elapsed
574 )
573 )
575
574
576 # We first write the requirements file. Any new requirements will lock
575 # We first write the requirements file. Any new requirements will lock
577 # out legacy clients.
576 # out legacy clients.
578 ui.status(
577 ui.status(
579 _(
578 _(
580 b'finalizing requirements file and making repository readable '
579 b'finalizing requirements file and making repository readable '
581 b'again\n'
580 b'again\n'
582 )
581 )
583 )
582 )
584 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
583 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
585
584
586 if upgrade_op.backup_store:
585 if upgrade_op.backup_store:
587 # The lock file from the old store won't be removed because nothing has a
586 # The lock file from the old store won't be removed because nothing has a
588 # reference to its new location. So clean it up manually. Alternatively, we
587 # reference to its new location. So clean it up manually. Alternatively, we
589 # could update srcrepo.svfs and other variables to point to the new
588 # could update srcrepo.svfs and other variables to point to the new
590 # location. This is simpler.
589 # location. This is simpler.
591 assert backupvfs is not None # help pytype
590 assert backupvfs is not None # help pytype
592 backupvfs.unlink(b'store/lock')
591 backupvfs.unlink(b'store/lock')
593
592
594 return backuppath
593 return backuppath
@@ -1,585 +1,561 b''
1 # storageutil.py - Storage functionality agnostic of backend implementation.
1 # storageutil.py - Storage functionality agnostic of backend implementation.
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11 import struct
11 import struct
12
12
13 from ..i18n import _
13 from ..i18n import _
14 from ..node import (
14 from ..node import (
15 bin,
15 bin,
16 nullrev,
16 nullrev,
17 sha1nodeconstants,
17 sha1nodeconstants,
18 )
18 )
19 from .. import (
19 from .. import (
20 dagop,
20 dagop,
21 error,
21 error,
22 mdiff,
22 mdiff,
23 pycompat,
23 pycompat,
24 )
24 )
25 from ..interfaces import repository
25 from ..interfaces import repository
26 from ..revlogutils import sidedata as sidedatamod
26 from ..revlogutils import sidedata as sidedatamod
27 from ..utils import hashutil
27 from ..utils import hashutil
28
28
29 _nullhash = hashutil.sha1(sha1nodeconstants.nullid)
29 _nullhash = hashutil.sha1(sha1nodeconstants.nullid)
30
30
31 # revision data contains extra metadata not part of the official digest
31 # revision data contains extra metadata not part of the official digest
32 # Only used in changegroup >= v4.
32 # Only used in changegroup >= v4.
33 CG_FLAG_SIDEDATA = 1
33 CG_FLAG_SIDEDATA = 1
34
34
35
35
36 def hashrevisionsha1(text, p1, p2):
36 def hashrevisionsha1(text, p1, p2):
37 """Compute the SHA-1 for revision data and its parents.
37 """Compute the SHA-1 for revision data and its parents.
38
38
39 This hash combines both the current file contents and its history
39 This hash combines both the current file contents and its history
40 in a manner that makes it easy to distinguish nodes with the same
40 in a manner that makes it easy to distinguish nodes with the same
41 content in the revision graph.
41 content in the revision graph.
42 """
42 """
43 # As of now, if one of the parent node is null, p2 is null
43 # As of now, if one of the parent node is null, p2 is null
44 if p2 == sha1nodeconstants.nullid:
44 if p2 == sha1nodeconstants.nullid:
45 # deep copy of a hash is faster than creating one
45 # deep copy of a hash is faster than creating one
46 s = _nullhash.copy()
46 s = _nullhash.copy()
47 s.update(p1)
47 s.update(p1)
48 else:
48 else:
49 # none of the parent nodes are nullid
49 # none of the parent nodes are nullid
50 if p1 < p2:
50 if p1 < p2:
51 a = p1
51 a = p1
52 b = p2
52 b = p2
53 else:
53 else:
54 a = p2
54 a = p2
55 b = p1
55 b = p1
56 s = hashutil.sha1(a)
56 s = hashutil.sha1(a)
57 s.update(b)
57 s.update(b)
58 s.update(text)
58 s.update(text)
59 return s.digest()
59 return s.digest()
60
60
61
61
62 METADATA_RE = re.compile(b'\x01\n')
62 METADATA_RE = re.compile(b'\x01\n')
63
63
64
64
65 def parsemeta(text):
65 def parsemeta(text):
66 """Parse metadata header from revision data.
66 """Parse metadata header from revision data.
67
67
68 Returns a 2-tuple of (metadata, offset), where both can be None if there
68 Returns a 2-tuple of (metadata, offset), where both can be None if there
69 is no metadata.
69 is no metadata.
70 """
70 """
71 # text can be buffer, so we can't use .startswith or .index
71 # text can be buffer, so we can't use .startswith or .index
72 if text[:2] != b'\x01\n':
72 if text[:2] != b'\x01\n':
73 return None, None
73 return None, None
74 s = METADATA_RE.search(text, 2).start()
74 s = METADATA_RE.search(text, 2).start()
75 mtext = text[2:s]
75 mtext = text[2:s]
76 meta = {}
76 meta = {}
77 for l in mtext.splitlines():
77 for l in mtext.splitlines():
78 k, v = l.split(b': ', 1)
78 k, v = l.split(b': ', 1)
79 meta[k] = v
79 meta[k] = v
80 return meta, s + 2
80 return meta, s + 2
81
81
82
82
83 def packmeta(meta, text):
83 def packmeta(meta, text):
84 """Add metadata to fulltext to produce revision text."""
84 """Add metadata to fulltext to produce revision text."""
85 keys = sorted(meta)
85 keys = sorted(meta)
86 metatext = b''.join(b'%s: %s\n' % (k, meta[k]) for k in keys)
86 metatext = b''.join(b'%s: %s\n' % (k, meta[k]) for k in keys)
87 return b'\x01\n%s\x01\n%s' % (metatext, text)
87 return b'\x01\n%s\x01\n%s' % (metatext, text)
88
88
89
89
90 def iscensoredtext(text):
90 def iscensoredtext(text):
91 meta = parsemeta(text)[0]
91 meta = parsemeta(text)[0]
92 return meta and b'censored' in meta
92 return meta and b'censored' in meta
93
93
94
94
95 def filtermetadata(text):
95 def filtermetadata(text):
96 """Extract just the revision data from source text.
96 """Extract just the revision data from source text.
97
97
98 Returns ``text`` unless it has a metadata header, in which case we return
98 Returns ``text`` unless it has a metadata header, in which case we return
99 a new buffer without hte metadata.
99 a new buffer without hte metadata.
100 """
100 """
101 if not text.startswith(b'\x01\n'):
101 if not text.startswith(b'\x01\n'):
102 return text
102 return text
103
103
104 offset = text.index(b'\x01\n', 2)
104 offset = text.index(b'\x01\n', 2)
105 return text[offset + 2 :]
105 return text[offset + 2 :]
106
106
107
107
108 def filerevisioncopied(store, node):
108 def filerevisioncopied(store, node):
109 """Resolve file revision copy metadata.
109 """Resolve file revision copy metadata.
110
110
111 Returns ``False`` if the file has no copy metadata. Otherwise a
111 Returns ``False`` if the file has no copy metadata. Otherwise a
112 2-tuple of the source filename and node.
112 2-tuple of the source filename and node.
113 """
113 """
114 if store.parents(node)[0] != sha1nodeconstants.nullid:
114 if store.parents(node)[0] != sha1nodeconstants.nullid:
115 return False
115 return False
116
116
117 meta = parsemeta(store.revision(node))[0]
117 meta = parsemeta(store.revision(node))[0]
118
118
119 # copy and copyrev occur in pairs. In rare cases due to old bugs,
119 # copy and copyrev occur in pairs. In rare cases due to old bugs,
120 # one can occur without the other. So ensure both are present to flag
120 # one can occur without the other. So ensure both are present to flag
121 # as a copy.
121 # as a copy.
122 if meta and b'copy' in meta and b'copyrev' in meta:
122 if meta and b'copy' in meta and b'copyrev' in meta:
123 return meta[b'copy'], bin(meta[b'copyrev'])
123 return meta[b'copy'], bin(meta[b'copyrev'])
124
124
125 return False
125 return False
126
126
127
127
128 def filedataequivalent(store, node, filedata):
128 def filedataequivalent(store, node, filedata):
129 """Determines whether file data is equivalent to a stored node.
129 """Determines whether file data is equivalent to a stored node.
130
130
131 Returns True if the passed file data would hash to the same value
131 Returns True if the passed file data would hash to the same value
132 as a stored revision and False otherwise.
132 as a stored revision and False otherwise.
133
133
134 When a stored revision is censored, filedata must be empty to have
134 When a stored revision is censored, filedata must be empty to have
135 equivalence.
135 equivalence.
136
136
137 When a stored revision has copy metadata, it is ignored as part
137 When a stored revision has copy metadata, it is ignored as part
138 of the compare.
138 of the compare.
139 """
139 """
140
140
141 if filedata.startswith(b'\x01\n'):
141 if filedata.startswith(b'\x01\n'):
142 revisiontext = b'\x01\n\x01\n' + filedata
142 revisiontext = b'\x01\n\x01\n' + filedata
143 else:
143 else:
144 revisiontext = filedata
144 revisiontext = filedata
145
145
146 p1, p2 = store.parents(node)
146 p1, p2 = store.parents(node)
147
147
148 computednode = hashrevisionsha1(revisiontext, p1, p2)
148 computednode = hashrevisionsha1(revisiontext, p1, p2)
149
149
150 if computednode == node:
150 if computednode == node:
151 return True
151 return True
152
152
153 # Censored files compare against the empty file.
153 # Censored files compare against the empty file.
154 if store.iscensored(store.rev(node)):
154 if store.iscensored(store.rev(node)):
155 return filedata == b''
155 return filedata == b''
156
156
157 # Renaming a file produces a different hash, even if the data
157 # Renaming a file produces a different hash, even if the data
158 # remains unchanged. Check if that's the case.
158 # remains unchanged. Check if that's the case.
159 if store.renamed(node):
159 if store.renamed(node):
160 return store.read(node) == filedata
160 return store.read(node) == filedata
161
161
162 return False
162 return False
163
163
164
164
165 def iterrevs(storelen, start=0, stop=None):
165 def iterrevs(storelen, start=0, stop=None):
166 """Iterate over revision numbers in a store."""
166 """Iterate over revision numbers in a store."""
167 step = 1
167 step = 1
168
168
169 if stop is not None:
169 if stop is not None:
170 if start > stop:
170 if start > stop:
171 step = -1
171 step = -1
172 stop += step
172 stop += step
173 if stop > storelen:
173 if stop > storelen:
174 stop = storelen
174 stop = storelen
175 else:
175 else:
176 stop = storelen
176 stop = storelen
177
177
178 return pycompat.xrange(start, stop, step)
178 return pycompat.xrange(start, stop, step)
179
179
180
180
181 def fileidlookup(store, fileid, identifier):
181 def fileidlookup(store, fileid, identifier):
182 """Resolve the file node for a value.
182 """Resolve the file node for a value.
183
183
184 ``store`` is an object implementing the ``ifileindex`` interface.
184 ``store`` is an object implementing the ``ifileindex`` interface.
185
185
186 ``fileid`` can be:
186 ``fileid`` can be:
187
187
188 * A 20 or 32 byte binary node.
188 * A 20 or 32 byte binary node.
189 * An integer revision number
189 * An integer revision number
190 * A 40 or 64 byte hex node.
190 * A 40 or 64 byte hex node.
191 * A bytes that can be parsed as an integer representing a revision number.
191 * A bytes that can be parsed as an integer representing a revision number.
192
192
193 ``identifier`` is used to populate ``error.LookupError`` with an identifier
193 ``identifier`` is used to populate ``error.LookupError`` with an identifier
194 for the store.
194 for the store.
195
195
196 Raises ``error.LookupError`` on failure.
196 Raises ``error.LookupError`` on failure.
197 """
197 """
198 if isinstance(fileid, int):
198 if isinstance(fileid, int):
199 try:
199 try:
200 return store.node(fileid)
200 return store.node(fileid)
201 except IndexError:
201 except IndexError:
202 raise error.LookupError(
202 raise error.LookupError(
203 b'%d' % fileid, identifier, _(b'no match found')
203 b'%d' % fileid, identifier, _(b'no match found')
204 )
204 )
205
205
206 if len(fileid) in (20, 32):
206 if len(fileid) in (20, 32):
207 try:
207 try:
208 store.rev(fileid)
208 store.rev(fileid)
209 return fileid
209 return fileid
210 except error.LookupError:
210 except error.LookupError:
211 pass
211 pass
212
212
213 if len(fileid) in (40, 64):
213 if len(fileid) in (40, 64):
214 try:
214 try:
215 rawnode = bin(fileid)
215 rawnode = bin(fileid)
216 store.rev(rawnode)
216 store.rev(rawnode)
217 return rawnode
217 return rawnode
218 except TypeError:
218 except TypeError:
219 pass
219 pass
220
220
221 try:
221 try:
222 rev = int(fileid)
222 rev = int(fileid)
223
223
224 if b'%d' % rev != fileid:
224 if b'%d' % rev != fileid:
225 raise ValueError
225 raise ValueError
226
226
227 try:
227 try:
228 return store.node(rev)
228 return store.node(rev)
229 except (IndexError, TypeError):
229 except (IndexError, TypeError):
230 pass
230 pass
231 except (ValueError, OverflowError):
231 except (ValueError, OverflowError):
232 pass
232 pass
233
233
234 raise error.LookupError(fileid, identifier, _(b'no match found'))
234 raise error.LookupError(fileid, identifier, _(b'no match found'))
235
235
236
236
237 def resolvestripinfo(minlinkrev, tiprev, headrevs, linkrevfn, parentrevsfn):
237 def resolvestripinfo(minlinkrev, tiprev, headrevs, linkrevfn, parentrevsfn):
238 """Resolve information needed to strip revisions.
238 """Resolve information needed to strip revisions.
239
239
240 Finds the minimum revision number that must be stripped in order to
240 Finds the minimum revision number that must be stripped in order to
241 strip ``minlinkrev``.
241 strip ``minlinkrev``.
242
242
243 Returns a 2-tuple of the minimum revision number to do that and a set
243 Returns a 2-tuple of the minimum revision number to do that and a set
244 of all revision numbers that have linkrevs that would be broken
244 of all revision numbers that have linkrevs that would be broken
245 by that strip.
245 by that strip.
246
246
247 ``tiprev`` is the current tip-most revision. It is ``len(store) - 1``.
247 ``tiprev`` is the current tip-most revision. It is ``len(store) - 1``.
248 ``headrevs`` is an iterable of head revisions.
248 ``headrevs`` is an iterable of head revisions.
249 ``linkrevfn`` is a callable that receives a revision and returns a linked
249 ``linkrevfn`` is a callable that receives a revision and returns a linked
250 revision.
250 revision.
251 ``parentrevsfn`` is a callable that receives a revision number and returns
251 ``parentrevsfn`` is a callable that receives a revision number and returns
252 an iterable of its parent revision numbers.
252 an iterable of its parent revision numbers.
253 """
253 """
254 brokenrevs = set()
254 brokenrevs = set()
255 strippoint = tiprev + 1
255 strippoint = tiprev + 1
256
256
257 heads = {}
257 heads = {}
258 futurelargelinkrevs = set()
258 futurelargelinkrevs = set()
259 for head in headrevs:
259 for head in headrevs:
260 headlinkrev = linkrevfn(head)
260 headlinkrev = linkrevfn(head)
261 heads[head] = headlinkrev
261 heads[head] = headlinkrev
262 if headlinkrev >= minlinkrev:
262 if headlinkrev >= minlinkrev:
263 futurelargelinkrevs.add(headlinkrev)
263 futurelargelinkrevs.add(headlinkrev)
264
264
265 # This algorithm involves walking down the rev graph, starting at the
265 # This algorithm involves walking down the rev graph, starting at the
266 # heads. Since the revs are topologically sorted according to linkrev,
266 # heads. Since the revs are topologically sorted according to linkrev,
267 # once all head linkrevs are below the minlink, we know there are
267 # once all head linkrevs are below the minlink, we know there are
268 # no more revs that could have a linkrev greater than minlink.
268 # no more revs that could have a linkrev greater than minlink.
269 # So we can stop walking.
269 # So we can stop walking.
270 while futurelargelinkrevs:
270 while futurelargelinkrevs:
271 strippoint -= 1
271 strippoint -= 1
272 linkrev = heads.pop(strippoint)
272 linkrev = heads.pop(strippoint)
273
273
274 if linkrev < minlinkrev:
274 if linkrev < minlinkrev:
275 brokenrevs.add(strippoint)
275 brokenrevs.add(strippoint)
276 else:
276 else:
277 futurelargelinkrevs.remove(linkrev)
277 futurelargelinkrevs.remove(linkrev)
278
278
279 for p in parentrevsfn(strippoint):
279 for p in parentrevsfn(strippoint):
280 if p != nullrev:
280 if p != nullrev:
281 plinkrev = linkrevfn(p)
281 plinkrev = linkrevfn(p)
282 heads[p] = plinkrev
282 heads[p] = plinkrev
283 if plinkrev >= minlinkrev:
283 if plinkrev >= minlinkrev:
284 futurelargelinkrevs.add(plinkrev)
284 futurelargelinkrevs.add(plinkrev)
285
285
286 return strippoint, brokenrevs
286 return strippoint, brokenrevs
287
287
288
288
289 def emitrevisions(
289 def emitrevisions(
290 store,
290 store,
291 nodes,
291 nodes,
292 nodesorder,
292 nodesorder,
293 resultcls,
293 resultcls,
294 deltaparentfn=None,
294 deltaparentfn=None,
295 candeltafn=None,
295 candeltafn=None,
296 rawsizefn=None,
296 rawsizefn=None,
297 revdifffn=None,
297 revdifffn=None,
298 flagsfn=None,
298 flagsfn=None,
299 deltamode=repository.CG_DELTAMODE_STD,
299 deltamode=repository.CG_DELTAMODE_STD,
300 revisiondata=False,
300 revisiondata=False,
301 assumehaveparentrevisions=False,
301 assumehaveparentrevisions=False,
302 sidedata_helpers=None,
302 sidedata_helpers=None,
303 ):
303 ):
304 """Generic implementation of ifiledata.emitrevisions().
304 """Generic implementation of ifiledata.emitrevisions().
305
305
306 Emitting revision data is subtly complex. This function attempts to
306 Emitting revision data is subtly complex. This function attempts to
307 encapsulate all the logic for doing so in a backend-agnostic way.
307 encapsulate all the logic for doing so in a backend-agnostic way.
308
308
309 ``store``
309 ``store``
310 Object conforming to ``ifilestorage`` interface.
310 Object conforming to ``ifilestorage`` interface.
311
311
312 ``nodes``
312 ``nodes``
313 List of revision nodes whose data to emit.
313 List of revision nodes whose data to emit.
314
314
315 ``resultcls``
315 ``resultcls``
316 A type implementing the ``irevisiondelta`` interface that will be
316 A type implementing the ``irevisiondelta`` interface that will be
317 constructed and returned.
317 constructed and returned.
318
318
319 ``deltaparentfn`` (optional)
319 ``deltaparentfn`` (optional)
320 Callable receiving a revision number and returning the revision number
320 Callable receiving a revision number and returning the revision number
321 of a revision that the internal delta is stored against. This delta
321 of a revision that the internal delta is stored against. This delta
322 will be preferred over computing a new arbitrary delta.
322 will be preferred over computing a new arbitrary delta.
323
323
324 If not defined, a delta will always be computed from raw revision
324 If not defined, a delta will always be computed from raw revision
325 data.
325 data.
326
326
327 ``candeltafn`` (optional)
327 ``candeltafn`` (optional)
328 Callable receiving a pair of revision numbers that returns a bool
328 Callable receiving a pair of revision numbers that returns a bool
329 indicating whether a delta between them can be produced.
329 indicating whether a delta between them can be produced.
330
330
331 If not defined, it is assumed that any two revisions can delta with
331 If not defined, it is assumed that any two revisions can delta with
332 each other.
332 each other.
333
333
334 ``rawsizefn`` (optional)
334 ``rawsizefn`` (optional)
335 Callable receiving a revision number and returning the length of the
335 Callable receiving a revision number and returning the length of the
336 ``store.rawdata(rev)``.
336 ``store.rawdata(rev)``.
337
337
338 If not defined, ``len(store.rawdata(rev))`` will be called.
338 If not defined, ``len(store.rawdata(rev))`` will be called.
339
339
340 ``revdifffn`` (optional)
340 ``revdifffn`` (optional)
341 Callable receiving a pair of revision numbers that returns a delta
341 Callable receiving a pair of revision numbers that returns a delta
342 between them.
342 between them.
343
343
344 If not defined, a delta will be computed by invoking mdiff code
344 If not defined, a delta will be computed by invoking mdiff code
345 on ``store.revision()`` results.
345 on ``store.revision()`` results.
346
346
347 Defining this function allows a precomputed or stored delta to be
347 Defining this function allows a precomputed or stored delta to be
348 used without having to compute on.
348 used without having to compute on.
349
349
350 ``flagsfn`` (optional)
350 ``flagsfn`` (optional)
351 Callable receiving a revision number and returns the integer flags
351 Callable receiving a revision number and returns the integer flags
352 value for it. If not defined, flags value will be 0.
352 value for it. If not defined, flags value will be 0.
353
353
354 ``deltamode``
354 ``deltamode``
355 constaint on delta to be sent:
355 constaint on delta to be sent:
356 * CG_DELTAMODE_STD - normal mode, try to reuse storage deltas,
356 * CG_DELTAMODE_STD - normal mode, try to reuse storage deltas,
357 * CG_DELTAMODE_PREV - only delta against "prev",
357 * CG_DELTAMODE_PREV - only delta against "prev",
358 * CG_DELTAMODE_FULL - only issue full snapshot.
358 * CG_DELTAMODE_FULL - only issue full snapshot.
359
359
360 Whether to send fulltext revisions instead of deltas, if allowed.
360 Whether to send fulltext revisions instead of deltas, if allowed.
361
361
362 ``nodesorder``
362 ``nodesorder``
363 ``revisiondata``
363 ``revisiondata``
364 ``assumehaveparentrevisions``
364 ``assumehaveparentrevisions``
365 ``sidedata_helpers`` (optional)
365 ``sidedata_helpers`` (optional)
366 If not None, means that sidedata should be included.
366 If not None, means that sidedata should be included.
367 A dictionary of revlog type to tuples of `(repo, computers, removers)`:
367 A dictionary of revlog type to tuples of `(repo, computers, removers)`:
368 * `repo` is used as an argument for computers
368 * `repo` is used as an argument for computers
369 * `computers` is a list of `(category, (keys, computer, flags)` that
369 * `computers` is a list of `(category, (keys, computer, flags)` that
370 compute the missing sidedata categories that were asked:
370 compute the missing sidedata categories that were asked:
371 * `category` is the sidedata category
371 * `category` is the sidedata category
372 * `keys` are the sidedata keys to be affected
372 * `keys` are the sidedata keys to be affected
373 * `flags` is a bitmask (an integer) of flags to remove when
373 * `flags` is a bitmask (an integer) of flags to remove when
374 removing the category.
374 removing the category.
375 * `computer` is the function `(repo, store, rev, sidedata)` that
375 * `computer` is the function `(repo, store, rev, sidedata)` that
376 returns a tuple of
376 returns a tuple of
377 `(new sidedata dict, (flags to add, flags to remove))`.
377 `(new sidedata dict, (flags to add, flags to remove))`.
378 For example, it will return `({}, (0, 1 << 15))` to return no
378 For example, it will return `({}, (0, 1 << 15))` to return no
379 sidedata, with no flags to add and one flag to remove.
379 sidedata, with no flags to add and one flag to remove.
380 * `removers` will remove the keys corresponding to the categories
380 * `removers` will remove the keys corresponding to the categories
381 that are present, but not needed.
381 that are present, but not needed.
382 If both `computers` and `removers` are empty, sidedata are simply not
382 If both `computers` and `removers` are empty, sidedata are simply not
383 transformed.
383 transformed.
384 Revlog types are `changelog`, `manifest` or `filelog`.
384 Revlog types are `changelog`, `manifest` or `filelog`.
385 """
385 """
386
386
387 fnode = store.node
387 fnode = store.node
388 frev = store.rev
388 frev = store.rev
389
389
390 if nodesorder == b'nodes':
390 if nodesorder == b'nodes':
391 revs = [frev(n) for n in nodes]
391 revs = [frev(n) for n in nodes]
392 elif nodesorder == b'linear':
392 elif nodesorder == b'linear':
393 revs = {frev(n) for n in nodes}
393 revs = {frev(n) for n in nodes}
394 revs = dagop.linearize(revs, store.parentrevs)
394 revs = dagop.linearize(revs, store.parentrevs)
395 else: # storage and default
395 else: # storage and default
396 revs = sorted(frev(n) for n in nodes)
396 revs = sorted(frev(n) for n in nodes)
397
397
398 prevrev = None
398 prevrev = None
399
399
400 if deltamode == repository.CG_DELTAMODE_PREV or assumehaveparentrevisions:
400 if deltamode == repository.CG_DELTAMODE_PREV or assumehaveparentrevisions:
401 prevrev = store.parentrevs(revs[0])[0]
401 prevrev = store.parentrevs(revs[0])[0]
402
402
403 # Set of revs available to delta against.
403 # Set of revs available to delta against.
404 available = set()
404 available = set()
405
405
406 for rev in revs:
406 for rev in revs:
407 if rev == nullrev:
407 if rev == nullrev:
408 continue
408 continue
409
409
410 node = fnode(rev)
410 node = fnode(rev)
411 p1rev, p2rev = store.parentrevs(rev)
411 p1rev, p2rev = store.parentrevs(rev)
412
412
413 if deltaparentfn:
413 if deltaparentfn:
414 deltaparentrev = deltaparentfn(rev)
414 deltaparentrev = deltaparentfn(rev)
415 else:
415 else:
416 deltaparentrev = nullrev
416 deltaparentrev = nullrev
417
417
418 # Forced delta against previous mode.
418 # Forced delta against previous mode.
419 if deltamode == repository.CG_DELTAMODE_PREV:
419 if deltamode == repository.CG_DELTAMODE_PREV:
420 baserev = prevrev
420 baserev = prevrev
421
421
422 # We're instructed to send fulltext. Honor that.
422 # We're instructed to send fulltext. Honor that.
423 elif deltamode == repository.CG_DELTAMODE_FULL:
423 elif deltamode == repository.CG_DELTAMODE_FULL:
424 baserev = nullrev
424 baserev = nullrev
425 # We're instructed to use p1. Honor that
425 # We're instructed to use p1. Honor that
426 elif deltamode == repository.CG_DELTAMODE_P1:
426 elif deltamode == repository.CG_DELTAMODE_P1:
427 baserev = p1rev
427 baserev = p1rev
428
428
429 # There is a delta in storage. We try to use that because it
429 # There is a delta in storage. We try to use that because it
430 # amounts to effectively copying data from storage and is
430 # amounts to effectively copying data from storage and is
431 # therefore the fastest.
431 # therefore the fastest.
432 elif deltaparentrev != nullrev:
432 elif deltaparentrev != nullrev:
433 # Base revision was already emitted in this group. We can
433 # Base revision was already emitted in this group. We can
434 # always safely use the delta.
434 # always safely use the delta.
435 if deltaparentrev in available:
435 if deltaparentrev in available:
436 baserev = deltaparentrev
436 baserev = deltaparentrev
437
437
438 # Base revision is a parent that hasn't been emitted already.
438 # Base revision is a parent that hasn't been emitted already.
439 # Use it if we can assume the receiver has the parent revision.
439 # Use it if we can assume the receiver has the parent revision.
440 elif assumehaveparentrevisions and deltaparentrev in (p1rev, p2rev):
440 elif assumehaveparentrevisions and deltaparentrev in (p1rev, p2rev):
441 baserev = deltaparentrev
441 baserev = deltaparentrev
442
442
443 # No guarantee the receiver has the delta parent. Send delta
443 # No guarantee the receiver has the delta parent. Send delta
444 # against last revision (if possible), which in the common case
444 # against last revision (if possible), which in the common case
445 # should be similar enough to this revision that the delta is
445 # should be similar enough to this revision that the delta is
446 # reasonable.
446 # reasonable.
447 elif prevrev is not None:
447 elif prevrev is not None:
448 baserev = prevrev
448 baserev = prevrev
449 else:
449 else:
450 baserev = nullrev
450 baserev = nullrev
451
451
452 # Storage has a fulltext revision.
452 # Storage has a fulltext revision.
453
453
454 # Let's use the previous revision, which is as good a guess as any.
454 # Let's use the previous revision, which is as good a guess as any.
455 # There is definitely room to improve this logic.
455 # There is definitely room to improve this logic.
456 elif prevrev is not None:
456 elif prevrev is not None:
457 baserev = prevrev
457 baserev = prevrev
458 else:
458 else:
459 baserev = nullrev
459 baserev = nullrev
460
460
461 # But we can't actually use our chosen delta base for whatever
461 # But we can't actually use our chosen delta base for whatever
462 # reason. Reset to fulltext.
462 # reason. Reset to fulltext.
463 if baserev != nullrev and (candeltafn and not candeltafn(baserev, rev)):
463 if baserev != nullrev and (candeltafn and not candeltafn(baserev, rev)):
464 baserev = nullrev
464 baserev = nullrev
465
465
466 revision = None
466 revision = None
467 delta = None
467 delta = None
468 baserevisionsize = None
468 baserevisionsize = None
469
469
470 if revisiondata:
470 if revisiondata:
471 if store.iscensored(baserev) or store.iscensored(rev):
471 if store.iscensored(baserev) or store.iscensored(rev):
472 try:
472 try:
473 revision = store.rawdata(node)
473 revision = store.rawdata(node)
474 except error.CensoredNodeError as e:
474 except error.CensoredNodeError as e:
475 revision = e.tombstone
475 revision = e.tombstone
476
476
477 if baserev != nullrev:
477 if baserev != nullrev:
478 if rawsizefn:
478 if rawsizefn:
479 baserevisionsize = rawsizefn(baserev)
479 baserevisionsize = rawsizefn(baserev)
480 else:
480 else:
481 baserevisionsize = len(store.rawdata(baserev))
481 baserevisionsize = len(store.rawdata(baserev))
482
482
483 elif (
483 elif (
484 baserev == nullrev and deltamode != repository.CG_DELTAMODE_PREV
484 baserev == nullrev and deltamode != repository.CG_DELTAMODE_PREV
485 ):
485 ):
486 revision = store.rawdata(node)
486 revision = store.rawdata(node)
487 available.add(rev)
487 available.add(rev)
488 else:
488 else:
489 if revdifffn:
489 if revdifffn:
490 delta = revdifffn(baserev, rev)
490 delta = revdifffn(baserev, rev)
491 else:
491 else:
492 delta = mdiff.textdiff(
492 delta = mdiff.textdiff(
493 store.rawdata(baserev), store.rawdata(rev)
493 store.rawdata(baserev), store.rawdata(rev)
494 )
494 )
495
495
496 available.add(rev)
496 available.add(rev)
497
497
498 serialized_sidedata = None
498 serialized_sidedata = None
499 sidedata_flags = (0, 0)
499 sidedata_flags = (0, 0)
500 if sidedata_helpers:
500 if sidedata_helpers:
501 old_sidedata = store.sidedata(rev)
501 old_sidedata = store.sidedata(rev)
502 sidedata, sidedata_flags = run_sidedata_helpers(
502 sidedata, sidedata_flags = sidedatamod.run_sidedata_helpers(
503 store=store,
503 store=store,
504 sidedata_helpers=sidedata_helpers,
504 sidedata_helpers=sidedata_helpers,
505 sidedata=old_sidedata,
505 sidedata=old_sidedata,
506 rev=rev,
506 rev=rev,
507 )
507 )
508 if sidedata:
508 if sidedata:
509 serialized_sidedata = sidedatamod.serialize_sidedata(sidedata)
509 serialized_sidedata = sidedatamod.serialize_sidedata(sidedata)
510
510
511 flags = flagsfn(rev) if flagsfn else 0
511 flags = flagsfn(rev) if flagsfn else 0
512 protocol_flags = 0
512 protocol_flags = 0
513 if serialized_sidedata:
513 if serialized_sidedata:
514 # Advertise that sidedata exists to the other side
514 # Advertise that sidedata exists to the other side
515 protocol_flags |= CG_FLAG_SIDEDATA
515 protocol_flags |= CG_FLAG_SIDEDATA
516 # Computers and removers can return flags to add and/or remove
516 # Computers and removers can return flags to add and/or remove
517 flags = flags | sidedata_flags[0] & ~sidedata_flags[1]
517 flags = flags | sidedata_flags[0] & ~sidedata_flags[1]
518
518
519 yield resultcls(
519 yield resultcls(
520 node=node,
520 node=node,
521 p1node=fnode(p1rev),
521 p1node=fnode(p1rev),
522 p2node=fnode(p2rev),
522 p2node=fnode(p2rev),
523 basenode=fnode(baserev),
523 basenode=fnode(baserev),
524 flags=flags,
524 flags=flags,
525 baserevisionsize=baserevisionsize,
525 baserevisionsize=baserevisionsize,
526 revision=revision,
526 revision=revision,
527 delta=delta,
527 delta=delta,
528 sidedata=serialized_sidedata,
528 sidedata=serialized_sidedata,
529 protocol_flags=protocol_flags,
529 protocol_flags=protocol_flags,
530 )
530 )
531
531
532 prevrev = rev
532 prevrev = rev
533
533
534
534
535 def run_sidedata_helpers(store, sidedata_helpers, sidedata, rev):
536 """Returns the sidedata for the given revision after running through
537 the given helpers.
538 - `store`: the revlog this applies to (changelog, manifest, or filelog
539 instance)
540 - `sidedata_helpers`: see `storageutil.emitrevisions`
541 - `sidedata`: previous sidedata at the given rev, if any
542 - `rev`: affected rev of `store`
543 """
544 repo, sd_computers, sd_removers = sidedata_helpers
545 kind = store.revlog_kind
546 flags_to_add = 0
547 flags_to_remove = 0
548 for _keys, sd_computer, _flags in sd_computers.get(kind, []):
549 sidedata, flags = sd_computer(repo, store, rev, sidedata)
550 flags_to_add |= flags[0]
551 flags_to_remove |= flags[1]
552 for keys, _computer, flags in sd_removers.get(kind, []):
553 for key in keys:
554 sidedata.pop(key, None)
555 flags_to_remove |= flags
556 return sidedata, (flags_to_add, flags_to_remove)
557
558
559 def deltaiscensored(delta, baserev, baselenfn):
535 def deltaiscensored(delta, baserev, baselenfn):
560 """Determine if a delta represents censored revision data.
536 """Determine if a delta represents censored revision data.
561
537
562 ``baserev`` is the base revision this delta is encoded against.
538 ``baserev`` is the base revision this delta is encoded against.
563 ``baselenfn`` is a callable receiving a revision number that resolves the
539 ``baselenfn`` is a callable receiving a revision number that resolves the
564 length of the revision fulltext.
540 length of the revision fulltext.
565
541
566 Returns a bool indicating if the result of the delta represents a censored
542 Returns a bool indicating if the result of the delta represents a censored
567 revision.
543 revision.
568 """
544 """
569 # Fragile heuristic: unless new file meta keys are added alphabetically
545 # Fragile heuristic: unless new file meta keys are added alphabetically
570 # preceding "censored", all censored revisions are prefixed by
546 # preceding "censored", all censored revisions are prefixed by
571 # "\1\ncensored:". A delta producing such a censored revision must be a
547 # "\1\ncensored:". A delta producing such a censored revision must be a
572 # full-replacement delta, so we inspect the first and only patch in the
548 # full-replacement delta, so we inspect the first and only patch in the
573 # delta for this prefix.
549 # delta for this prefix.
574 hlen = struct.calcsize(b">lll")
550 hlen = struct.calcsize(b">lll")
575 if len(delta) <= hlen:
551 if len(delta) <= hlen:
576 return False
552 return False
577
553
578 oldlen = baselenfn(baserev)
554 oldlen = baselenfn(baserev)
579 newlen = len(delta) - hlen
555 newlen = len(delta) - hlen
580 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
556 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
581 return False
557 return False
582
558
583 add = b"\1\ncensored:"
559 add = b"\1\ncensored:"
584 addlen = len(add)
560 addlen = len(add)
585 return newlen >= addlen and delta[hlen : hlen + addlen] == add
561 return newlen >= addlen and delta[hlen : hlen + addlen] == add
@@ -1,106 +1,105 b''
1 # ext-sidedata.py - small extension to test the sidedata logic
1 # ext-sidedata.py - small extension to test the sidedata logic
2 #
2 #
3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net>
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 hashlib
10 import hashlib
11 import struct
11 import struct
12
12
13 from mercurial.node import nullrev
13 from mercurial.node import nullrev
14 from mercurial import (
14 from mercurial import (
15 changegroup,
16 extensions,
15 extensions,
17 requirements,
16 requirements,
18 revlog,
17 revlog,
19 )
18 )
20
19
21 from mercurial.upgrade_utils import engine as upgrade_engine
20 from mercurial.upgrade_utils import engine as upgrade_engine
22
21
23 from mercurial.revlogutils import constants
22 from mercurial.revlogutils import constants
24 from mercurial.revlogutils import sidedata
23 from mercurial.revlogutils import sidedata
25
24
26
25
27 def wrapaddrevision(
26 def wrapaddrevision(
28 orig, self, text, transaction, link, p1, p2, *args, **kwargs
27 orig, self, text, transaction, link, p1, p2, *args, **kwargs
29 ):
28 ):
30 if kwargs.get('sidedata') is None:
29 if kwargs.get('sidedata') is None:
31 kwargs['sidedata'] = {}
30 kwargs['sidedata'] = {}
32 sd = kwargs['sidedata']
31 sd = kwargs['sidedata']
33 ## let's store some arbitrary data just for testing
32 ## let's store some arbitrary data just for testing
34 # text length
33 # text length
35 sd[sidedata.SD_TEST1] = struct.pack('>I', len(text))
34 sd[sidedata.SD_TEST1] = struct.pack('>I', len(text))
36 # and sha2 hashes
35 # and sha2 hashes
37 sha256 = hashlib.sha256(text).digest()
36 sha256 = hashlib.sha256(text).digest()
38 sd[sidedata.SD_TEST2] = struct.pack('>32s', sha256)
37 sd[sidedata.SD_TEST2] = struct.pack('>32s', sha256)
39 return orig(self, text, transaction, link, p1, p2, *args, **kwargs)
38 return orig(self, text, transaction, link, p1, p2, *args, **kwargs)
40
39
41
40
42 def wrap_revisiondata(orig, self, nodeorrev, *args, **kwargs):
41 def wrap_revisiondata(orig, self, nodeorrev, *args, **kwargs):
43 text, sd = orig(self, nodeorrev, *args, **kwargs)
42 text, sd = orig(self, nodeorrev, *args, **kwargs)
44 if getattr(self, 'sidedatanocheck', False):
43 if getattr(self, 'sidedatanocheck', False):
45 return text, sd
44 return text, sd
46 if self.version & 0xFFFF != 2:
45 if self.version & 0xFFFF != 2:
47 return text, sd
46 return text, sd
48 if nodeorrev != nullrev and nodeorrev != self.nullid:
47 if nodeorrev != nullrev and nodeorrev != self.nullid:
49 cat1 = sd.get(sidedata.SD_TEST1)
48 cat1 = sd.get(sidedata.SD_TEST1)
50 if cat1 is not None and len(text) != struct.unpack('>I', cat1)[0]:
49 if cat1 is not None and len(text) != struct.unpack('>I', cat1)[0]:
51 raise RuntimeError('text size mismatch')
50 raise RuntimeError('text size mismatch')
52 expected = sd.get(sidedata.SD_TEST2)
51 expected = sd.get(sidedata.SD_TEST2)
53 got = hashlib.sha256(text).digest()
52 got = hashlib.sha256(text).digest()
54 if expected is not None and got != expected:
53 if expected is not None and got != expected:
55 raise RuntimeError('sha256 mismatch')
54 raise RuntimeError('sha256 mismatch')
56 return text, sd
55 return text, sd
57
56
58
57
59 def wrapget_sidedata_helpers(orig, srcrepo, dstrepo):
58 def wrapget_sidedata_helpers(orig, srcrepo, dstrepo):
60 repo, computers, removers = orig(srcrepo, dstrepo)
59 repo, computers, removers = orig(srcrepo, dstrepo)
61 assert not computers and not removers # deal with composition later
60 assert not computers and not removers # deal with composition later
62 addedreqs = dstrepo.requirements - srcrepo.requirements
61 addedreqs = dstrepo.requirements - srcrepo.requirements
63
62
64 if requirements.SIDEDATA_REQUIREMENT in addedreqs:
63 if requirements.SIDEDATA_REQUIREMENT in addedreqs:
65
64
66 def computer(repo, revlog, rev, old_sidedata):
65 def computer(repo, revlog, rev, old_sidedata):
67 assert not old_sidedata # not supported yet
66 assert not old_sidedata # not supported yet
68 update = {}
67 update = {}
69 revlog.sidedatanocheck = True
68 revlog.sidedatanocheck = True
70 try:
69 try:
71 text = revlog.revision(rev)
70 text = revlog.revision(rev)
72 finally:
71 finally:
73 del revlog.sidedatanocheck
72 del revlog.sidedatanocheck
74 ## let's store some arbitrary data just for testing
73 ## let's store some arbitrary data just for testing
75 # text length
74 # text length
76 update[sidedata.SD_TEST1] = struct.pack('>I', len(text))
75 update[sidedata.SD_TEST1] = struct.pack('>I', len(text))
77 # and sha2 hashes
76 # and sha2 hashes
78 sha256 = hashlib.sha256(text).digest()
77 sha256 = hashlib.sha256(text).digest()
79 update[sidedata.SD_TEST2] = struct.pack('>32s', sha256)
78 update[sidedata.SD_TEST2] = struct.pack('>32s', sha256)
80 return update, (0, 0)
79 return update, (0, 0)
81
80
82 srcrepo.register_sidedata_computer(
81 srcrepo.register_sidedata_computer(
83 constants.KIND_CHANGELOG,
82 constants.KIND_CHANGELOG,
84 b"whatever",
83 b"whatever",
85 (sidedata.SD_TEST1, sidedata.SD_TEST2),
84 (sidedata.SD_TEST1, sidedata.SD_TEST2),
86 computer,
85 computer,
87 0,
86 0,
88 )
87 )
89 dstrepo.register_wanted_sidedata(b"whatever")
88 dstrepo.register_wanted_sidedata(b"whatever")
90
89
91 return changegroup.get_sidedata_helpers(srcrepo, dstrepo._wanted_sidedata)
90 return sidedata.get_sidedata_helpers(srcrepo, dstrepo._wanted_sidedata)
92
91
93
92
94 def extsetup(ui):
93 def extsetup(ui):
95 extensions.wrapfunction(revlog.revlog, 'addrevision', wrapaddrevision)
94 extensions.wrapfunction(revlog.revlog, 'addrevision', wrapaddrevision)
96 extensions.wrapfunction(revlog.revlog, '_revisiondata', wrap_revisiondata)
95 extensions.wrapfunction(revlog.revlog, '_revisiondata', wrap_revisiondata)
97 extensions.wrapfunction(
96 extensions.wrapfunction(
98 upgrade_engine, 'get_sidedata_helpers', wrapget_sidedata_helpers
97 upgrade_engine, 'get_sidedata_helpers', wrapget_sidedata_helpers
99 )
98 )
100
99
101
100
102 def reposetup(ui, repo):
101 def reposetup(ui, repo):
103 # We don't register sidedata computers because we don't care within these
102 # We don't register sidedata computers because we don't care within these
104 # tests
103 # tests
105 repo.register_wanted_sidedata(sidedata.SD_TEST1)
104 repo.register_wanted_sidedata(sidedata.SD_TEST1)
106 repo.register_wanted_sidedata(sidedata.SD_TEST2)
105 repo.register_wanted_sidedata(sidedata.SD_TEST2)
General Comments 0
You need to be logged in to leave comments. Login now